In How JavaScript Works, I demonstrated two ways to approximate real numbers using integers: Floating Point and Rationals. There is also a third way: scaled numbers. This was the very first way used on the first generation of computers. The technique is still effective today, especially on the cheapest microprocessors.
It is really simple.
scaled_number = int * scale_factor int = scaled_number / scale_factor
In a scaled number, the range is traded off for a fractional piece, essentially
small_int + numerator / scale_factor
I first worked with these in 1983, writing a graphics library in which the pixel coordinates were real numbers.
For example, the rect
function took a scaled x, a scaled y, a scaled width, and a scaled height.
That kind of stuff is pretty common now, but it was bleeding edge forty years ago.
Addition and subtraction of scaled numbers is really easy. If two numbers have the same scale factor, you can add and subtract them as if they are integers. Multiplication and division are a little bit hard. The result of the multiply instruction must be divided by the scale factor. This is because
(int * scale_factor) * (another_int * scale_factor) = (int * another_int) * (scale_factor * scale_factor)
Similarly, when dividing, the dividend must first be multiplied by the scale factor. On the cheapest processors, make the scale factor a power of two, because shifting bits is a lot easier than multiplying and dividing.
My favorite scale factor is nine million. In a 64 bit system, this gives numbers as huge as
1024819115206 775807/9000000
1024819115206.0862007
and as tiny as
1/9000000
0.0000001
The overline above the seventh decimal place indicates that the digit is repeating.
0.000000111111...
An upper bound of a trillion is too small for some scientific applications, but is adequate for most business needs. That comes with six exact decimal places, which is something that IEEE 754 binary floating point can not do, and there is a repeating seventh digit. That makes it possible to exactly represent all of these fractions:
1/2, 1/3, 1/4, 1/5, 1/6, 1/8, 1/9, 1/10, 1/12, 1/15, 1/16, 1/18, 1/20, 1/24, 1/25, 1/30, 1/32, 1/36, 1/40, 1/45, 1/48, 1/50, 1/60, 1/72, 1/75, 1/80, 1/90, 1/96, 1/100, 1/120, 1/125, 1/144, 1/150, 1/160, 1/180, 1/200, 1/225, 1/240, 1/250, 1/288, 1/300, 1/360, 1/375, 1/400, 1/450, 1/480, 1/500, 1/600, 1/625, 1/720, 1/750, 1/800, 1/900, 1/1000, 1/1125, 1/1200, 1/1250, 1/1440, 1/1500, 1/1800, 1/1875, 1/2000, 1/2250, 1/2400, 1/2500, 1/3000, 1/3125, 1/3600, 1/3750, 1/4000, 1/4500, 1/5000, 1/5625, 1/6000, 1/6250, 1/7200, 1/7500, 1/9000, 1/9375, 1/10000, 1/11250, 1/12000, 1/12500, 1/15000, 1/18000, 1/18750, 1/20000, 1/22500, 1/25000, 1/28125, 1/30000, 1/36000, 1/37500, 1/45000, 1/50000, 1/56250, 1/60000, 1/75000, 1/90000, 1/100000, 1/112500, 1/150000, 1/180000, 1/225000, 1/300000, 1/450000, 1/900000
I call this system Num9e6, and it is much better suited to most computing tasks than the IEEE format we are currently stuck with.