Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the Delphi / Borland Pascal STR procedure round

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.

like image 472
dummzeuch Avatar asked Feb 25 '10 15:02

dummzeuch


3 Answers

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' }
like image 196
kludg Avatar answered Oct 23 '22 19:10

kludg


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.

like image 2
Jeff Wilhite Avatar answered Oct 23 '22 21:10

Jeff Wilhite


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.

like image 1
Mason Wheeler Avatar answered Oct 23 '22 20:10

Mason Wheeler