Inaccurate floating/double numbers

BlitzMax Forums/BlitzMax Programming/Inaccurate floating/double numbers

Johnsprogram(Posted 2009) [#1]
It has been bothering me for a while.
When I use the data types float or double to a variable and assign a number 0.02 and later retrieve it, it'll show up as something like 0.01932932 or so.
Is there a reason for that?


GfK(Posted 2009) [#2]
Its been mentioned about 14 million times before. Floating point innacuracy - nothing you can do about it (other than store the value as a string).


Brucey(Posted 2009) [#3]
Here's some more information as per the Wikipedia article on Floating point

By their nature, all numbers expressed in floating-point format are rational numbers with a terminating expansion in the relevant base (for example, a terminating decimal expansion in base-10, or a terminating binary expansion in base-2). Irrational numbers, such as π or √2, or non-terminating rational numbers, must be approximated. The number of digits (or bits) of precision also limits the set of rational numbers that can be represented exactly. For example, the number 123456789 clearly cannot be exactly represented if only eight decimal digits of precision are available.
When a number is represented in some format (such as a character string) which is not a native floating-point representation supported in a computer implementation, then it will require a conversion before it can be used in that implementation. If the number can be represented exactly in the floating-point format then the conversion is exact. If there is not an exact representation then the conversion requires a choice of which floating-point number to use to represent the original value. The representation chosen will have a different value to the original, and the value thus adjusted is called the rounded value.
Whether or not a rational number has a terminating expansion depends on the base. For example, in base-10 the number 1/2 has a terminating expansion (0.5) while the number 1/3 does not (0.333...). In base-2 only rationals with denominators that are powers of 2 (such as 1/2 or 3/16) are terminating. Any rational with a denominator that has a prime factor other than 2 will have an infinite binary expansion. This means that numbers which appear to be short and exact when written in decimal format may need to be approximated when converted to binary floating-point.



Of course, you can also store your values as rational numbers (fractions), and work your arithmetic on those... but if you need at some point, to show it on screen with a decimal place, you'll probably have to round it.


Warpy(Posted 2009) [#4]
And using rationals is a massive faff, so not worth bothering with unless you're doing Science!


Brucey(Posted 2009) [#5]
Nothing wrong with science! :-p


ImaginaryHuman(Posted 2009) [#6]
You might also try using fixed point, which is particularly nice if your numbers are not going to get too large. If you are within the range 0..65535, then you can just use `Shl 16` and `Shr 16` to convert between fixed point and integer. You will get better accuracy, in a way, than floating point, because you always get the same number of decimal places no matter where the number is in the range - whereas with floating point, smaller numbers get more decimal points than larger ones.

With that in mind, what if you store a float as 0.00002, does it come back correctly?


Shortwind(Posted 2009) [#7]
In some quick tests, it seems to me that the internal floats are working as needed, it seems on quick inspection that the print function is borked...

Can any blitzmax dev or other expert confirm this brief analyst?


ImaginaryHuman(Posted 2009) [#8]
It would help if you show how you tested it


GfK(Posted 2009) [#9]
It would help if you show how you tested it
Doesn't really matter how he tested it, because its wrong. Floats by nature are not accurate. Nothing to do with Blitz, or print functions. You would have the same issues using floats in any other language.


Shortwind(Posted 2009) [#10]
I recant my previous post... Doing more investigation, will return my findings shortly...


Shortwind(Posted 2009) [#11]
Still investigating, but have a question. How does anyone do simple currency calculations using floats? From what I can see you can't, easily, and that is scratching the surface. What is the internal conversion of the compiler when getting literal float values? I mean, my old C64 was more accurate then this. LOL

Local i:Float = 2.01
Local k:Float = 0
Local x:Int
Local a:Long

k=i
a=201
For x=1 To 10
k=k+1.02
a=a+102
Print k
Print a
Print
Next

The only way (for currency anyway) to get some accuracy is to do the following:

local i:Float = 2.01001

But even that will turn up errors eventually.


TaskMaster(Posted 2009) [#12]
For currency, do all of your math in Integers, then place the decimal point yourself when printing numbers.


Nate the Great(Posted 2009) [#13]
are you planning on starting a bank with the money management programmed in blitz max?

but floating point innacuracy is not avoidable as far as I know... unless you make your own math system to use strings as numbers.


Shortwind(Posted 2009) [#14]
@GfK:

First: I beg to differ on your comment that you'd have the same problems in any language. I'm not disputing the problems with floats, I'm just saying somewhere in the implementation of them in BlitzMax there is an error. They are seriously too inaccurate.

Standard C
#include <stdio.h>

int main()
{
float i=2.01;
float k=0.00;
int x;

k=i;

for(x=1;x<10;x++)
{
k=k+1.01;
printf("%f\n",k);
}

}

As you can see, if you compile and run this code, the standard C float, for literals conversion is more than sufficiently accurate. Somewhere in the BlitzMax code it gets it seriously wrong. Example:

Local j:float = 0.22
print j

AND

#include <stdio.h>

int main()
{
float j=0.22;
printf("%f\n\n",j);

}

I'd really like to here from a developer on this issue. It's not like this is a free program, so I feel I have the right as a owner of the software to ask questions in this regard.


Gabriel(Posted 2009) [#15]
What's your definition of "seriously wrong"? When I run your code, it's no more than 0.000000001 away from what it should be. That seems pretty accurate for single precision floats to me.

EDIT: I'm not using the latest version of BlitzMax so if you're getting vastly different results, perhaps it's a newly introduced bug.


plash(Posted 2009) [#16]
It's not like this is a free program, so I feel I have the right as a owner of the software to ask questions in this regard.
An official response is a rare sight :(


Shortwind(Posted 2009) [#17]
Currency was just an example. In the scope of games, maybe it is a moot point, but it is still valid. Search the forums and see how many functions and routines require floats and doubles? There are tons. I'm sure we could list 100 things that these inaccuracies will effect in many types of programming. There are more than just game programmers out there using this language, so the argument is not moot.

For someone that has been programming since 1982, of course you come to realize that floating point calculations can go wack. But, you learn the scope of the accuracy (to how many decimal places) you can expect your code to work, most of the time. But to have your floating point values screwed before you even attempt any calculations on them is not just inconvienent, it's unforgivable.

When you put in a literal such as 0.22, the very least the compiler should do is encode that as 0.22000000000 or how ever many places the float or double requires. If the blitzmax compiler is encoding this value as 0.219999999 this is just wrong.


ziggy(Posted 2009) [#18]
If making currency, you should use string based calculations as PL/1 does. I remember there was a module written by Brucey to do so.
for small currency calculations, you can use good old error rounding techniques:
Local i:Float = 2.01
Local k:Float = 0
Local x:Int
Local a:Long

k = i
a = 201
For x = 1 To 10
	k = k + 1.02
	a = a + 102
	Print k
	Print a
	Print ccRound(a, 2)
	Print
Next

Function ccRound:Double(number:Double, decimals:Int)
	Local t:Int = 10^decimals
	Local result:Double = Int(number * t + 0.5:Double * Sgn(number))
	Return result / t
End Function



Shortwind(Posted 2009) [#19]
@Gabriel

With only 2 decimal places when you enter 0.22 it should print 0.22, not 0.219999999.......


Shortwind(Posted 2009) [#20]
@ziggy

True, true. Of course there are work arounds, but in every other language I've ever programmed in I didn't have to worry to much about accuracy in the first and second decimal place..... :(


ziggy(Posted 2009) [#21]
@Shortwind:Well, you were complaining on how it is printed, so that is the solution. If you complain about the precision of a float, then there's nothing to do!
It may help (not tested) if you specify double precision on variables AND constats
k = k + 1.02:Double



GfK(Posted 2009) [#22]
It's not like this is a free program, so I feel I have the right as a owner of the software to ask questions in this regard.
Of course, but having the right to ask questions doesn't magically make you right, and everybody else wrong.


Shortwind(Posted 2009) [#23]
@GfK

LOL. Of course. Not trying to start a fight, just trying to figure out how they screwed up something so seemingly simple.

How do I represent 0.22 in floats without convoluted routines and integers and such? Do tell. LOL


*(Posted 2009) [#24]

Doesn't really matter how he tested it, because its wrong. Floats by nature are not accurate. Nothing to do with Blitz, or print functions. You would have the same issues using floats in any other language.


Every program under the sun has this problem, all languages do. The only programs that dont seem to is accounting packages.


matibee(Posted 2009) [#25]
Here's a simple test, and I found BMax is outputting floats to 8 decimal places which is why you see the error; printf works better in your example when defaulting to 6 decimal places.

//====  floattostring.h
#include <stdio.h>

extern "C" {

	void floattostring( char * pDest, float f );
	
}

//==== floattostring.cpp
#include "floattostring.h"

void floattostring( char * pDest, float f )
{
	sprintf( pDest, "%f\n", f ); // defaults to 6 decimal places
	//sprintf( pDest, "%5.8f\n", f ); // gives same result as bmax

}

//==== test.bmx
Import "floattostring.cpp"

Extern "C"
	Function floattostring( a:Byte Ptr , f:Float )
End extern


Local f1:Float = Float 2.01
Local f2:Float = Float 1.02

For Local t:int = 1 To 10

	Local b:Byte[200]
	Local bp:Byte Ptr = b
	Local s:String

	floattostring( bp , f1 )
	
	Local i:Int = 0
	While b[i]
		s = s + Chr$( b[i] )
		i :+ 1
	End while

	Print "float : " + f1
	Print "string: " + s
	
	f1 :+ f2
next


I couldn't find a better way of passing a string buffer, or converting one to a string :)


Brucey(Posted 2009) [#26]
How does anyone do simple currency calculations using floats

You don't. In any language. They aren't accurate enough. (of course that doesn't stop companies using 32-bit floats to do currency calculations... perhaps they didn't know any better, but it's a bit embarrassing admitting to working for one...)

Using Integers is one way. Using String/character representation is another.

For example, MAPM can handle up to an accuracy of 2 billion digits, if you have enough memory to store it. Internally it uses some kind of digit representation.


Floyd(Posted 2009) [#27]
The accuracy of floats and doubles is whatever the hardware supports. The odd looking display comes from the fact that there is no control over formatting.

For single precision BlitzMax uses nine digits when converting a float to a string, which is then used for Print. The reason it uses nine digits is that this is the smallest number which guarantees converting float to string and then back to float recreates the original float. Thus you can save a text file, or XML or whatever, read it back and get the original number. Doubles do the same thing, but with seventeen digits.

The result is an unsightly display, which is as accurate as it can be.

Note that Blitz3D rounds the display to six digits. The result often looks better, and more accurate, even though the underlying numbers are the same.


Brucey(Posted 2009) [#28]
I couldn't find a better way of passing a string buffer, or converting one to a string

There may even be a module to do it already...


Floyd(Posted 2009) [#29]
You can find the default number-to-string conversion in \mod\brl.mod\blitz.mod\blitz_string.c

The code for float and double is
	sprintf( buf,"%#.9g",n );
	sprintf( buf,"%#.17lg",n );
It would be nice to have a little more control for display purposes. But this is the way it is done for automatic conversion between numeric and string values.


matibee(Posted 2009) [#30]
There may even be a module to do it already...



lol brucey, I'm sure there is. I expected to be able to pass a ToCString(), but meh. Quick newbie test n all that.

Anyway, none of this solves the op's problem, it's merely highlighting using floats for currency is a bad idea.

Integer math would readily give you +/-$21,474,836.48. That'd do for balancing my books.. just about :)


xlsior(Posted 2009) [#31]
lol brucey, I'm sure there is.


Yup: Brucey made one himself some time back. It's on his download page.


Shortwind(Posted 2009) [#32]
Well that was a lively discussion. Thanks to all that responded.

I suppose I'll have to work on modifiying the blitzmax source code to support BCD. :)


dynaman(Posted 2009) [#33]
> Every program under the sun has this problem, all languages do. The only programs that dont seem to is accounting packages.

Used to do some bank work, they have special packages that do the calculations and never use floating point variables (well, at least the ones I worked on). The package code was much slower then floating point, but much more accurate - and accuracy was the primary requirement.


ziggy(Posted 2009) [#34]
I did wrote this some time ago. It is a function to show floats with precission lose correction when converted to strings. It works very well and outputs the same as b3d and other languages.
Local V1:Float
V1 = -1.112
Print V1
Print FloatToString(V1)

V1 = 1324323.54323
Print V1
Print FloatToString(V1)

V1 = 1000000000
Print V1
Print FloatToString(V1)

V1 = 0.0001
Print V1
Print FloatToString(V1)


Function FloatToString:String(FVale:Float)
	Local S:String = ccRound(Fvale, 6)
	If s.EndsWith("1") Then s = Left(s, Len(s) - 1)	'Last char double-precission rounding error.
	While Right(s, 1) = "0"	'Remove unneeded 0 at the end of te decimal part
		s = Left(s, Len(s) - 1)
	Wend
	If s.EndsWith(".") Then s = Left(s, Len(s) - 1)
	Return S
	Function ccRound:Double(number:Double, decimals:Byte)	'Weigted non-trucate rounding.
		Local t:Long = 10 ^ decimals
		Local result:Double = Long(number * t + 0.5:Double * Sgn(number))
		Return result / Double(t)
	End Function
End Function



Blueapples(Posted 2009) [#35]
How does anyone do simple currency calculations using floats

You don't. In any language. They aren't accurate enough.


Trust me, Brucey hit the nail on the head with this. Spent about 10 hours forcing floats to work correctly for a Quickbooks import. We finally got it to work but you have to do a lot of really retarded round(x, 2) and 2 decimal string formatting. Too bad it's too late at this point to change our DB structure to use a fixed decimal string value, it would have saved us a lot of time.

Oh and this is Python code. If Python doesn't "get it right", no one has much chance. Use a currency library.