Jump to content
Mahdi Safsafi

Strange behavior for literals

Recommended Posts

Posted (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 by Mahdi Safsafi
  • Like 1

Share this post


Link to post
Posted (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 by Kas Ob.

Share this post


Link to post

@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

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
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

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
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
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.

  • Like 2

Share this post


Link to post
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

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. 

  • Like 1

Share this post


Link to post
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
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
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
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
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

@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
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
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

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
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.

 

  • Confused 1

Share this post


Link to post
Posted (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 by David Heffernan
  • Like 1

Share this post


Link to post
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

@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

  • Like 1

Share this post


Link to post
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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×