The float, the finance and the folly

When I was studying mathematics in university, I was introduced to 3 number “lines”, for lack of a better word. They were the integers (denoted by Z with a double line at the slant), natural numbers (denoted by N with a double line at the slant) and real numbers (denoted by R with a double line at the vertical). There were others, such as complex numbers, rational and irrational numbers. Let’s keep this simple, shall we?

Mathematical symbol for natural, integer and real numbers

Yeah, that’s how I wrote those symbols. I particularly like writing the Z with the 2 disconnected strokes. Ah, those times of writing mathematical proofs… To forestall a question you might have, the I is used to denote imaginary numbers, that’s why Z is used for integers. As to why Z is used for integers, Z

stands for Zahlen (German for numbers)

Integers (or whole numbers) are numbers without fractional parts, such as 5 or 72 or -3, including zero. Natural numbers are numbers for counting, referring to zero and positive integers (sometimes referred to as non-negative integers). Depending on context, zero might not be included. Real numbers are every single existing number available. Numbers such as 1.23456 and 3.14159 and 1.414. They also include integers, and naturally include the natural numbers.

Where am I going with this?

When I learned programming, the idea of having 2 sets of variable types dealing with numbers, one of integral types and one of non-integral types (which I gave a basic introduction here and here), was very natural. On the one hand, we have byte, short, int and long. On the other, we have float and double. I also learned the limitations of each variable type and the appropriate usage of each type.

You should be familiar with the integral types. It’s the non-integral types, or the floating points that I’m worried about. You will note that, while the integrals can’t represent all integers (they have a limit, like 2 ^ 31), they do represent the particular integer value exactly. A 100 is definitely a 100, for example (unless you change the form of representation).

The float and double variable types present a problem. They only hold a number value that’s close to the value you want (most of the time). It’s something about the IEEE representation, the mantissa and the exponent. Research on your own; it’s good for your learning.

Real numbers can have a precision stretching up to infinity. Obviously, our float and double have a bit of a problem with that. Actually this reminds me of a song I remembered from a children’s show (from way back when I was a child):

Thaaaaaaattt’ss iiiiinnnfinnnity
Yooouu can count forever
There’ll always be one more

That’s infinity
Count from dusk till dawn
You’ll never reach infinity
It’ll just go on, and on, and on, and on, and on…

You know, maybe I’ll sing and record it down… you know what? Maybe not. I’ll spare you the agony…

Ok, where was I? Oh yes, precision.

Games don’t need it

More precisely, precision usually isn’t an issue in games. floats and doubles are used in games for storing values such as the (x,y,z) positional coordinate of a player for instance. They don’t have to exact. They just have to be relatively close to the exact value. It’s not like you’re going to notice a 0.00000005 unit left shift, or you rotated like 0.000003 radians clockwise more than is required.

This is why floats are preferable to doubles in games (I might be dated on this…), because they’re faster to add and multiply to, and you don’t need the extra precision.

Financial applications depend on it

If you work with financial applications, it’s a whole new story. The Singapore and American currency use the dollar and cents. 100 cents equal a dollar. It’s typical to use $12.34 to represent 12 dollars and 34 cents (my apologies to the comma-toting Europeans). So the dollar is correct (and exact) up to 2 decimal places.

Does anyone see a problem with using floats on this?

Sure, the float is more than capable of displaying just 2 decimal places. But it cannot represent the smallest unit of currency 0.01 exactly. To illustrate this, try the following piece of code:

float f = 0f;
int i;
for (i = 0; i < 100; ++i)
    f += 0.01f;

It's in C#, and you should be able to translate to C or some other language you use. Note the output. 0.01 is not 0.01 when stored in a floating point variable type.

This presents some exceedingly infuriating debugging sessions while figuring out why certain values don't work out correctly. It's also why you cannot do equality checks with floating point values (I mentioned this in my recent newsletter, and if you're not on it, please go sign up).

If you're using C# or some other modern language, there should be a variable type for fixed-point numbers (fixed number of digits after radix [or the decimal point]), the decimal type being the one for C#. Or the numeric data type in databases, but stay away from the money type. I don't know, I find it funny to store my monetary values in a money variable type. *shudder*

If you're using C or some other language without fixed-point number variable types, then my advice is to avoid calculation with floating points as much as possible. Do them in the environment where they are still exact.

For example, you want to retrieve some values from the database and bind them to local program variables. Then you're going to do some calculation with the variables, and update them back into the database. Can you do the calculation entirely in the database environment?

You might not have grasped the significance of the code above. Sure, the values are still exactly represented for most of the 100 decimal values. Imagine adding, subtracting, multiplying and dividing those values tens of times, hundreds of times. Ever heard of rounding errors? Errors don't disappear, they accumulate. If you're lucky, they cancel each other out. If not...

Here's a question for you. Even though it's a folly to represent the 2-decimal-point currency values with a floating point variable, there are values which a float can represent exactly. Given the range of non-negative 2-decimal-point values less than 1, what are the values that can be represented exactly?

I'll publish my answer and reasoning in another (later) article. I also want to hear your answers too. Add your answer in a comment. Explain your reasoning too. Frankly, I'd much rather publish your answers, and add in some of my comments.

Comments are closed.