Search code examples
cfloating-pointhex

How to manually convert fprintf hexadecimal representation of floating point to decimal


The hexadecimal representation of a floating point number from fprintf and snprintf seems to take the form (using A as a format specifier): 0XH.HHHHP[+-]d , where the number of hexadecimal digits to the right of the decimal point is variable.

I've found at least one manual explaining this format:

https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/printf.3.html

  aA     The double argument is rounded and converted to hexadecimal notation in the 
         style [-]0xh.hhhp[+-]d, where the number of digits
         after the hexadecimal-point character is equal to the precision
         specification.  If the precision is missing, it is taken as
         enough to represent the floating-point number exactly, and no
         rounding occurs.  If the precision is zero, no hexadecimal-point
         character appears.  The p is a literal character `p', and the
         exponent consists of a positive or negative sign followed by a
         decimal number representing an exponent of 2.  The A conversion
         uses the prefix ``0X'' (rather than ``0x''), the letters
         ``ABCDEF'' (rather than ``abcdef'') to represent the hex digits,
         and the letter `P' (rather than `p') to separate the mantissa and
         exponent.

         Note that there may be multiple valid ways to represent floating-point 
         point numbers in this hexadecimal format.  For example,
         0x3.24p+0, 0x6.48p-1 and 0xc.9p-2 are all equivalent.  The format
         chosen depends on the internal representation of the number, but
         the implementation guarantees that the length of the mantissa
         will be minimized.  Zeroes are always represented with a mantissa
         of 0 (preceded by a `-' if appropriate) and an exponent of +0.

I don't understand this explanation. I believe I understand the general approach to converting floating point numbers to binary: include a sign bit, an exponent bit that refers to the location of the first significant digit in binary, and use the rest of the bits to represent the number to the right of the decimal in binary.

What I don't understand is how this is done with this format, for example:

What does the character to the left of the decimal point represent?

What does it mean when the decimal point is missing?

What do the characters to the right of the decimal point represent?

Why is the letter P always there?

Can anyone show me some examples of converting to/from this representation and a floating point number?


Solution

  • I've found at least one manual explaining this format…

    You should look to the C standard for information like this.

    What does the character to the left of the decimal point represent?

    In decimal, 34.5•100, 3.45•101, and .345•102, are the same number. In hexadecimal, 9A.B16•160, 9.AB16•161, .9AB16•162 are the same number. In the description of the style as [-]0xh.hhhhp±d, the h.hhhh part is just telling you that the formatting routine will format it with one hexadecimal digit to the left of the point (and adjust the exponent to match).

    What does it mean when the decimal point is missing?

    This is merely an aesthetic choice. When there are no digits in fraction positions, to the right of a decimal point, there is no need to have a decimal point to indicate where the fraction positions start. However, the %a format is mostly used to display the entire floating-point value, so we usually let all the digits appear and do not set the precision to zero.

    What do the characters to the right of the decimal point represent?

    They are digits in fraction positions, the same as in decimal notation. For example, the hexadecimal numeral 123.45616 represents 1•162 + 2•161 + 3•160 + 4•16−1 + 5•16−2 + 6•16−3.

    Why is the letter P always there?

    It stands for “power” and marks the start of the exponent. For output, we could suppress it if the exponent is zero. However, this output format is chosen to match the form of hexadecimal literals in source code, and, for those, we need “p” at least in integers to distinguish the hexadecimal integer literal 0x3 from the hexadecimal floating-point literal 0x3p0.