Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StrToFloat fails to report invalid floating point numbers in Delphi 64bits

The following code which attempts to convert a value well beyond the double precision range

StrToFloat('1e99999999')

correctly reports an incorrect floating point value in Delphi 10.2r3 with the Windows 32 bits compiler, but when compiled with the Window 64 bits compiler, it silently returns a 0 (zero).

Is there a way to have StrToFloat report an error when the floating point value is incorrect?

I have tried TArithmeticException.exOverflow, but this has no effect in that case.

I also tried TArithmeticException.exPrecision but it triggers in many usual approximation cases (f.i. it triggers when converting '1e9').

Issue was noticed with Delphi 10.2 update 3

addendum: to workaround the issue, I have started a clean-room alternative implementation of string to double conversion, initial version with tests can be found in dwscript commit 2ba1d4a

like image 254
Eric Grange Avatar asked Apr 09 '18 15:04

Eric Grange


1 Answers

This is a defect that is present in all versions of Delphi that use the PUREPASCAL version of StrToFloat. That maps through to InternalTextToExtended which reads the exponent like this:

function ReadExponent: SmallInt;
var
  LSign: SmallInt;
begin
  LSign := ReadSign();
  Result := 0;
  while LCurrChar.IsDigit do
  begin
    Result := Result * 10;
    Result := Result + Ord(LCurrChar) - Ord('0');
    NextChar();
  end;

  if Result > CMaxExponent then
    Result := CMaxExponent;

  Result := Result * LSign;
end;

The problem is the location of

if Result > CMaxExponent then

This test is meant to be inside the loop, and in the asm x86 version of this code it is. As coded above, with the max exponent test outside the loop, the 16 bit signed integer result value is too small for your exponent of 99999999. As the exponent is read, the value in Result overflows, and becomes negative. So for your example it turns out that an exponent of -7937 is used rather than 99999999. Naturally this leads to a value of zero.

This is a clear bug and I have submitted a bug report: RSP-20333.

As for how to get around the problem, I'm not aware of another function in the Delphi RTL that performs this task. So I think you will need to do one of the following:

  • Roll your own StrToFloat.
  • Pre-process the string, and handle out of range exponents before they read StrToFloat.
  • Use one of the functions from the C runtime library that performs the same task.

Finally, I am grateful for you asking this question because I can see that my own program is affected by this defect and so I can now fix it!

Update:

You may also be interested to look at a related bug that I found when investigating: RSP-20334. It might surprise you to realise that, StrToFloat('߀'), when using the PUREPASCAL version of StrToFloat, returns 1936.0. The trick is that the character that is being passed to StrToFloat is a non-Latin digit, in this case U+07C0.

like image 135
David Heffernan Avatar answered Oct 22 '22 14:10

David Heffernan