Mahdi Safsafi 225 Posted June 6, 2020 (edited) Hello, I found a very strange behavior about how Delphi handle literals. Here is a quick example (Delphi 10.3) showing what I found : // For each error, read the comment ... comment the line and next. const F1 = 0.2; F2 = Single(0.2); // this is not a single type ... see below ! I1: Integer = F1; // E2010 Incompatible types: Integer and Comp. => F1 = Comp ! I2: Integer = F2; // E2010 Incompatible types: Integer and Extended => F2 = Extended even when specifying it as Single ! ConstInt1 = 1; // primary expression ConstInt2 = +1; // unary expression SizeOfConstInt1 = sizeof(ConstInt1); // size = 1 ! SizeOfConstInt2 = sizeof(ConstInt2); // size = 4 ! FArray = [1, 2, 3] + [4, 5, 6]; // untyped array. function GetFArrayPointerUsingAsm(): Pointer; asm lea eax, FArray[0] // return invalid pointer ! end; procedure ProcessFArray(const X); begin // compiler pushed unkown STACK reference ! end; var len: Integer; size: Integer; P: PInteger; value: Integer = 3; begin len := length([1, 2, 3] + [4, 5, 6]); // len = 6 len := length(FArray); // Error E2008 Incompatible type ! size := sizeof([1, 2, 3] + [4, 5, 6]); // error does not compile. size := sizeof(FArray); // wrong size = 1 ! size := sizeof([1, 2, 3]); // wrong size = 1 ! P := @FArray[0]; // Error E2016 Array type required. P := GetFArrayPointerUsingAsm(); // function returns invalid pointer ! ProcessFArray(FArray); // compiler passed unkown/invalid stack reference ! if (value in FArray) then // Ok. begin Writeln('FArray contains value'); // passed. end; Readln; end. I'm not even going to mention typed constants or constants that may change ! I just believe that it's really the time to consider checking the compiler's const type system. Edited June 6, 2020 by Mahdi Safsafi Share this post Link to post
Guest Posted June 7, 2020 (edited) I want to add these too const A = 1 shl 63; // Wrong value A = -2147483648 B: Int64 = 1 shl 63; // Wrong value B = -2147483648 C: UInt64 = 1 shl 63; // error "E1012 Constant expression violates subrange bounds" D: NativeUInt = 1 shl 63; // error "E1012 Constant expression violates subrange bounds" Edit : Tested on Seattle and XE8. To get them work you need to make a hint to the compiler about the the constant number itself like this A = Int64(1) shl 63; // Right here A = -9223372036854775808 B: UInt64 = Int64(1) shl 63; // Still error "E1012 Constant expression violates subrange bounds" C: Int64 = UInt64(1) shl 63; // Right here C = -9223372036854775808 D: NativeUInt = UInt64(1) shl 63; // Wrong value D = 0 on 32bit and Right on 64bit E: NativeUInt = NativeUInt(1) shl 63; // Right F: NativeInt = NativeUInt(1) shl 63; // Right G: NativeUInt = NativeInt(1) shl 63; // Still error "E1012 Constant expression violates subrange bounds" Edited June 7, 2020 by Guest Share this post Link to post
Lars Fosdal 1792 Posted June 7, 2020 @Kas Ob. Ref second tests: B and G reporting errors is correct. Int64(1) or NativeInt(1) shl 63 are negative numbers, which you cannot assign to an unsigned int. As for D, NativeUInt = 32-bit. Shifting a 1 63 places to the left in a 32-bit variable, should yield 0. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 In help, they write: "If constantExpression is a real, its type is Extended." I believe F1 and F2 are both extended. const F1 = 0.2; F2 = Single(0.2); writeln(F1); writeln(F2); 2.00000000000000E-0001 2.00000002980232E-0001 If F1 was comp, it could save only integer values. Share this post Link to post
Guest Posted June 7, 2020 48 minutes ago, Lars Fosdal said: Ref second tests: B and G reporting errors is correct. Int64(1) or NativeInt(1) shl 63 are negative numbers, which you cannot assign to an unsigned int. As for D, NativeUInt = 32-bit. Shifting a 1 63 places to the left in a 32-bit variable, should yield 0. Yet this compile fine. const X1: Integer = -5 shr 63; X2: UInt32 = -5 shr 63; X3: NativeUInt = -5 shr 63; Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 Also note this in help: The operations x shl y and x shr y shift the value of x to the left or right by y bits, which (if x is an unsigned integer) is equivalent to multiplying or dividing x by 2^y; the result is of the same type as x. For example, if N stores the value 01101 (decimal 13), then N shl 1 returns 11010 (decimal 26). Note that the value of y is interpreted modulo the size of the type of x. Thus for example, if x is an integer, x shl 40 is interpreted as x shl 8 because an integer is 32 bits and 40 mod 32 is 8. So we have: const P: UInt32 = UInt32(1) shl 63; // contains 2147483648 Share this post Link to post
Guest Posted June 7, 2020 54 minutes ago, Lars Fosdal said: As for D, NativeUInt = 32-bit. Shifting a 1 63 places to the left in a 32-bit variable, should yield 0. It should fail to compile with the error above instead providing wrong value. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 55 minutes ago, Lars Fosdal said: @Kas Ob. As for D, NativeUInt = 32-bit. Shifting a 1 63 places to the left in a 32-bit variable, should yield 0. Delphi's "Shifting a 1 63 places to the left in a 32-bit variable" is the same as "Shifting a 1 31 places to the left in a 32-bit variable", so you will get $80000000. In the example above, shifting is done in 64bit variable and the result $8000000000000000 is assigned to 32bit typed constant - lower 32bits are used (0) and a warning is generated. 1 Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 4 minutes ago, Kas Ob. said: It should fail to compile with the error above instead providing wrong value. It is not consistent: Y: UInt32 = $123456789; // just a warning Z: Byte = 1000; // an error Share this post Link to post
David Heffernan 2345 Posted June 7, 2020 The inability to control the type of a floating point literal is the only real issue here. Everything else mentioned above can be readily worked around. But the fact that you can't control the type of a floating point literal presents problems that have no workaround. I want to write const s = 0.1s; d = 0.1d; And have two literals with different values. Share this post Link to post
Guest Posted June 7, 2020 Just now, Vandrovnik said: Delphi's "Shifting a 1 63 places to the left in a 32-bit variable" is the same as "Shifting a 1 31 places to the left in a 32-bit variable", so you will get $80000000. In the example above, shifting is done in 64bit variable and the result $8000000000000000 is assigned to 32bit typed constant - lower 32bits are used (0) and a warning is generated. In my opinion, the modulo calculation is right, but it should be runtime code, let it emulate the default behaviour of the CPU itself (shr eax,77 is fine), on other hand i think it should at least warn when used in constant. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 7 minutes ago, Kas Ob. said: It should fail to compile with the error above instead providing wrong value. If we use {$R+}, both generate an error. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 2 minutes ago, David Heffernan said: The inability to control the type of a floating point literal is the only real issue here. Everything else mentioned above can be readily worked around. But the fact that you can't control the type of a floating point literal presents problems that have no workaround. I want to write const s = 0.1s; d = 0.1d; And have two literals with different values. You have: const F1 = 0.2; F2 = Single(0.2); writeln(F1); writeln(F2); 2.00000000000000E-0001 2.00000002980232E-0001 Share this post Link to post
Leif Uneus 43 Posted June 7, 2020 @Vandrovnik, Still both F1 and F2 are of type extended. Share this post Link to post
Vandrovnik 214 Posted June 7, 2020 7 minutes ago, Leif Uneus said: @Vandrovnik, Still both F1 and F2 are of type extended. Yes, they are. But David said "And have two literals with different values." Share this post Link to post
Lars Fosdal 1792 Posted June 7, 2020 3 hours ago, Kas Ob. said: Yet this compile fine. const X1: Integer = -5 shr 63; X2: UInt32 = -5 shr 63; X3: NativeUInt = -5 shr 63; -5 shr 63 will always be 1. Share this post Link to post
Mahdi Safsafi 225 Posted June 7, 2020 @David Heffernan Quote The inability to control the type of a floating point literal is the only real issue here. No that's not the issue !! In fact the compiler by its design is free to assume literal type AND force it (prevent you from modifying it by emitting error when casting it to another type) Most scripting languages do that (they assume it is double and they do not let you control it). However the real issue here is that sizeof(1) != sizeof(+1) !!! Why ? this is because there is a dissociative system between literal and expression type. Delphi choose the smallest type to represent literal. However it choose the Word/Native CPU available type for expression. This is wrong ! because literal is an expression too (terminal) ! In LLVM for example constants are all typed and the strong-typing system there prevent you from operating on two different types. const ConstA = 1; ConstB = +1; ConstC = 0.2; ConstD = Single(0.2); // I don't mind but I will do something behind your back ! begin Writeln(IntToStr(SizeOf(ConstA))); // size = 1 Writeln(IntToStr(SizeOf(ConstB))); // size = 4 Writeln(IntToStr(SizeOf(ConstC))); // size = 8 Writeln(IntToStr(SizeOf(ConstD))); // size = 10 Readln; end. // correct behavior is: // ---------------------- const ConstA = 1; // sizeof(1) = sizeof(OrdinalExpression). ConstB = +1; // sizeof(+1) = sizeof(OrdinalExpression). ConstD = Single(0.2); // error static expression must be of type Extend. ConstD2 = Single(0.2); // type of ConstD2 = Single. For the last thing: I'd prefer : const d = 0.1; s = 0.1f; Share this post Link to post
David Heffernan 2345 Posted June 7, 2020 9 minutes ago, Mahdi Safsafi said: No that's not the issue !! In fact the compiler by its design is free to assume literal type AND force it (prevent you from modifying it by emitting error when casting it to another type) As far as I am concerned the only issue raised in this thread that can't be worked around is the type of floating point literals. Integer literals can be cast. But not floating point literals. Having said that, my recollection is that `Single(0.1)` would not compile so I wonder if there has been a change since XE7, the version I am most familiar with. Share this post Link to post
Guest Posted June 7, 2020 31 minutes ago, Lars Fosdal said: -5 shr 63 will always be 1. I was pointing to odd behaviour and inconsistency. program Project5; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, windows; {$R+} const T1: NativeUInt = UInt32(-1) shl 63; var T: NativeUInt; begin T := UInt32(-1); T := T shl 63; Writeln(IntToHex(T1, 16)); Writeln(IntToHex(T, 16)); Readln; end. The result on 32bit Quote 0000000080000000 0000000080000000 and on 64bit Quote 0000000080000000 8000000000000000 Share this post Link to post
Guest Posted June 7, 2020 While with program Project5; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, windows; {$R+} const T1: UInt64 = UInt32(-1) shl 63; var T: UInt64; begin T := UInt32(-1); T := T shl 63; Writeln(IntToHex(T1, 16)); Writeln(IntToHex(T, 16)); Readln; end. The result Quote on 32bit 0000000080000000 8000000000000000 on 64bit 0000000080000000 8000000000000000 The result here is consistent but wrong on 32bit. Share this post Link to post
Mahdi Safsafi 225 Posted June 7, 2020 12 minutes ago, David Heffernan said: As far as I am concerned the only issue raised in this thread that can't be worked around is the type of floating point literals. Integer literals can be cast. But not floating point literals. @David Heffernan I believe that you are not seeing the full picture here : The real issue is the dissociative type between literal and expression (not like what you understand "the ability to control FP constants") . BTW you don't work around an issue ... you need to fix it (fixing the type system will let you control FP type) !! This is a critical issue ! For now it just looks less critical but just think if at some time Delphi introduces type inquiring feature ! what will happen ? I was working on a Pascal-like compiler and I used a weak-typed literal system for constants, meaning the const was stored using the longest available type and casted on demand. I got a very surprising result when inquiring types/expressions/variables. This was my motivation to inspect Delphi constants. So I'm very sure that this aspect is wrong and will at some point produce unpredictable behaviors. I hope -by now- that you are seeing the full picture and everything is clear for you. 1 Share this post Link to post
David Heffernan 2345 Posted June 7, 2020 (edited) 5 hours ago, Mahdi Safsafi said: @David Heffernan I believe that you are not seeing the full picture here : The real issue is the dissociative type between literal and expression (not like what you understand "the ability to control FP constants") . BTW you don't work around an issue ... you need to fix it (fixing the type system will let you control FP type) !! This is a critical issue ! For now it just looks less critical but just think if at some time Delphi introduces type inquiring feature ! what will happen ? I was working on a Pascal-like compiler and I used a weak-typed literal system for constants, meaning the const was stored using the longest available type and casted on demand. I got a very surprising result when inquiring types/expressions/variables. This was my motivation to inspect Delphi constants. So I'm very sure that this aspect is wrong and will at some point produce unpredictable behaviors. I hope -by now- that you are seeing the full picture and everything is clear for you. I don't think the issue is that I don't see the full picture and have a lack of clarity. Edited June 7, 2020 by David Heffernan 1 Share this post Link to post
Mahdi Safsafi 225 Posted June 7, 2020 39 minutes ago, David Heffernan said: I don't think the issue is that I don't see the full picture and have aack of clarity. Saying that "The inability to control the type of a floating point literal is the only real issue here." pointed me to believe that you're not seeing the full picture ! Share this post Link to post
Daniel 417 Posted June 7, 2020 @Mahdi Safsafi there is no need to convince someone here. Sometimes we just have different views on an item. This is not wrong by itself. if you think that you have found an error in Delphi, please create a bug-report at quality.embarcadero.com 1 Share this post Link to post
Mahdi Safsafi 225 Posted June 7, 2020 20 minutes ago, Daniel said: @Mahdi Safsafi there is no need to convince someone here. Sometimes we just have different views on an item. This is not wrong by itself. if you think that you have found an error in Delphi, please create a bug-report at quality.embarcadero.com I'm not trying to convince anyone ! Everyone is free to believe in whatever think is right. I just tried to give a clean explanation (from my point of view of course). Share this post Link to post