| [ | Date | | | 2026-08-31 21:25 -0400 | ] |
After years of barely acknowledging the existence of a syntax for hexadecimal floating point literals, I accidentally printed one, through the use of printf with a percent sign in the wrong location, resulting in %a within my format string.
I understand that hex floats exists to provide an unambiguous and relatively terse way of expressing exact floating point values, where a decimal syntax would need expertise and numerous digits to retain a specific value after going through parsing and printing. Perhaps there are other reasons, but regardless I never felt the need to use that syntax.
My example printed what was supposed to be the current year, 2026, as 0xf.d4p+7. This was pretty confusing, since I expected to see 2026, and also since this ended up in the middle of a string I had meant to output literally.
The Linux man page1 for printf(3) says, about conversion specifier a:
For
aconversion, the double argument is converted to hexadecimal notation (using the letters abcdef) in the style[-]0xh.hhhhp±d[…]. There is one hexadecimal digit before the decimal point, and the number of digits after it is equal to the precision. The default precision suffices for an exact representation of the value if an exact representation in base 2 exists and otherwise is sufficiently large to distinguish values of type double. The digit before the decimal point is unspecified for nonnormalized numbers, and nonzero but otherwise unspecified for normalized numbers.
From this I get that:
0xf.d4p+7 should be read as 0xf.d4 , where the dot between f and d is a decimal point. Shifting left by two hex digits (eight bits) so that the hex number becomes an integer, the expression becomes 0xfd4 = 0xfd4 = 0x7ea = 2026. This checks out.
In the output of this implementation of printf, there is always exactly one hex digit before the decimal point (the first h in 0xh.hhhh), which takes any value within 1 .. f for normalized numbers. I would have expected the digit to be fixed, perhaps to 1. And, indeed, implementations2 differ in the value they pick for the first digit:
$ printf '%a\n' 2026 ## Bash 5.2.21
0xf.d4p+7
$ perl -E 'printf "%a\n", 2026' ## Perl v5.32.1
0x1.fa8p+10
$ ./a.out ## C; GCC 10.2.1, GLIBC 2.31
0x1.fa8p+10
I don’t know why different implementation make different choices here. I also have not checked how much the Bash and Perl implementations of printf rely on the underlying libc printf.
Hex float literals work consistently for input in the three languages I tested above for output; normalizing for their preferred format:
# Bash
$ printf '%a\n' 0x1.fa8p+10 0xf.d4p+7
0xf.d4p+7
0xf.d4p+7
# Perl
$ perl -E 'printf "%a\n%a\n", 0x1.fa8p+10, 0xf.d4p+7'
0x1.fa8p+10
0x1.fa8p+10
# C
printf("%a\n%a\n", 0x1.fa8p+10, 0xf.d4p+7);
0x1.fa8p+10
0x1.fa8p+10
Quick links: