Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
176 views
in Technique[技术] by (71.8m points)

Questions on representing data types in C using printf

I have a couple of examples of playing with floats, and doubles via printf in C that I think exhibit some strange / unintuitive behavior, and for each example I want to know

  1. Is my interpretation correct? (or have I made some bug in the code or my reasoning) and

  2. If my interpretation is correct, what is the reason for having the code behave in this way?

I am using C99 on a Windows 10 machine. I will try to avoid just making type-checking complaints =p

A) According to wikipedia, the max positive value for single precision variables is 2^127. However, overflow doesn't occur for floats until we reach over 2^128 - why is this? Is C on my computer somehow not using the IEEE standard?

float f;

// no overflow
f = 3.4e38;  // ~ 2^128
printf("
 b10 val: %e, hex val: %x, n bytes: %i", f, f, sizeof(f));

// overflow once we get past 2^128
f = 3.5e38;
printf("
 b10 val: %e, hex val: %x, n bytes: %i", f, f, sizeof(f));

> b10 val: 3.400000e+038, hex val: c0000000, n bytes: 4
> b10 val: 1.#INF00e+000, hex val: 00000000, n bytes: 4

B) I cannot print long doubles on my machine (they just appear as overflowed doubles), even though I believe I am using the correct format spec %Le, and the long doubles are properly represented as 16-byte (according to sizeof). Am I doing something wrong?

long double d_l;
  
d_l = pow(2, 1024);
printf("
 b10 val: %Le, hex val: %llx, n bytes: %i", d_l, d_l, sizeof(d_l));

d_l = 2;
printf("
 b10 val: %Le, hex val: %llx, n bytes: %i", d_l, d_l, sizeof(d_l));

> b10 val: 3.172897e-317, hex val: 61fdf0, n bytes: 16
> b10 val: 3.172897e-317, hex val: 61fdf0, n bytes: 16
question from:https://stackoverflow.com/questions/65850532/questions-on-representing-data-types-in-c-using-printf

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The posted code has multiple problems:

  • f = 3.4e38; You convert a floating point literal of type double to type float, you should probably write f = 3.4e38F; or f = powf(2, 127);

  • printf(" b10 val: %e, hex val: %x, n bytes: %i", f, f, sizeof(f)); multiple problems here:

    • you pass a float, which is actually converted to a double and passed as such but printf expects an unsigned int for %x: This has undefined behavior. The output is meaningless.
    • you pass the value of sizeof(f), which has type size_t, but printf expects an int for %i: This has undefined behavior, but unlikely to cause problems.
  • f = 3.5e38; this is a conversion of a literal of type double to a float, but in this case the value of the double exceeds the range of type float. This has undefined behavior. It seems to produce INF but this is not guaranteed by the C Standard. If you wrote f = 3.5e38F; the compiler would probably detect the overflow as the literal exceeds the range of its defined type.

  • printf(" b10 val: %e, hex val: %x, n bytes: %i", f, f, sizeof(f)); same problems as above

  • d_l = pow(2, 1024); the computation is performed as double. You should probably instead use the function powl to compute as long double.

  • printf(" b10 val: %Le, hex val: %llx, n bytes: %i", d_l, d_l, sizeof(d_l)); same problems as above. Passing a long double for %llx has undefined behavior.

  • d_l = 2; no problem here!

  • printf(" b10 val: %Le, hex val: %llx, n bytes: %i", d_l, d_l, sizeof(d_l)); see above.

You cannot really draw any conclusions from the output, the behavior is undefined for multiple reasons.

Here is a modified version:

#include <math.h>
#include <stdio.h>

char *tohex(char *buf, const void *val, size_t n) {
    // convert to hex in little endian format
    char *p = buf;
    const unsigned char *src = val;
    while (n --> 0) {
        p += sprintf(p, "%02X", src[n]);
    }
    return buf;
}

int main() {
    char buf[33];
    float f;
    double d;
    long double d_l;

    // no overflow
    f = 3.4e38;  // ~ 2^128
    printf("b10 val: %E, %A, hex val: %s, n bytes: %i
",
           f, f, tohex(buf, &f, sizeof(f)), (int)sizeof(f));

    // overflow once we get past 2^128
    f = 3.5e38;
    printf("b10 val: %E, %A, hex val: %s, n bytes: %i
",
           f, f, tohex(buf, &f, sizeof(f)), (int)sizeof(f));

    d = 3.4e38;  // ~ 2^128
    printf("b10 val: %E, %A, hex val: %s, n bytes: %i
",
           d, d, tohex(buf, &d, sizeof(d)), (int)sizeof(d));

    d = 3.5e38;
    printf("b10 val: %E, %A, hex val: %s, n bytes: %i
",
           d, d, tohex(buf, &d, sizeof(d)), (int)sizeof(d));

    d_l = powl(2, 1024);
    printf("b10 val: %LE, %LA, hex val: %s, n bytes: %i
",
           d_l, d_l, tohex(buf, &d_l, sizeof(d_l)), (int)sizeof(d_l));

    d_l = 2;
    printf("b10 val: %LE, %LA, hex val: %s, n bytes: %i
",
           d_l, d_l, tohex(buf, &d_l, sizeof(d_l)), (int)sizeof(d_l));

    return 0;
}

Output for OS/X 64-bit:

b10 val: 3.400000E+38, 0X1.FF933CP+127, hex val: 7F7FC99E, n bytes: 4
b10 val: INF, INF, hex val: 7F800000, n bytes: 4
b10 val: 3.400000E+38, 0X1.FF933C78CDFADP+127, hex val: 47EFF933C78CDFAD, n bytes: 8
b10 val: 3.500000E+38, 0X1.074F8C4D3CD7BP+128, hex val: 47F074F8C4D3CD7B, n bytes: 8
b10 val: 1.797693E+308, 0X8P+1021, hex val: 00000001041843FF8000000000000000, n bytes: 16
b10 val: 2.000000E+00, 0X8P-2, hex val: 00000001041840008000000000000000, n bytes: 16

Output for OS/X 32-bit:

b10 val: 3.400000E+38, 0X1.FF933CP+127, hex val: 7F7FC99E, n bytes: 4
b10 val: INF, INF, hex val: 7F800000, n bytes: 4
b10 val: 3.400000E+38, 0X1.FF933C78CDFADP+127, hex val: 47EFF933C78CDFAD, n bytes: 8
b10 val: 3.500000E+38, 0X1.074F8C4D3CD7BP+128, hex val: 47F074F8C4D3CD7B, n bytes: 8
b10 val: 1.797693E+308, 0X8P+1021, hex val: BFF87978000043FF8000000000000000, n bytes: 16
b10 val: 2.000000E+00, 0X8P-2, hex val: BFF87978000040008000000000000000, n bytes: 16

The difference comes from the 48 padding bits in the long double representation: long double only has 80 bits on this architecture, the 48 bits in the high order bytes (12 hex digits) are meaningless.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...