5
votes

Both, Borland Pascal 7 and Delphi 2007 have got the procedure STR which takes a number, a length and precision and converts it to a string like this:

str(9.234:5:1, s); // -> s = '  9.2'

All is fine if the rounding is non-ambiguous, but if it isn't (0.5 -> up or down?) there is a problem: It seems to depend on the floating point data type in BP but is apparently consistent in Delphi 2007:

BP:

var
  e: extended;
  d: double;
begin
  d := 2.15;
  e := 2.15;
  str(d:5:1, s); { -> s = '  2.1' }
  str(e:5:1, s); { -> s = '  2.2' }
  { but: }
  d := 2.25
  e := 2.25
  str(d:5:1, s); { -> s = '  2.3' }
  str(e:5:1, s); { -> s = '  2.3' }

I was unable to find any rule on how doubles are being rounded, while apparently extendeds are always rounded up.

Delphi 2007 apparently always rounds up independent of the data type.

Does anybody know how the rounding is done in BP for double values?

I want to know because I am in the middle of porting some Borland Pascal code that uses doubles to Delphi 2007 and when I compare the outputs I get inconsistencies that result from rounding in the STR procedure. These do not really matter for the result but it makes it very difficult to spot important differences.

5
Why try to figure out how it works when it's glitchy when you could change all Str() calls to FloatToStr calls? - Warren P
He can't change the BP code. Only changing the Delphi code to FloatToStr thus buys him nothing. - Lieven Keersmaekers
Note that the double is probably converted to an extended before it is passed to Str(). This means that the extended 2.15 and the double 2.15 after teh conversion to extended can have different values and therefore might be rounded differently. Otherwise, the accepted answer is right: 2.25 can be represented exactly (.25 is 2^-2 exactly) both by extended and by double, while 2.15 can't. - Rudy Velthuis
@dummzeuch, have you found a solution for this? Looks like I have the same problem. - pyfyc
@pyfyc no, I never found a solution. I adjusted my tests to deal with the differences in rounding. - dummzeuch

5 Answers

4
votes

The cases of d = 2.15 and d = 2.25 are different:

2.15 cannot be represented exactly in float format, and so it is impossible to say how the value is rounded without analyzing the binary representation of the float value in a given float format;

2.25 is exactly represented in float format, and the rounding result must be predictable;

I have tested rounding on some values which are exactly represented in float format and found that STR always rounds up for positive values and down for negative values. STR does not follow "banker's rounding", eg:

  d := 2.25;
//  d:= roundto(d, -1);  banker's rounding is 2.2
  str(d:5:1, s); { -> s = '  2.3' }

  d:= 2.75;
//  d:= roundto(d, -1);  banker's rounding is 2.8
  str(d:5:1, s); { -> s = '  2.8' }
2
votes

I think the problem you are seeing is that many numbers that can be represented exactly in decimal notation can only be represented as repeating decimals (binimals?) in binary. So, it may be that 2.15 cannot be exactly represented by a double and that 2.14999999999234 (or something) is the closest you can get with a binary representation.

Since the closest binary representation of the number is strictly less than 2.15, the Str function rounds down instead of up.

1
votes

Looks like a floating point rounding error. When you look at the assembly code generated in Delphi, you can see that _Str2Ext is called for both operations, which converts an Extended into a string. So in order to do that it has to convert your Double into an Extended behind the scenes:

Project1.dpr.16: str(d:5:1, s); { -> s = '  2.1' }
0040E666 DD45E8           fld qword ptr [ebp-$18]
0040E669 83C4F4           add esp,-$0c
0040E66C DB3C24           fstp tbyte ptr [esp]
0040E66F 9B               wait 

And somewhere in the conversion from Double to Extended, you're losing a little bit of precision, and ending up with a slightly different number than if you'd declared the same number (as we read them) as an Extended to begin with. This is pretty common in floating-point conversions. Not sure if there's anything you can do about it.

0
votes

I investigated this, and discovered that adding 0.000001 will produce the correct result for doubles.

0
votes

Note that there are two aspects here.

First, your decimal literal value is possibly rounded to a binary floating point number. This means that the number put in the assembly code can vary very slightly from the number you wrote down. If the nearest machinenumber is slightly less, it can appear as if values that should be rounded up, are rounded down by STR.

Second, the resulting binary floating point number is rounded, using the rounding configured in the FPU statusword, which hopefully hasn't been changed by external libraries.