Jump to content
Mahdi Safsafi

Strange behavior for literals

Recommended Posts

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

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

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

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

@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
Posted (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 by Vandrovnik

Share this post


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

 

Const.thumb.png.0cad397c44b7e4af9634de8528485172.png

 

is this consistent behaviour for a compiler ?

is it abnormal and faulty?

 

Two different questions.

Share this post


Link to post

When I compile in 10.3.3., I get this (even when optimization disabled):

 

test.thumb.png.8cc8923cbfb0540401686dc5743813e5.png

 

Share this post


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

 

Const.thumb.png.0cad397c44b7e4af9634de8528485172.png

 

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

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

 Const3.thumb.png.47fd1ba9ec2a13c14266bff609d8fb50.png

 

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

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

 

  • Thanks 1

Share this post


Link to post

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

@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

For me as purely a Delphi user, the comparison to FPC is irrelevant.

 

Share this post


Link to post

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

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

  • Thanks 1

Share this post


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

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

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

×