Search the Community
Showing results for tags 'floats'.
Found 1 result
-
bug Delphi (Win32) quiets signaling NaN on function return
Jim McKeeth posted a topic in RTL and Delphi Object Pascal
I found myself down the rabbit hole of IEEE 754 standard on Floating-Point numbers (which Delphi follows), specifically the different values of NaN.... There are multiple potential internal values for NaN, all which evaluate as fsNaN. I found what I believe to be a bug, but I'm curious if anyone else has any thoughts on it. (As I was writing this up I discovered the behavior is only on Win32, so I've decided it must be a bug (RSS-1831), but sharing here anyway because I find the details interesting.) In short: IEEE 754 distinguishes between Signaling NaN and Quiet NaN. Delphi defaults to Quiet NaN, but you can make it signaling manually. Unfortunately, when a float is returned from a function in the Win32 compiler, the quiet flag is set. I was testing to see if Delphi converted it into an exception, which would be understandable, but instead it just silently sets the quiet flag, suppressing the signaling state. Testing in FPC, Delphi Win64, or Delphi Linux64, and the flag value doesn't change, which is what I would expect. Detailed explanation and background IEEE 754 divides a float into 3 parts Sign Exponent Fraction For a NaN the Exponent bits are all set ($FF in Single), any value for sign (Delphi's default NaN has it set, making it a negative NaN, which doesn't carry any specific significance), and the Fraction is anything but all 0. This allows for quiet a few different values that all are treated as NaN. What may distinguish the different values of NaN is signaling vs. quiet NaN. The Quiet flag is usually the first bit of the fraction. When the quiet flag isn't set, then it is considered a signaling NaN. The internal the internal representation of a NaN is typically displayed as follows (notice this is reversed from how Delphi stores the value in memory to put the most significant bit first.) S Exponent Fraction 1 | 11111111 | 10000000000000000000000 ^ The quiet flag So a signaling NaN is has an value for Faction without that first bit set. What I found in Delphi Win32 is it handles all these values correctly, except that if the quiet flag is missing (making it a signaling NaN), then when the value is returned from a function the quiet flag is set. Before returning from function: (Notice that the debugger recognizes it as negative NaN. Very nice!) This is 1 | 11111111 | 00000000000000000000001 in binary, which doesn't have the Quiet flag set 1 | 11111111 | 10000000000000000000001 After return it isn't the Default NaN, but it is the previous value with the Quiet flag set. Here is some sample code that demonstrates the behavior // The Delphi Win32 (tested in Delphi 12.1 and 12.2) sets NaN's quiet flag when are returning from a function // More information on IEEE 754 NaN https://en.wikipedia.org/wiki/NaN#Quiet_NaN // More information on this code: https://gist.github.com/jimmckeeth/2b4f017917afbae88ee7a3deb75b4ef7 program NaNSignalBug; {$APPTYPE CONSOLE} uses System.SysUtils;//, SingleUtils in 'SingleUtils.pas'; function is_Signaling(const NaN: Single): Boolean; begin Result := NaN.IsNan and (NaN.Frac and $400000 = 0); end; function NaNDetails(const Value: Single): string; begin if value.IsNan then begin if is_Signaling(value) then Result := 'NaN is Signaling' else Result := 'NaN is Quiet'; end else Result := 'Not a NaN'; end; procedure MakeSignaling(var NaN: Single); begin NaN.Exp := 255; NaN.Frac := NaN.Frac and not (1 shl 22); Assert(is_Signaling(NaN)); Writeln('Manipulated: ',NaNDetails(NaN),#9, '// Line 33'); end; // When a NaN is returned from a function the Signal bit is set function SignalingNaN: Single; begin Result := Single.NaN; Result.Frac := 1; MakeSignaling(Result); Assert(is_Signaling(Result)); Writeln(#9,'SignalingNaN Returning',#9'// Line 43'); end; function NestedNaN: Single; begin var NaN : Single := SignalingNaN; // The quiet bit was set Writeln('Returned: ',NaNDetails(NaN),#9, '// Line 50'); // without returning it from a function it works fine MakeSignaling(NaN); Writeln('Manipulated: ',NaNDetails(NaN),#9, '// Line 53'); Assert(is_Signaling(NaN)); Writeln(#9,'NestedNaN Returning ',#9,'// Line 55'); Exit(NaN); end; begin try Writeln(TOSVersion.ToString); Writeln('Used: ',SizeOf(NativeInt)*8,'-bit compiler'); var NaN : Single := NestedNaN; // The quiet bit was set Writeln('Returned: ', NaNDetails(NaN),#9,'// Line 66'); //Assert(is_Signaling(NaN)); // Fails on Win32 // without returning it from a function it works fine MakeSignaling(NaN); Writeln('Manipulated: ', NaNDetails(NaN),#9,'// Line 70'); Assert(is_Signaling(NaN)); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; readln; end. and here is the output Windows 11 (Version 23H2, OS Build 22631.4169, 64-bit Edition) Used: 32-bit compiler Manipulated: NaN is Signaling // Line 33 SignalingNaN Returning // Line 43 Returned: NaN is Quiet // Line 50 Manipulated: NaN is Signaling // Line 33 Manipulated: NaN is Signaling // Line 53 NestedNaN Returning // Line 55 Returned: NaN is Quiet // Line 66 Manipulated: NaN is Signaling // Line 33 Manipulated: NaN is Signaling // Line 70 and a more detailed look at the NaN values Default NaN Value of : Nan Special : fsNaN Sign : TRUE Exponent : 255 Fraction : 4194304 Size : 4 InvertHex: $FFC00000 1 | 11111111 | 10000000000000000000000 ----------------- Singnaling NaN Value of : Nan Special : fsNaN Sign : TRUE Exponent : 255 Fraction : 1 Size : 4 InvertHex: $FF800001 1 | 11111111 | 00000000000000000000001 ----------------- Returned from Function Value of : Nan Special : fsNaN Sign : TRUE Exponent : 255 Fraction : 4194305 Size : 4 InvertHex: $FFC00001 1 | 11111111 | 10000000000000000000001 @David Heffernan it was suggested I tag you on this... NaNSignalBug.dpr