Deutsch/German
2014-02-05

Decimal output of floating point numbers in C and C++

In short: It is Pain In The Ass and the C Standard Library is just unable to do it properly. :-(

Why?

The C Standard library offers the printf() function (whose functionality was copied into many programming languages). It has several so-called “format directives” to specify how to print a (numerical) data type.

The format directives can be augmented with a field width, that specifies how many characters should be printed, and a precision, that specifies the number of fractional digits. The format directives can be look like this: %10.3f which means: "3 fractional digits, 10 characters at all (incl. decimal point)". Smaller numbers will be padded with blanks at the left, but bigger numbers will exceed the specified field width without warning!

For floating point types there are 3 different format directives that generate decimal output of the value: %f, %e and %g. Unfortunately none of them is able to print a floating point number in a sensible way (which means: without loss of precision, but in the shortest form, see below). (The directive %a which produce a hexadecimal output of a floating point value is completely sane and works without problems)

To show how complex, but still imperfect the format directives works, let's have a look into the man page printf(3):

f, F
The double argument is rounded and converted to decimal notation in the style [-]ddd.ddd, where the number of digits after the decimal-point character is equal to the precision specification. If the precision is missing, it is taken as 6; if the precision is explicitly zero, no decimalpoint character appears. If a decimal point appears, at least one digit appears before it.
e, E
The double argument is rounded and converted in the style [-]d.ddddd where there is one digit before the decimal-point character and the number of digits after it is equal to the precision; if the precision is missing, it is taken as 6; if the precision is zero, no decimal-point character appears. An E conversion uses the letter E (rather than e) to introduce the exponent. The exponent always contains at least two digits; if the value is zero, the exponent is 00.
g, G
The double argument is converted in style f or e (or F or E for G conversions). The precision specifies the number of significant digits. If the precision is missing, 6 digits are given; if the precision is zero, it is treated as 1. Style e is used if the exponent from its conversion is less than -4 or greater than or equal to the precision. Trailing zeros are removed from the fractional part of the result; a decimal point appears only if it is followed by at least one digit.

What does “sensible output” mean?

  1. Each (finite) floating point number f represents an interval I of real numbers: all that real numbers that are rounded to f. There are several standardized rounding models („round to zero“, „round to nearest“ etc.) so the particular interval might differ a bit, depending on the rounding model.
  2. In this interval there is an infinite amount of rational numbers that have a finite decimal fraction representation.
  3. Each of these rational numbers represents the interval and hence the floating point number f.
  4. For each possible interval there is at least one “shortest” rational number; if there are more than one rational number with the same number of digits, chose the one with the smallest (absolute) value as unique representative of the floating point number f.
How can I get this unique representative for a given floating point number f? If anyone has ideas, algorithms or ready-to-use C or C++ libraries to get this number, please e-mail me.

Examples

nBits%a%.17g
000000000000000000000000000000000000000000000000000000000000000000x0p+00
0.100111111101110011001100110011001100110011001100110011001100110100x1.999999999999ap-40.10000000000000001
0.11100111111101111000110101001111110111110011101101100100010110100010x1.c6a7ef9db22d1p-40.111
+1001000000001001000000000000000000000000000000000000000000000000000x1.4p+310
10.11101000000001001000011100011010100111111011111001110110110010001100x1.438d4fdf3b646p+310.111000000000001
1234567890123456789001000011111001010110101010010101001100011001110101100011111000010x1.56a95319d63e1p+631.2345678901234567e+19

Lars H. Rohwedder, 2014-02-05

Feedback

After some searching I realize: On this topic they research since 40 years! The problem is more complex than I thought, especially when printing very huge/tiny numbers where the exponential form is the only sensible one.

Here are some papers and C++ source code I found:

Thanks a lot to all who gave me hints and links!
Lars H. Rohwedder, 2014-02-06