views:

430

answers:

5

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.

+2  A: 

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.

Jeff Wilhite
I think you are right. But, how can I find out which is the case for a given double value? I'll experiment a bit...
dummzeuch
+1 I think you're right. Use the Source Luke.
Lieven
@dummzeuch - if you want exact numbers, you shouldn't be using doubles but scaled integers instead. Take a look at http://rvelthuis.de/articles/articles-floats.html for a very good explanation on the subject.
Lieven
@Lieven: I don't want exact numbers, the precision is fine for the actual calculation. I want to produce the the exact numbers (as strings) that were generated in Borland Pascal so I can easily verify (by comparing text output) that my Delphi program does the same as the Borland Pascal program. Basically, I'd love if someone gave me a Delphi implementation of STR that does exactly the same as STR in Borland Pascal so I could temporarily use it to verify my program and afterwards discard it.
dummzeuch
@dummzeuch - in that case, can't you just multiply the numbers by 100 in Delphi, use STR and shift the comma two places in the resulting string?
Lieven
@Lieven: Sounds like an idea, but I am too tired now to think about it. I'll give it a try tomorrow.
dummzeuch
A: 

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

MarkRobinson
I cannot change the BP source code, I only got it for reference purposes. The test data I have got has already been processed using the compiled BP program. I want to know how it works so I can (temporarily) reproduce this behavior in Delphi.
dummzeuch
+1  A: 

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.

Mason Wheeler
A: 

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.

Marco van de Voort
+3  A: 

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' }
Serg
Good observation! Combine this with my answer and you've got a pretty good idea of what's going on.
Mason Wheeler