handmade.network » Wiki » Fixed point arithmetic

Fixed point numbers and arithmetic are used on CPUs without a floating point unit, they are also useful for calculations where controlling precision is very important, like operations on money values. This article discusses only decimal fixed point numbers at this point.

Notation

This article uses fixed point notation with (1/s) denoting the comma scaling factor, where s is the number you would have to divide the given integer by to get the comma into the right place. For example: the number 12.34 is 1234(1/100) => 1234 / 100 = 12.34

Examples

The example calculations are meant to give additional insight into the calculations if the textual explanation is not sufficient. They intentionally show many intermediate steps in the calculation to make them accessible to more readers.

Converting scaling factors

To convert from one scaling factor to another we divide the target s by the source s and multiply the result with the integer. Special consideration has to be given to the case when the source s is larger than the target s. In this case we have to divide the source s by the target s and then divide the integer by the result.

Converting a number from one scaling factor to another happens as follows:

Example: In this case we convert from (1/100) to (1/1000)

1
2
3
4
5
6
12.43       = x * 1.243
1243(1/100) = x * 1243(1/1000)     | / 1243(1/1000)
x = (1243(1/100))/(1243(1/1000))
x = (1243/1243)*((1/100)/(1/1000))
x = (1)*(1000/100)
x = 10 => 10*1243(1/1000) = 12430(1/1000) => 12.430

Addition and Subtraction

Addition (and in turn subtraction) of numbers with the same scaling factor happens by simply adding (or subtracting) the integer numbers. The scaling factor stays the same. To add or subtract numbers with different scaling factors, convert them to a common scaling factor.

Multiplication

Multiplication can be done by multiplying the numbers as integers and then determining what the scaling factor of the result is. The scaling factor of the result is the product of both initial scaling factors.

Example:

1
2
3
4
5
  12.43       * 54.684
  1243(1/100) * 54684(1/1000) 
= 1243*54684*(1/100)*(1/1000) 
= 1243*54684*(1/100*1000)     
= 67972212(1/100000) => 679.72212

Division

Division has the potential for serious loss of precision, even truncated wrong results if scaling factors are not taken into account. While multiplication yields a result that has an s value that's the product of the input s values, in division they result s is a quotient of all the input s values. The difference in the scaling factors of the dividend and divisor determines the scaling factor of the result. Let's consider the three different cases.

  • The scaling factor of the dividend is larger than that of the divisor. We get a scaling factor <1 (e.g. 1/10)
  • the scaling factor of the divisor is larger than that of the dividend. We get a scaling factor >1 (e.g. 10/1)
  • The scaling factors are the same. We get a scaling factor =1 (e.g 10/10)

If the scaling factor of the dividend is larger than that of the divisor we get result digits after the decimal point, this is the situation we want. We need a difference that is large enough to get a result that has a scaling factor that allows for rounding to the needed significant digits.

Let's do an example to see how the scaling factor comes out:

1
2
3
4
5
6
  54.684 / 12.43
  54684(1/1000)/1243(1/100)         
= (54684/1243) * ((1/1000)/(1/100)) 
= (54684/1243) * (100/1000)         
= (54684/1243) * (1/10)             
= 43(1/10) => 4.3

Assume we want the result to have two significant digits, and in total four to do rounding. First, determine the scaling factor of the result: (1/1000)/(1/100) = (1/10) => one digit after comma, we need to add three. Then convert from the result scaling factor to the one we actually want the result to have.

1
2
3
4
5
6
7
s2 > s1 ->
s2/s1        = x
1000000/1000 = x
x = 1000

since s2 > s1, multiply 
54684(1/1000000) * 1000 = 54684000(1/1000000)

Example Division with more precision

1
2
3
4
5
6
 54.684 / 12.43
 54684000(1/1000000)/1243(1/100) 
= (54684000/1243) * ((1/1000000)/(1/100))
= (54684000/1243) * (100/1000000)
= (54684000/1243) * (1/10000)
= 43993(1/10000) => 4.3993

Rounding

To round, add 5 to the digit that is one past your desired precision. Then truncate up to the desired precision.

Rounding example:

1
43993(1/10000) + 5*10 = 44043(1/10000) / 100 = 440(1/100) => 4.40