views:

385

answers:

4

How can a hexadecimal floating point constant, as specified in C99, be printed from a array of bytes representing the machine representation of a floating point value? e.g. given

union u_double
{
    double  dbl;
    char    data[sizeof(double)];
};

An example hexadecimal floating point constant is a string of the form

0x1.FFFFFEp127f

A syntax specification for this form of literal can be found on the IBM site, and a brief description of the syntax is here on the GCC site.

The printf function can be used to do this on platforms with access to C99 features in the standard library, but I would like to be able to perform the printing in MSVC, which does not support C99, using standard C89 or C++98.

+4  A: 

printf manual says:

a,A

(C99; not in SUSv2) For a conversion, the double argument is converted to hexadecimal notation (using the letters abcdef) in the style [-]0xh.hhhhp�d; for A conversion the prefix 0X, the letters ABCDEF, and the exponent separator P is used. 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 non-normalized numbers, and non-zero but otherwise unspecified for normalized numbers.

dfa
Thanks, but I need to perform this printing in standard C++98 or C89, without access to a C99 style printf ;-(+1.
grrussel
then you should add a specific request in your question :)
dfa
Since many standard libraries have source available, you might grab the formatting code from vaprintf and use that for your output.
plinth
Links to a specific standard libs source code online?
grrussel
plinth
and oddly enough, one of the last names on the source is a guy I went to college with. The quality of code is not up to his usual standards.
plinth
+1  A: 

You can use frexp() which is in math.h since at least C90 and then do the conversion yourself. Something like this (not tested, not designed to handle boundaries like NaN, infinities, buffer limits, and so on)

void hexfloat(double d, char* ptr)
{
    double fract;
    int    exp = 0;

    if (d < 0) {
        *ptr++ = '-';
        d = -d;
    }
    fract = frexp(d, &exp);

    if (fract == 0.0) {
        strcpy(ptr, "0x0.0");
    } else {
        fract *= 2.0;
        --exp;
        *ptr++ = '0';
        *ptr++ = 'x';
        *ptr++ = '1';
        fract -= 1.0;
        fract *= 16.0;
        *ptr++ = '.';
        do {
            char const hexdigits[] = "0123456789ABCDEF";
            *ptr++ = hexdigits[(int)fract]; // truncate
            fract -= (int)fract;
            fract *= 16;
        } while (fract != 0.0);
        if (exp != 0) {
            sprintf(ptr, "p%d", exp);
        } else {
            *ptr++ = '\0';
        }
    }
}
AProgrammer
A: 
#include <stdint.h>
#include <stdio.h>

int main(void)
{
    union { double d; uint64_t u; } value;
    value.d = -1.234e-5;

    // see http://en.wikipedia.org/wiki/Double_precision
    _Bool sign_bit = value.u >> 63;
    uint16_t exp_bits = (value.u >> 52) & 0x7FF;
    uint64_t frac_bits = value.u & 0xFFFFFFFFFFFFFull;

    if(exp_bits == 0)
    {
        if(frac_bits == 0)
            printf("%s0x0p+0\n", sign_bit ? "-" : "");
        else puts("subnormal, too lazy to parse");
    }
    else if(exp_bits == 0x7FF)
        puts("infinity or nan, too lazy to parse");
    else printf("%s0x1.%llxp%+i\n",
        sign_bit ? "-" : "",
        (unsigned long long)frac_bits,
        (int)exp_bits - 1023);

    // check against libc implementation
    printf("%a\n", value.d);
}
Christoph
Is this not using all the bits in the double to create frac_bits?
grrussel
@grussel: no, only the lower 52 ones
Christoph
A: 

This might be an "outside the box" answer, but why not convert the double to a string using sprintf, then parse the string for the mantissa and exponent, convert those to

e.g., something like:

char str[256];
long long a, b, c;
sprintf(str, "%e", dbl);
sscanf(str, "%d.%de%d", &a, &b, &c);
printf("0x%x.%xp%x", a, b, c);

I'm sure you'd have to modify the formats for sprintf and sscanf. And you'd never get a first hex digit between A and F. But in general, I think this idea should work. And it's simple.

A better way would be to find an open source library that implements this format for printf (e.g., newlib, uClibc?) and copy what they do.

Brendan Dowling