In a computer, decimal floating-point numbers are converted to binary floating-point numbers for calculation, and binary floating-point numbers are converted to decimal floating-point numbers for display or storage. In general, these conversions are inexact; they are rounded, and rounding is governed by the spacing of numbers in each set.
Floating-point numbers are unevenly spaced, and the spacing varies with the base of the number system. Binary floating-point numbers have power of two sized gaps that change size at power of two boundaries. Decimal floating-point numbers are similarly spaced, but with power of ten sized gaps changing size at power of ten boundaries. In this article, I will discuss the spacing of decimal floating-point numbers.
Decimal Floating-Point Numbers
A decimal floating-point number is a number written in normalized decimal scientific notation, a number like 4.87764 x 10-5. It is made up of a significand (4.87764) and a power of ten (10-5). The exponent of the power of ten shifts the decimal point to its appropriate place (0.0000487764). Numbers can be positive or negative. (I’ll consider only positive numbers; negative numbers are spaced the same, but as the mirror image.)
The significand contains a limited number of digits, called the precision. This results in gaps between consecutive numbers. Precision determines the number of gaps, and precision and exponent together determine the size of the gaps.
(I will use the phrase ‘decimal floating-point numbers’ but will be specifically talking about decimal floating-point strings. With regard to spacing there is no distinction, except that strings have no notion of subnormal numbers.)
Spacing In a Toy Floating-Point Number System
To get a feel for spacing, let’s look at a toy decimal floating-point system with one digit of precision and three exponents, -1, 0, and 1. There are 9 1-digit significands: 1, 2, 3, 4, 5, 6, 7, 8, 9. Combined with the three exponents, this system describes 27 numbers (in decimal floating-point, the number of significands and the size of the gaps grows too quickly, making it difficult to show more than the simplest of examples):
|1 x 10-1||0.1|
|2 x 10-1||0.2|
|3 x 10-1||0.3|
|4 x 10-1||0.4|
|5 x 10-1||0.5|
|6 x 10-1||0.6|
|7 x 10-1||0.7|
|8 x 10-1||0.8|
|9 x 10-1||0.9|
|1 x 100||1|
|2 x 100||2|
|3 x 100||3|
|4 x 100||4|
|5 x 100||5|
|6 x 100||6|
|7 x 100||7|
|8 x 100||8|
|9 x 100||9|
|1 x 101||10|
|2 x 101||20|
|3 x 101||30|
|4 x 101||40|
|5 x 101||50|
|6 x 101||60|
|7 x 101||70|
|8 x 101||80|
|9 x 101||90|
You can see three ranges of spacing: gaps of size 0.1 in the interval [0.1,1), gaps of size 1 in the interval [1,10), and gaps of size 10 in the interval [10,100). Here’s how the spacing looks over [0.1,10):
Gap count is determined by precision, so let’s start by counting the significands for each increment of precision:
- 1 digit of precision: There are 9 1-digit significands: 1, 2, 3, 4, 5, 6, 7, 8, 9
- 2 digits of precision: There are 90 2-digit significands: 1.0, 1.1, 1.2, … , 9.7, 9.8, 9.9
- 3 digits of precision: There are 900 3-digit significands: 1.00, 1.01, 1.02, … , 9.97, 9.98, 9.99
- 4 digits of precision: There are 9000 4-digit significands: 1.000, 1.001, 1.002, … , 9.997, 9.998, 9.999
From the pattern, you can see that there are 9·10p-1 p-digit significands. More formally, think of the significands as integers (disregard the decimal point). All p-digit integers lie in the interval [10p-1,10p), so there are 10p – 10p-1 = 9·10p-1 of them.
Another way to think of it is this: for each of the nine nonzero leading digits, there are p-1 digits left to vary, and thus 10p-1 combinations.
The number of significands is the same as the number of gaps, since there is a gap from the highest significand to the next power of ten.
Gap count = 9·10p-1
If we go from precision p to precision p+1, we’ll have 9·10p gaps. That is, the number of gaps is multiplied by ten. For each increment of precision, we add 9·10p – 9·10p-1 = 81·10p-1 gaps.
All decimal floating-point numbers with power of ten exponent e are in the interval [10e,10e+1). (The interval includes 10e but excludes 10e+1.) The length of this interval is 10e+1 – 10e = 9·10e.
Each interval has the same number of equal-sized gaps, which is determined by the precision p. The size of each gap in each interval is the length of the interval divided by the number of gaps: 9·10e/9·10p-1 = 10e-(p-1) = 10e+1-p.
Gap size = 10e+1-p
Each gap is a power of ten in size, and depends on the exponent and precision. With each increment of exponent, gap size is multiplied by ten; with each increment of precision, gap size is divided by ten. So if you increment the exponent and precision at the same time (or if you decrement both at the same time), the gap size will remain the same.
You can think of the 1-p part of the formula as shifting the exponent from the highest digit of the significand to the lowest digit. The lowest digit represents one ULP, and its place value is the gap size.
Spacing With Increased Precision and Range
Moving beyond our toy system, let’s look at the spacing with increased precision and range.
Spacing Between Nonnegative Powers of Ten
This table shows the gap counts and gap sizes between nonnegative powers of ten, for various combinations of precision and exponent:
Spacing Between Nonpositive Powers of Ten
This table shows the same information for nonpositive powers of ten:
Relationship to IEEE Binary Floating-Point
I’ve included precision and exponent values relevant to single-precision and double-precision IEEE binary floating-point. The decimal equivalents of single-precision numbers have exponents that range from -38 to 38, and the decimal equivalents of double-precision numbers have exponents that range from -308 to 308. (IEEE subnormal values would extend the decimal exponents down to -46 for single-precision and -324 for double-precision.) Precisions 6 and 9 are relevant to single-precision numbers, and precisions 15 and 17 are relevant to double-precision numbers; they are limits pertaining to round-trip conversions.
Binary Vs. Decimal Floating-Point Summary
Binary floating-point numbers have a power of two number of power of two sized gaps between powers of two; decimal floating-point numbers have power of ten sized gaps between powers of ten, but there aren’t a power of ten number of them.