Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2007-05

FormatFloat bug

18 messages · 6 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Graeme Geldenhuys

Hi,

Attached is a application that demonstrates a bug in FormatFloat. All tests pass when run under Delphi, but test four fails under Free Pascal.

Here is the application output:

-------------------------------------------
$ ./project1
Time:00.001 N:7 E:0 F:1 I:0
TTestFormatFloat Time:00.001 N:7 E:0 F:1 I:0
00.000 Test1
00.000 Test2
00.000 Test3
00.000 Test4 Failed: Failed on 4 expected: <$ 0.01> but was: <$ 0.00> 00.000 Test5
00.000 Test6
00.000 Test7

Number of run tests: 7
Number of errors: 0
Number of failures: 1

List of failures:
Failure:
Message: TTestFormatFloat.Test4: Failed on 4 expected: <$ 0.01> but was: <$ 0.00>
Exception class: EAssertionFailedError
Exception message: Failed on 4 expected: <$ 0.01> but was: <$ 0.00>

-------------------------------------------

Graeme Geldenhuys General error, hit any user to continue.

Reply by email

Joao Morais
to Graeme Geldenhuys
Graeme Geldenhuys wrote 5 lines
Hi, Attached is a application that demonstrates a bug in FormatFloat. All tests pass when run under Delphi, but test four fails under Free Pascal.

IMHO, fpc behaves correctly. 0.005 is closer than 0 instead 0.01. If you try 0.00500000001, you will have 0.01, also correct.

Joao Morais > Here is the application output: > > ------------------------------------------- > $ ./project1 > Time:00.001 N:7 E:0 F:1 I:0 > TTestFormatFloat Time:00.001 N:7 E:0 F:1 I:0 > 00.000 Test1 > 00.000 Test2 > 00.000 Test3 > 00.000 Test4 Failed: Failed on 4 expected: <$ 0.01> but was: <$ 0.00> > 00.000 Test5 > 00.000 Test6 > 00.000 Test7 > > Number of run tests: 7 > Number of errors: 0 > Number of failures: 1 > > List of failures: > Failure: > Message: TTestFormatFloat.Test4: Failed on 4 expected: > <$ 0.01> but was: <$ 0.00> > Exception class: EAssertionFailedError > Exception message: Failed on 4 expected: <$ 0.01> but was: <$ 0.00> > > ------------------------------------------- > > > ------------------------------------------------------------------------ > > _______________________________________________ > fpc-devel maillist - > http://lists.freepascal.org/mailman/listinfo/fpc-devel

Reply by email

Graeme Geldenhuys
to Joao Morais
Joao Morais wrote 11 lines
Graeme Geldenhuys wrote: > Hi, > > Attached is a application that demonstrates a bug in FormatFloat. All > tests pass when run under Delphi, but test four fails under Free > Pascal. IMHO, fpc behaves correctly. 0.005 is closer than 0 instead 0.01. If you try 0.00500000001, you will have 0.01, also correct.

What rounding does FormatFloat() use so I can see if it is mathematically correct and how compatible do we want to be with Delphi?

Graeme.

Reply by email

Graeme Geldenhuys
to Joao Morais: IMHO, fpc behaves correctly. 0.005 is closer than 0 instead 0.01. If you try…
Joao Morais wrote 8 lines
> Hi, > > Attached is a application that demonstrates a bug in FormatFloat. All > tests pass when run under Delphi, but test four fails under Free > Pascal. IMHO, fpc behaves correctly. 0.005 is closer than 0 instead 0.01. If you try 0.00500000001, you will have 0.01, also correct.

Normally you would round 5 or higher up so taking 0.005 to two decimal places would give you 0.01 which is what Delphi does! This is mathematically correct. It's got nothing to do with whether it is closer to zero or to one.

Graeme.

Reply by email

Michael Van Canneyt
to Graeme Geldenhuys: What rounding does FormatFloat() use so I can see if it is mathematically correct and how…
Graeme Geldenhuys wrote 17 lines
On 5/29/07, Joao Morais wrote: > Graeme Geldenhuys wrote: > > > Hi, > > > > Attached is a application that demonstrates a bug in FormatFloat. All > > tests pass when run under Delphi, but test four fails under Free > > Pascal. > > IMHO, fpc behaves correctly. 0.005 is closer than 0 instead 0.01. If you > try 0.00500000001, you will have 0.01, also correct. > What rounding does FormatFloat() use so I can see if it is mathematically correct and how compatible do we want to be with Delphi?

If I remember correctly:
Bookkeepers rounding as implemented by the Intel processors. No own routines are used. It's left up to the processor.

Michael.

Reply by email

