In my article “When Floats Don’t Behave Like Floats” I explained how calculations involving single-precision floating-point variables may be done, under the covers, in double or extended precision. This leads to anomalies in expected results, which I demonstrated with two C programs — compiled with Microsoft Visual C++ and run on a 32-bit Intel Core Duo processor.
In this article, I’ll do a similar analysis for double-precision floating-point variables, showing how similar anomalies arise when extended precision calculations are done. I modified my two example programs to use doubles instead of floats. Interestingly, the doubles version of program 2 does not exhibit the anomaly. I’ll explain.
The decimal representations of oppositely signed powers of two and powers of five look alike, as seen in these examples: 2-3 = 0.125 and 53 = 125; 5-5 = 0.00032 and 25 = 32. The significant digits in each pair of powers is the same, even though one is a fraction and one is an integer. In other words, a negative power of one base looks like a positive power of the other.
This relationship is not coincidence; it’s a by-product of how fractions are represented as decimals. I’ll show you simple algebra that proves it, as well as algebra that proves similar properties — in products involving negative powers.
PARI/GP is an open source computer algebra system I use frequently in my study of binary numbers. It doesn’t manipulate binary numbers directly — input, and most output, is in decimal — so I use it mainly to do the next best thing: calculate with powers of two. Calculations with powers of two are, indirectly, calculations with binary numbers.
PARI/GP is a sophisticated tool, with several components — yet it’s easy to install and use. I use its command shell in particular, the PARI/GP calculator, or gp for short. I will show you how to use simple gp commands to explore binary numbers.
To write a computer program to print the first 1000 nonnegative powers of two, do you think you’d need to use arbitrary precision arithmetic? After all, 21000 is a 302-digit number. How about printing the first 1000 negative powers of two? 2-1000 weighs in at a whopping 1000 decimal places. It turns out all you need is standard double-precision floating-point arithmetic — and the right compiler!
Interestingly, programming languages vary in how much precision they allow in printed floating-point fractions. You would think they’d all be the same, allowing you to print as many decimal places as you ask for. After all, a floating-point fraction is a dyadic fraction; it has as many decimal places as it has bits in its fractional representation.
Consider the dyadic fraction 5,404,319,552,844,595/253. Its decimal expansion is 0.59999999999999997779553950749686919152736663818359375, and its binary expansion is 0.10011001100110011001100110011001100110011001100110011. Both are 53 digits long. The ideal programming language lets you print all 53 decimal places, because all are meaningful. Unfortunately, many languages won’t let you do that; they typically cap the number of decimal places at between 15 and 17, which for our example might be 0.59999999999999998.
Now that you know how the powers of two are named, lets look at other, nonstandard ways to name them. You will see these names on the internet as well as in books. We will not use them on this site other than in this article, and we only discuss them here to make you aware of their use. As a by-product of this discussion, you may gain some insight into the nature of the powers of two. But beware — you may become confused as well!
That’s easy if it’s in the form 2n, where n is an integer. For example, 212, 20, and 2-37 are powers of two. That is by definition. But what about arbitrary positive numbers like 16,392, 524,288, or 0.00390625? Are they powers of two? Here’s how to tell — if they can be simplified to the form 2n, they are; if they can’t, they’re not.