1
 $ gcc --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 12.0.5 (clang-1205.0.22.9)
Target: x86_64-apple-darwin20.3.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
$ uname -a
Darwin MacBook-Air.local 20.3.0 Darwin Kernel Version 20.3.0: Thu Jan 21 00:07:06 PST 2021; root:xnu-7195.81.3~1/RELEASE_X86_64 x86_64

Code:

#include <stdio.h>

int main(void) {


    int i = 2;
    printf("int \"2\" as %%.128f:                  %.128f\n", i);

    printf("int \"2\" as %%.128lf:                 %.128lf\n", i);

    printf("int \"2\" as %%.128LF:                 %.128Lf\n", i);

    return 0;

}

Compile:

$ gcc floatingpointtypes.c

Execute:

$ ./a.out
int "2" as %.128f:                  0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
int "2" as %.128lf:                 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
int "2" as %.128LF:                 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

When binary of integer 2 is interpreted as IEEE-754 single precision (32 bit) or double precision (64 bit) floating point number format then it is a denormalized float (exponent bits are all 0s) and the resultant value in decimal is 2e-148.

Question:

Why does my code print 0s? Is it because C can't interpret denormalized floating point numbers to their correct value ?

Ankur Agarwal
  • 23,692
  • 41
  • 137
  • 208
  • 3
    You have undefined behavior because you are passing an `int` to `printf` where the format specifier expects `double` or `long double`. It's a variadic function so the conversion is not done for you. Try casting `i` to the appropriate type and you should get more reasonable behavior. – Nate Eldredge May 28 '21 at 07:33
  • @NateEldredge I understand C can handle value 2 represented as floating point. What I was trying to do was interpret integer 2 as different kinds of floating values of varying precision. I hope I am conveying what I am trying to do here. – Ankur Agarwal May 28 '21 at 07:38
  • No, I don't understand. If you're trying to get the bits of the integer 2 interpreted as a floating-point value, this is not the way to do it. It's not even the right size. And on x86-64, integers are passed in different registers than floating point values, so you're printing whatever happened to be in some register which is entirely unaffected by your argument `i`. – Nate Eldredge May 28 '21 at 07:40
  • @NateEldredge Yes thats exactly what I am trying to do. What is the best way to do it ? Also what is wrong about my code ? I know I can print 67 as %c or %d. Why can't I print int 2 as %lf ? – Ankur Agarwal May 28 '21 at 07:43
  • 4
    Perhaps you instead want to do something like `double d; uint64_t u = 2; memcpy(&d, &u, 8);`. – Nate Eldredge May 28 '21 at 07:44
  • 1
    Variadic functions don't have prototypes, so your arguments undergo the [default argument promotions](https://stackoverflow.com/questions/1255775/default-argument-promotions-in-c-function-calls) regardless of what the format string says. `char` and `short` are promoted to `int`, and `%c` and `%d` both expect `int`. But `int` will not be promoted to `double`. – Nate Eldredge May 28 '21 at 07:47
  • Keep in mind also that `float` is default promoted to `double`, and in fact the printf format specifiers `%f` and `%lf` are synonyms and both expect `double` (different from the situation for scanf). – Nate Eldredge May 28 '21 at 08:00
  • Looks like you're trying to do `printf("%f", *(double*)&i); /* UB here */`. – pmg May 28 '21 at 08:06
  • Note: the `printf()` conversions `"%f"` and `"%lf"` are equivalent (unlike `scanf()` conversions). – pmg May 28 '21 at 08:09
  • 2
    @NateEldredge: Variadic functions have prototypes. They end with `...`, per C 2018 6.5.2.2 6. A *prototype* is the function declaration in the form with a parameter lit that includes types (for the named parameters). If the declaration is `void foo(int x, ...);`, it is a prototype. The C standard does not say that the `int x` is a prototype and the `...` is not; the whole declaration is a prototype. – Eric Postpischil May 28 '21 at 11:07

2 Answers2

6

If you want to convert the 'bits' of an int to a floating point type, the easiest way to do that is with a union:

#include <stdio.h>
#include <stdint.h>

union u32 {
    int32_t     i;
    float       f;
};

union u64 {
    int64_t     i;
    double      d;
};

int main() {
    union u32 a;
    union u64 b;

    a.i = 2;
    b.i = 2;

    printf("%g\n", a.f);
    printf("%g\n", b.d);
    return 0;
}

resulting output:

2.8026e-45
9.88131e-324

What is likely happening with your code is that you're using a system which passes arguments in different registers depending on the type (integer registers for int values and floating point registers for fp types). So the call puts 2 into an integer register, but the value getting printed by %f is whatever value happens to be in the first floating point register used for arguments -- probably 0 as no fp code has run before the call.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
2

In addition to undefined behavior, you overestimated the size of the float because the %f specifier expects double-precision floating-point numbers. The exact value of the number printed is 2^-1073. The first non-zero happens 324 decimal digits after the decimal place, but you were only printing the first 128 digits. Try:

#include <stdio.h>

int main(void)
{
    long long i = 2;
    printf("long long \"2\" as %%.1073f:                  %.1073f\n", *(double *) &i);
    return 0;
}

Output:

long long "2" as %.1073:                  0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000988131291682493088353137585736442744730119605228649528851171365001351014540417503730599672723271984759593129390891435461853313420711879592797549592021563756252601426380622809055691634335697964207377437272113997461446100012774818307129968774624946794546339230280063430770796148252477131182342053317113373536374079120621249863890543182984910658610913088802254960259419999083863978818160833126649049514295738029453560318710477223100269607052986944038758053621421498340666445368950667144166486387218476578691673612021202301233961950615668455463665849580996504946155275185449574931216955640746893939906729403594535543517025132110239826300978220290207572547633450191167477946719798732961988232841140527418055848553508913045817507736501283943653106689453125
TheNumberOne
  • 404
  • 3
  • 13
  • 1
    You have a good point, except the number is not `2^-1023 + 2^-1074`. There is no implicit leading 1 for denormal numbers. You might also define `i` as a `long long` and modify the `%%.128f` as `%%.1073f` :) – chqrlie May 28 '21 at 09:13
  • oh interesting, i didnt know that denormal numbers did not have the implicit 1 :3. – TheNumberOne May 28 '21 at 09:24
  • it is undefined Behaviour too. You access past the memory occupied by the int. What is in that memory is unpredictable. The example makes no sense. – 0___________ May 28 '21 at 09:31
  • @0___________: `i` has type `long`, which is 64-bit on many platforms. Yet `long long` would be a safer choice. – chqrlie May 28 '21 at 09:46
  • @chqrlie it was `int` when I started to write this comment. He should add an edit not edit the question invalidating the comments. – 0___________ May 28 '21 at 09:49
  • *she :p Also, sorry about that. I did not see your comment when I edited out the int – TheNumberOne May 28 '21 at 09:50
  • @0___________: editing questions is a problem, refining answers not as much (IMHO). – chqrlie May 28 '21 at 09:58