Burkhard Carstens
to Graeme Geldenhuys: Normally you would round 5 or higher up so taking 0.005 to two decimal places would give…
Graeme Geldenhuys wrote 14 lines
On 5/29/07, Joao Morais wrote: > > Hi, > > > > Attached is a application that demonstrates a bug in FormatFloat. > > All tests pass when run under Delphi, but test four fails under > > Free Pascal. > > IMHO, fpc behaves correctly. 0.005 is closer than 0 instead 0.01. > If you try 0.00500000001, you will have 0.01, also correct. Normally you would round 5 or higher up so taking 0.005 to two decimal places would give you 0.01 which is what Delphi does! This is mathematically correct. It's got nothing to do with whether it is closer to zero or to one.

Try 0.015, that should round to 0.02. IIRC, it's "round-to-even", see http://en.wikipedia.org/wiki/Rounding.

regards
Burkhard

Reply by email

Michael Van Canneyt
to Graeme Geldenhuys
Graeme Geldenhuys wrote 9 lines
On 5/29/07, Michael Van Canneyt wrote: > If I remember correctly: > Bookkeepers rounding as implemented by the Intel processors. > No own routines are used. It's left up to the processor. I thought FormatFloat() uses Common Rounding and FormatCurr() uses Bankers Rounding. This would make more sense, and I believe Delphi has this behavior.

Not that I know of. As far as I know, it's left up to the CPU. But it's been a long time since I looked at this, so I might be wrong. This is in the delphi docs:

<help>
function Round(X: Extended): Int64;

Description

In Delphi, the Round function rounds a real-type value to an integer-type value.

X is a real-type expression. Round returns an Int64 value that is the value of X rounded to the nearest whole number. If X is exactly halfway between two whole numbers, the result is always the even number. This method of rounding is often called "Banker’s Rounding".

If the rounded value of X is not within the Int64 range, a runtime error is generated, which can be handled using the EInvalidOp exception.

Note: The behavior of Round can be affected by the Set8087CW procedure or SetRoundMode function. </help>

And that's all that can be found. So from this I conclude it's up to the CPU, and furthermore you cannot assume anything about it since the behaviour of the CPU is programmable with the abovementioned procedures. (which are obviously not portable, because they are Intel specific...)

Michael.

Reply by email

Jonas Maebe
to Michael Van Canneyt
Michael Van Canneyt wrote
Not that I know of. As far as I know, it's left up to the CPU. But it's been a long time since I looked at this, so I might be wrong.

The writing of floating point values does not use the rounding functionality provided by the FPU, it is implemented in a completely cpu-independent way. Further, this program:

var
   c: currency;
begin
   c:=0.005;
   writeln(c:0:2);
end.

writes 0.01. So it's probably a bug in one of the umpteen sysutils float formatting "helpers" (and if someone fixes it, please take into account that most cpus have comp=currency=int64, and that most do not support the extended types; there have been many bugs on different platforms in the past because of wrong assumptions about this in the past).

Jonas

Reply by email

Michael Van Canneyt
to Jonas Maebe
Jonas Maebe wrote 9 lines
On 30 mei 2007, at 11:10, Michael Van Canneyt wrote: >Not that I know of. As far as I know, it's left up to the CPU. >But it's been a long time since I looked at this, so I might >be wrong. The writing of floating point values does not use the rounding functionality provided by the FPU, it is implemented in a completely cpu-independent way.

Doesn't it use rounding ?

Show quoted text 14 lines
Further, this program: var c: currency; begin c:=0.005; writeln(c:0:2); end. writes 0.01. So it's probably a bug in one of the umpteen sysutils float formatting "helpers" (and if someone fixes it, please take into account that most cpus have comp=currency=int64, and that most do not support the extended types; there have been many bugs on different platforms in the past because of wrong assumptions about this in the past).

If it's not CPU stuff, then this is going to be messy :/

Michael.

Reply by email

Jonas Maebe
to Michael Van Canneyt
Michael Van Canneyt wrote 8 lines
On Wed, 30 May 2007, Jonas Maebe wrote: > The writing of floating point values does not use the rounding > functionality > provided by the FPU, it is implemented in a completely cpu- > independent way. Doesn't it use rounding ?

It does, but does not use the round() routine for that. Sometimes it even rounds at the string level (since with FPU numbers, 0.x *10 can be >= 10.0).

Jonas

Reply by email

Graeme Geldenhuys
to Michael Van Canneyt: Not that I know of. As far as I know, it's left up to the CPU. But it's been a long time…
Michael Van Canneyt wrote 5 lines
And that's all that can be found. So from this I conclude it's up to the CPU, and furthermore you cannot assume anything about it since the behaviour of the CPU is programmable with the abovementioned procedures. (which are obviously not portable, because they are Intel specific...)

So if the behavior is built into the processor, it *still* doesn't explain why Free Pascal differs from Delphi. Using the same processor on the same PC in a dual boot setup.

Graeme Geldenhuys General error, hit any user to continue.

Reply by email

Graeme Geldenhuys
to Jonas Maebe: The writing of floating point values does not use the rounding functionality provided by…
Jonas Maebe wrote 10 lines
var c: currency; begin c:=0.005; writeln(c:0:2); end. writes 0.01. So it's probably a bug in one of the umpteen sysutils float formatting "helpers" (and if someone fixes it, please take into

