Guest Posted June 7, 2020 39 minutes ago, Daniel said: if you think that you have found an error in Delphi, please create a bug-report at quality.embarcadero.com I believe it is a bug, and in fact there is many of those bugs in calculating the constants, but i only can test few on XE8 and Seattle, so will not open a bug report. For what it worth, the following example is very common bug, it is hidden and very dangerous specially if used in conditional statement like if, because those constants has the same value there too. {$R+} const C: NativeInt = Int32(-1) shl 63; var L: NativeInt; M: NativeInt; begin L := Int32(-1) shl 63; M := Int32(-1); M := M shl 63; Writeln('C = ' + IntToHex(C, 16)); Writeln('L = ' + IntToHex(L, 16)); Writeln('M = ' + IntToHex(M, 16)); Readln; end. and the out put Quote on 32bit C = 0000000080000000 L = 0000000080000000 M = 0000000080000000 on 64bit C = FFFFFFFF80000000 L = FFFFFFFF80000000 M = 8000000000000000 also in this example if you change NativeInt to Int64, the output will be for both 32bit and 64bit Quote C = FFFFFFFF80000000 L = FFFFFFFF80000000 M = 8000000000000000 So if you Daniel or anyone see this as a bug, and of course if this result still in later compilers and can confirm it, then report it, as i remember there was very similar behaviour report years back, there was reports in Delphi 2009 and 2010, can't find those report now. the combination of those failure are many, also in example above with Int32 does compile, while replacing it with ShortInt it will refuse to compile on 32bit, and Uint32 will give different result like above (different post), Such bugs shouldn't be missed by compiler testing, as they are many with many combination. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 3 minutes ago, Kas Ob. said: I believe it is a bug... Which of the examples above you think are bugs? Share this post Link to post
Guest Posted June 7, 2020 1 minute ago, Vandrovnik said: Which of the examples above you think are bugs? I am sorry, i don't understand the question, are you asking which one i pointed to ? Share this post Link to post
David Heffernan 2345 Posted June 7, 2020 FWIW, const C = Single(0.1) does not compile in XE7 which is my day to day Delphi. And it does in 10.3 and possibly earlier versions too. Which would appear to provide a way to specify the type of floating point literal when the value is not exactly representable. I'd prefer to use a suffix rather than a type, but the precedent was set by integer types. So, no, I don't see any unsurmountable problems here. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 22 minutes ago, Kas Ob. said: I am sorry, i don't understand the question, are you asking which one i pointed to ? I did not understand, which of the values of C, L, M in your example is buggy? Share this post Link to post
Guest Posted June 7, 2020 C and L on 64bit They should be the same value with M, M is the right here. If breaking perfect logical math calculation one line of code into two lines change then value then yes, the compiler is wrong and faulty, the way L and M calculated is identical why should the result be different. All in all, even it is faulty i prefer consistency, so they should have the same value on all platform and bitness accordingly, don't you think ? I will not go with all the combination, i believe you can, if you want change those types with other simple types, some will compile and some will not, and here -1 is fool the compiler to accept it with $R+, but in fact it should not be needed to start with, the compiler refuse to compile this "C : UInt64 = 1 shl 63" while has no problem with "C = UInt64(-1)", is it right to refuse compilation with error ? will this example with NativeInt and NativeUInt , change you mind? {$R+} const C: UInt64 = UInt32(-1) shl 63; var L: UInt64; M: UInt64; begin L := UInt32(-1) shl 63; M := UInt32(-1); M := M shl 63; Writeln('C = ' + IntToHex(C, 16)); Writeln('L = ' + IntToHex(L, 16)); Writeln('M = ' + IntToHex(M, 16)); if (M = 1 shl 63) then Writeln('True') else Writeln('False'); if (M = L) then Writeln('True') else Writeln('False'); if (M = C) then Writeln('True') else Writeln('False'); Readln; end. Still wrong C = 0000000080000000 L = 0000000080000000 M = 8000000000000000 False False False You don't see it as a bug, which is completely fine, each of us has opinion, may be this is a feature and should be documented, so the document should clearly explain and document each and every situation with each type. Share this post Link to post
Leif Uneus 43 Posted June 7, 2020 1 hour ago, David Heffernan said: Which would appear to provide a way to specify the type of floating point literal when the value is not exactly representable. The type is still extended, but the value will be as a single(0.1) program Project169; {$APPTYPE CONSOLE} uses System.SysUtils, System.TypInfo; type TTypeInfo = class class function ShowTypeInfo<T>(const X: T) : String; end; class function TTypeInfo.ShowTypeInfo<T>(const X: T) : String; var LTypeInfo: PTypeInfo; begin LTypeInfo := TypeInfo(T); Result := LTypeInfo.Name; end; const F1 : Single = 0.1; F2 = Single(0.1); // <- Extended type, with the value of a single 0.1 F3 = Extended(0.1); begin WriteLn(TTypeInfo.ShowTypeInfo(F1):10,':',F1); WriteLn(TTypeInfo.ShowTypeInfo(F2):10,':',F2); WriteLn(TTypeInfo.ShowTypeInfo(F3):10,':',F3); ReadLn; end. Outputs: Single: 1.00000001490116E-0001 Extended: 1.00000001490116E-0001 Extended: 1.00000000000000E-0001 Share this post Link to post
Mahdi Safsafi 225 Posted June 7, 2020 @Kas Ob. Quote C: Int32= Int32(-1) shl 63; In most CPU, shifting outside register bits range is qualified as implementation defined. Meaning CPU vendor may: - May mask the amount with register bits size and continue execution. - Execute instruction as NOP. - May raise exception. - ... The first one is the most popular. In MSVC compiler, it choose to return 0 value However, Delphi on the other hand, choose to mask the amount with 0x1F before computing the value. So the compiler see the above expression as: C : Int32 = Int32(-1) shl (63 and $1F); So its not a bug ... it just how the compiler choose to handle it. Share this post Link to post
David Heffernan 2345 Posted June 7, 2020 23 minutes ago, Leif Uneus said: The type is still extended, but the value will be as a single(0.1) It's more nuanced. Assign it to a single variable, and it's just a 4 byte assignment. At least that's how it always has been for floating point literals. Its type is only really crystallised then you assign it is how you might think about it. Share this post Link to post
Mahdi Safsafi 225 Posted June 7, 2020 @Leif Uneus That's because literal act like find and replace macro(something similar for C define macro) for runtime expression(TypeInfo). The problem is with static expressions such SizeOf. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 (edited) 47 minutes ago, Kas Ob. said: C and L on 64bit They should be the same value with M, M is the right here. If breaking perfect logical math calculation one line of code into two lines change then value then yes, the compiler is wrong and faulty, the way L and M calculated is identical why should the result be different. All in all, even it is faulty i prefer consistency, so they should have the same value on all platform and bitness accordingly, don't you think ? I will not go with all the combination, i believe you can, if you want change those types with other simple types, some will compile and some will not, and here -1 is fool the compiler to accept it with $R+, but in fact it should not be needed to start with, the compiler refuse to compile this "C : UInt64 = 1 shl 63" while has no problem with "C = UInt64(-1)", is it right to refuse compilation with error ? will this example with NativeInt and NativeUInt , change you mind? {$R+} const C: UInt64 = UInt32(-1) shl 63; var L: UInt64; M: UInt64; begin L := UInt32(-1) shl 63; M := UInt32(-1); M := M shl 63; Writeln('C = ' + IntToHex(C, 16)); Writeln('L = ' + IntToHex(L, 16)); Writeln('M = ' + IntToHex(M, 16)); if (M = 1 shl 63) then Writeln('True') else Writeln('False'); if (M = L) then Writeln('True') else Writeln('False'); if (M = C) then Writeln('True') else Writeln('False'); Readln; end. Still wrong C = 0000000080000000 L = 0000000080000000 M = 8000000000000000 False False False You don't see it as a bug, which is completely fine, each of us has opinion, may be this is a feature and should be documented, so the document should clearly explain and document each and every situation with each type. C : UInt64 = UInt32(-1) shl 63; I suppose it is calculated in unsigned 32 bits (because you used UInt32) as $FFFFFFFF shl 31 (63 is masked by width of the left side operand), which is $80000000. This number is expanded to unsigned 64bit, so $0000000080000000 is expected result. The same for L. Sorry, I do not see a bug here. Edited June 7, 2020 by Vandrovnik Share this post Link to post
Guest Posted June 7, 2020 18 minutes ago, Mahdi Safsafi said: In most CPU, shifting outside register bits range is qualified as implementation defined. Meaning CPU vendor may: - May mask the amount with register bits size and continue execution. - Execute instruction as NOP. - May raise exception. - ... The first one is the most popular. In MSVC compiler, it choose to return 0 value However, Delphi on the other hand, choose to mask the amount with 0x1F before computing the value. So the compiler see the above expression as: C : Int32 = Int32(-1) shl (63 and $1F); Thank you for explaining, Now i want to ask for your opinion about the generated assembly from my example above, and i was explicitly trying to hint compiler that i want the consistent of type UInt64, compiler did refused to compile "C : UInt64 = 1 shl 63" but accepted 1 shl 63 and UInt64(1 shl 63) in if statement, in both cases the result was false comparison. is this consistent behaviour for a compiler ? is it abnormal and faulty? Two different questions. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 When I compile in 10.3.3., I get this (even when optimization disabled): 1 Share this post Link to post
Mahdi Safsafi 225 Posted June 7, 2020 7 minutes ago, Kas Ob. said: Thank you for explaining, Now i want to ask for your opinion about the generated assembly from my example above, and i was explicitly trying to hint compiler that i want the consistent of type UInt64, compiler did refused to compile "C : UInt64 = 1 shl 63" but accepted 1 shl 63 and UInt64(1 shl 63) in if statement, in both cases the result was false comparison. is this consistent behaviour for a compiler ? is it abnormal and faulty? Two different questions. That's because the compiler uses two different evaluator. One for static expression. And the other for optimization. C : UInt64 = 1 shl 63 this does not compile because it uses static expression evaluator. Inside if that is another story because the expression is not static (it's a dynamic expression). The compiler may not evaluate it and if does it just a kindness from him. Now whenever the compiler sees an if statement it checks if the expression inside it could be evaluated if so it evaluates it but using another component evaluator (not the static evaluator). And that's why you get two different result (the static evaluator complained, but the other just didn't care). About the generated assembly, that's correct and is just a kind of optimization : # Rather than emitting : mov rax, -1 shl rax, 63 cmp rax, qword[@M] jx @address # it just choose to optimize things: cmp qword[@M], (-1 shl 64) Did you know : const c: UInt64 = 1 shl 63; // does not compile. d: UInt64 = 1 shl (63 + 1); // compile fine. e: UInt64 = 1 shl 64; // Ok twoo. Share this post Link to post
Guest Posted June 7, 2020 @Mahdi Safsafi Thank you again for this detailed explanation. I am really sorry, and thank you for your time, but still want to know more. Now i have those lines var L: UInt64; M: UInt64; begin L := $123456789ABCDEF; M := L * 2; if M = UInt64($FFFFFFFF) shl 16 then Exit; if M = UInt64($FFFFFFFF shl 16) then Exit; if M = UInt32($FFFFFFFF) shl 16 then Exit; if M = UInt32($FFFFFFFF shl 16) then Exit; if M = UInt16($FFFFFFFF) shl 16 then Exit; if M = UInt16($FFFFFFFF shl 16) then Exit; end. And the assembly like this this (note that also Byte will act just like UInt16) How to make a rule for it then ? For me it looks the compiler is biased when it concerns UInt32, am i right in this conclusion? if not how to make such conditional compare work right for types ? Share this post Link to post
Mahdi Safsafi 225 Posted June 7, 2020 Yes you're right ! Compiler and CPU love UInt32 type ... its the CPU native word ! Even for x64 machine, CPU always prefer UInt32 type over UInt64. And gives much performance against any other type (UInt8, UInt16, UInt64, ...). For performance reason, you should always use UInt32/Int32 type even if your data is less than U/Int32. Final things: M is a UInt64 global variable stored in memory. Meaning you can view/access it as array! When comparing it against UIn32, UInt16, UInt8, the compiler can just compare its elements. However in your example, the compiler generated a non optimized code ! # In fact Its much better to do : cmp (dword|word|byte) [@M + offset], constant # rather than: mov rax, constant cmp qword [@M], rax Share this post Link to post
Lars Fosdal 1792 Posted June 8, 2020 On the other hand, very few major crises in Delphi development appear to have been caused by unclear constant types. For me, it is a bigger challenge that a typed constant is not regarded as an actual constant. Share this post Link to post
Guest Posted June 8, 2020 @Lars Fosdal @Mahdi Safsafi @Vandrovnik Thank you all, I really want to get to the bottom of this and reach a clear opinion about it, so here a simple example {$R+} type TMyVar = UInt64; TConst = UInt32; const A = TConst(-1); B = 63; var X, Y: TMyVar; begin X := A shl B; Y := A; Y := Y shl B; if X = Y then Writeln('True') else Writeln('False'); Writeln('A = ' + IntToHex(A, 16)); Writeln('B = ' + IntToHex(B, 16)); Writeln('X = ' + IntToHex(X, 16)); Writeln('Y = ' + IntToHex(Y, 16)); Readln; end. And here the result of changing TMyVar and TConst , Quote TMyVar = UInt64; // Delphi // FPC TConst = UInt64; // True for both 32bit and 64bit // True for both 32bit and 64bit TMyVar = UInt64; // Delphi // FPC TConst = UInt32; // False for both 32bit and 64bit // True for both 32bit and 64bit TMyVar = UInt32; // Delphi // FPC TConst = UInt32; // True for 32bit and refuse to compile on 64bit // refuse to compile on both 32bit and 64bit TMyVar = UInt64; // Delphi // FPC TConst = UInt16; // refuse to compile for both 32bit and 64bit // True for both 32bit and 64bit TConst = UInt8; // TConst = Byte; // Now to very simple questions 1) Which does have the right behaviour ? (Delphi or FPC) 2) Which is more consistent in logic ? (Delphi or FPC) 3) Which one is breaking language standard and logic arithmetic ? (Delphi or FPC) 4) Is this a bug in Delphi ? (yes or no) 5) Is this a bug in FPC ? (yes or no) 6) In case this is not a bug, don't you think the documentation should explain the difference between first result and second and the refusal of compilation for the last ? (at least for shl) Share this post Link to post
David Heffernan 2345 Posted June 8, 2020 I have a question for you. Which integer value are you unable to declare as a literal? Share this post Link to post
Lars Fosdal 1792 Posted June 8, 2020 For me as purely a Delphi user, the comparison to FPC is irrelevant. Share this post Link to post
Guest Posted June 8, 2020 You both didn't answer my simple short questions, you are steering this away, and lets stay to single subject point, does the Delphi compiler have consistent logical behavior. 36 minutes ago, David Heffernan said: Which integer value are you unable to declare as a literal? You mean i should not code like the example, then this should be documented. great this is your opinion, and there is nothing needs to be adjusted in the compiler. 30 minutes ago, Lars Fosdal said: For me as purely a Delphi user, the comparison to FPC is irrelevant. Your opinion FPC is irrelevant, great, but does this mean Delphi is right ? or Wrong ? Again please, i tried in many post to refrain from adding FPC here, i tried with simple examples and no one is answering directly, David and Vandrovnik asked question instead answering or provide an opinion, and yes, i had a claim that Delphi is doing things wrong and inconsistent, and provided a proof, so the questions are so simple, please just answer or don't steer this to irrelevant discussion ( like bringing FPC here or the ability to do things should be allowed, not documented if OK or NOT) Share this post Link to post
Guest Posted June 8, 2020 @David Heffernan @Lars Fosdal Shouldn't the compiler warns in this when compile fine on 32bit ? Quote TMyVar = UInt32; // Delphi // FPC TConst = UInt32; // True for 32bit and refuse to compile on 64bit // refuse to compile on both 32bit and 64bit Share this post Link to post
Vandrovnik 214 Posted June 8, 2020 27 minutes ago, Kas Ob. said: .. David and Vandrovnik asked question instead answering or provide an opinion,... I have asked, what you think is a bug. You have answered and I wrote you, how compiler probably decides the value and that I do not see a bug there. When you want to decide, whether Delphi is wrong with something, first you need a specifiaction saying, what is right. Then you can compare actual result with it. And do testing with current version of Delphi, because even if you find an error in old version, it will probably never be fixed. Share this post Link to post
David Heffernan 2345 Posted June 8, 2020 1 hour ago, Kas Ob. said: You both didn't answer my simple short questions, you are steering this away, and lets stay to single subject point, does the Delphi compiler have consistent logical behavior. Let's start by establishing whether or not there is a problem that cannot be worked around. Which integer value are you unable to declare as a literal? Share this post Link to post
Guest Posted June 8, 2020 Thank you, that is answer from you, not for all of them, but will do half. 31 minutes ago, Vandrovnik said: When you want to decide, whether Delphi is wrong with something, first you need a specifiaction saying, what is right. Great, the documentation is not complete, (something like don't cast types for constant in conditional expression in such case....) 31 minutes ago, Vandrovnik said: And do testing with current version of Delphi, because even if you find an error in old version, it will probably never be fixed. Agree, a fix for older versions is not be expected, but it should be considered to correct this behaviour in future versions, at least discussed, like now and here. Now from your post above with this jmp in the assembly, shouldn't be considered a bug or inefficiency and reported, that is bizarre. Share this post Link to post