Yeah, that outputs the correct value here as well. Confirmed a bug then!

Graeme Geldenhuys General error, hit any user to continue.

Reply by email

Yury Sidorov
to Graeme Geldenhuys

From: "Graeme Geldenhuys"

Show quoted text 16 lines
On 5/30/07, Jonas Maebe wrote: > > var > c: currency; > begin > c:=0.005; > writeln(c:0:2); > end. > > writes 0.01. So it's probably a bug in one of the umpteen sysutils > float formatting "helpers" (and if someone fixes it, please take > into Yeah, that outputs the correct value here as well. Confirmed a bug then!

Currency to string conversion is handled in other way that float types. In original example extended type was used....

Yury,

Reply by email

Graeme Geldenhuys
to Yury Sidorov
Yury Sidorov wrote 6 lines
> Yeah, that outputs the correct value here as well. Confirmed a bug > then! Currency to string conversion is handled in other way that float types. In original example extended type was used....

Yury, you are right! I never spotted that, thanks.

Running the example program below shows the difference in output. The currency type rounds to 0.01 and the extended type rounds to 0.00. Either way, I still believe this is a bug in FormatFloat.

Test system is a Intel Pentium 4 processor running Windows 2000 and Linux.

-----------------------------------------------------------
program Project1;
{$mode objfpc}{$H+}
uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes
  { add your units here };
var
  c: currency;
  e: extended;
begin
  c:=0.005;
  e:= 0.005;
  writeln(c:0:2);
  writeln(e:0:2);
end.
-----------------------------------------------------------
Graeme Geldenhuys General error, hit any user to continue.

Reply by email

Michael Van Canneyt
to Graeme Geldenhuys
Graeme Geldenhuys wrote 13 lines
On 5/30/07, Yury Sidorov wrote: > > Yeah, that outputs the correct value here as well. Confirmed a bug > > then! > > Currency to string conversion is handled in other way that float > types. In original example extended type was used.... > Yury, you are right! I never spotted that, thanks. Running the example program below shows the difference in output. The currency type rounds to 0.01 and the extended type rounds to 0.00. Either way, I still believe this is a bug in FormatFloat.

Please put it in the bugtracker, so it won't be forgotten.

Michael.

Reply by email

Jonas Maebe
to Graeme Geldenhuys: Yury, you are right! I never spotted that, thanks. Running the example program below…
Graeme Geldenhuys wrote 5 lines
Yury, you are right! I never spotted that, thanks. Running the example program below shows the difference in output. The currency type rounds to 0.01 and the extended type rounds to 0.00. Either way, I still believe this is a bug in FormatFloat.

No, then it is related to the generic float->string routines. However, you are mainly running into a precision issue with the fpu, because 0.005 cannot be represented exactly using any floating point type. This is especially clear since if you change the type from extended to double, you also get 0.01.

0.005, as extended, is actually 0.004999999999999999999898356046329484 As double, it is actually 0.0050000000000000001

It's possible that the float to string routines can be tweaked to produce 0.01 also for extended, but if so this will require extensive testing to ensure that they don't give wrong results again for other values.

Jonas

Reply by email

Graeme Geldenhuys
to Jonas Maebe
Jonas Maebe wrote
0.005, as extended, is actually 0.004999999999999999999898356046329484 As double, it is actually 0.0050000000000000001

For now I have managed to implement a work-around in my code which passes all unit tests I could first look at what precision is required from the user, by looking at the format string (eg: '. I then use a tiSetPrecision function that rounds the Extended (or Double or Single) value passed in to the correct amount of decimals using a default rounding method (my default is Common Rounding aka HalfUp). It then passes the new extended which contains the already rounded value to the FormatFloat() so no round needs to be done, only formatting. This works perfectly and gives consistent results under both FPC and Delphi.

The rounding routine is based on the code posted to the Borland Developer Networks forums for all to use.

Here is an extract which highlights the exact problem we are talking about: ---------------------------------------
These routines round input values to fit as closely as possible to an output number with desired number of decimal fraction digits.

Because, in general, numbers with decimal fractions cannot be exactly represented in IEEE-754 floating binary point variables, error limits are used to determine if the input numbers are intended to represent an exact decimal fraction rather than a nearby value. Thus an error limit will be taken into account when deciding that a number input such as 1.295, which internally is represented 1.29499 99999 …, really should be considered exactly 1.295 and that 0.29999 99999 ... really should be interpreted as 0.3 when applying rounding rules.

Nine rounding rules are implemented.
--------------------------------

The implementation as used in the tiOPF project can be viewed here (around line 3200): http://tinyurl.com/2jawq9
The function comment also includes the original link to the BDN page.

I think the Free Pascal developers will find this code very useful. Hopefully it can find it's way into the FPC compiler.

Graeme Geldenhuys General error, hit any user to continue.

Reply by email