0

I am running a test code for learning C. However I am curious regarding representation of negative numbers n hexadecimal, for which I have written a test-code. To my surprise, by running that test-code I am receiving only zero

    union unin
    {
        char chrArray[4];
        float flotVal;
    }uninObj;

    uninObj.flotVal = -25;

    printf("%x %x %x %x",uninObj.chrArray[0], uninObj.chrArray[1], /
           uninObj.chrArray[2], uninObj.chrArray[3]);
    printf("\n Float in hex: %x",uninObj.flotVal);
    return 0;
delve123
  • 47
  • 5
  • 1
    Hexadecimal in negative? nops, of course you can use `printf("-%x -%x -%x -%x"` but is quite strange to see an hexadecimal with `-`. Anyway using `%u` with a `float` is undefined behaviour. – David Ranieri Jun 26 '19 at 18:46
  • According to [cppreference.com](https://en.cppreference.com/w/cpp/io/c/fprintf), the `%x` specifier _converts an unsigned integer into hexadecimal representation hhhh._ So it looks like a type-conversion may be happening. – 001 Jun 26 '19 at 18:50
  • @KeineLust I agree it will be very strange. However my question was much on the representation part in hexadecimal. On running the above program I am getting value as '0' but I was expecting something between 0 to F (Naturally ignoring -) – delve123 Jun 26 '19 at 18:51
  • @delve123, you have no sound justification for *any* particular expectation of the output of a program that executes the code presented. Its behavior is *undefined*. That means what it says -- it is not a synonym for "strange" or "faulty", though strange behavior indeed might be observed. – John Bollinger Jun 26 '19 at 18:54
  • Zero seems strange, but your union will overlay the floating point representation of -25 on 4 bytes, so the result would be machine dependent. So what processor and OS is this? I get `0` `0` `ffffffc8` `ffffffc1` – Deepstop Jun 26 '19 at 18:54
  • This is probably going to be dependent on your specific architecture and operating system. When I run it on Linux, I also get `0` in the second `printf`. However, when I run it on MacOS, I get `120a8`. – anonmess Jun 26 '19 at 18:54
  • 2
    Aside: a syntax error: `/` line continuation should be back slash, but it is not needed anyway. – Weather Vane Jun 26 '19 at 19:01
  • @Deepstop I7-8gen Windows-10. I am also getting the same output as yours – delve123 Jun 26 '19 at 19:02
  • @delve123 I'm using CentOS-7-64. With format code %02X I at least get the tidier output of `00 00 C8 C1`. – Deepstop Jun 26 '19 at 19:09
  • For learning, take a look at [hexadecimal floating constant in C](https://stackoverflow.com/questions/4825824/hexadecimal-floating-constant-in-c) - there is such an animal. Also, in your case, if you simply want to check each byte in the `union` and output the value in hex with the appropriate sign in front, you can loop over the bytes, check if the `char` value would be less than zero, and output a negative sign in front of the absolute value output in hex. Kinda wonky, but I can see experimenting. Also, the format specifier would be `"%hhx"` for each bytes as hex. – David C. Rankin Jun 26 '19 at 19:37

3 Answers3

4

Passing float to a specifier expecting unsigned int is undefined behaviour.

Furthermore, the unsigned int expected by %x is not guaranteed to be the same size as float. So the attempt to trick printf that way may or may not "work".

Anyway, for a variadic function such as printf the compiler will promote an argument of type float to double, so that may be the reason why you (and I) get 0 output.

On my system
sizeof(double) is 8.
sizeof(unsigned int) is 4.

And if you look at the bytes output for that part of the union, the first two are 0. So having passed 8 bytes to the function instead of the 4 expected by %x, the data is aligned in a way that %x is getting four 0 value bytes.

Weather Vane
  • 33,872
  • 7
  • 36
  • 56
  • I understand form your explanation as the **float** is being promoted to **double** . However it doesn't stops the **printf** to represent the contents in hexadecimal. correct ? – delve123 Jun 26 '19 at 19:15
  • The decimal value (minus) `25` only needs a few of the m.s. bits in the significand to represent it, the rest are `0`. So in the `double` promotion, there are at least four `0` bytes compared witht the two for a `float`. If the values are little-endian, the `%x` format will be looking at four `0` bytes, not at the sign, exponent, and m.s bits of the significand. – Weather Vane Jun 26 '19 at 19:18
  • 1
    The mismatch between `printf` directive and the the type of the corresponding argument produces undefined behavior quite independently of the sizes of any of the types involved. – John Bollinger Jun 26 '19 at 19:20
  • @JohnBollinger yes, as the first sentence of the answer. The rest is by way of explanation as to *why* `0` is output, in this particular case. Undefined behaviour does not necessarily mean random. – Weather Vane Jun 26 '19 at 19:22
  • > *"is not guaranteed to be the same size as float"*. It has nothing to do with size; the `%x` conversion specifier requires an `unsigned int`, which is not the same **type** as a `float`. It's a run-time type mismatch situation that results in undefined behavior. – Kaz Jun 26 '19 at 19:24
  • @Kaz `printf` does not *care* what the type is, but the compiler tries to afford some protection. If there are 4 bytes presented to a specifier expecting 4 bytes, then it will interpret those 4 bytes in the way it is told to. – Weather Vane Jun 26 '19 at 19:26
  • @JohnBollinger the first sentence isn't worded carefully enough, I will edit it thank you. – Weather Vane Jun 26 '19 at 19:28
  • `printf` absolutely cares what the type is. Inside `printf`, the `%x` argument is extracted from the variable argument list using something similar to `va_arg(vl, unsigned)`. If that is matched by a `double` argument, it's doing undefined type punning. – Kaz Jun 26 '19 at 20:42
  • @Kaz yes, but the processor is not governed by the C standard. The result may be undefined in the sense that the C standard is written, but the processor carries on regardless. There are few cats painted green, or nasal demons, because most of the useless computers which did that have been thrown into the sea. – Weather Vane Jun 26 '19 at 20:46
  • ... suppose my C program has some slightly odd behaviour in one part and occasional crashes which happen in another part. I find a bug and fix it, the crashes stop and so does the seemingly unrelated odd behaviour elsewhere. I want to assure myself that the two were related and explain to myself that I really have dealt with the problem, not simply moved it somewhere else. Saying "oh that is undefined" is not helpful. I want to know *exactly* what the processor has done. In the same way, this answer attempts to explain why the output was `0`. – Weather Vane Jun 26 '19 at 20:56
  • The situation is diagnosable; the program might fail to translate with a diagnostic message. E.g. GCC diagnoses mismatches between conversion specifiers and arguments in `printf`. The warning can be turned into a fatal error with a command line option. – Kaz Jun 26 '19 at 21:08
1

On my CentOS 7 Intel 64 architecture, sizeof(float) is 4. So little endian result that I see on my test of 00 00 C8 C1 is a negative number. The Intel single precision floating point representation is:

1 bit sign
8 bit exponent
23 bit significand (with the first 1 bit implied)

As the Intel architecture is little-endian, the floating point value for 00 00 C8 C1 is 1100 0001 1100 1000 0000 0000 0000 0000. The first 1 means the number is negative. The next 8 bits, 10000011 (Decimal 131), are the exponent, and the next 4 bits 1001, with the implied 1 bit 11001, is the number 25 shifted right 4 bits. The exponent of 131 is offset from 127 by 4, which is the number of bits that 1.1001 is shifted left to get back to 25.

On a 64 bit representation, the exponent is 11 bits, and the exponent offset is 1023. So you would expect the number to be 1 (negative sign), Decimal 1027 in 11 bits 100 0000 0011, then 25 decimal as 1001 with the implied leading 1 bit (as in the single precision version), then all zeroes which together is C0 39 00 00 , 00 00 00 00. You can see that the last 4 bytes are all zeros. But this is still little-endian, so as a 64 bit number it would look like 00 00 00 00 00 00 39 C0. So you are getting all zeros if you print the first 4 bytes.

You would see non-zero values from your program either by (a) Specifying an 8 character array in the declaration and printing all 8 (and you would see two bytes with 39 C0), or (b) using a value other than -25 in your test that requires more binary digits to represent like a large prime number or an irrational number (as suggested by @David C. Rankin).

Checking sizeof(float) would determine what your floating point size (in bytes) and I would expect you to see it as 8, because you are seeing zeroes and not C8 C1 like I do.

Deepstop
  • 3,627
  • 2
  • 8
  • 21
  • For fun you could enter negative PI as the `float` value, e.g. `-3.14159` (or just PI) if you wanted to ensure all bytes had value. – David C. Rankin Jun 26 '19 at 20:56
  • Right. Anything with more than 20 significant binary digits would push bits into the lower 32 bits which would show up the in the result. All the talk about printf and hex representations aside, the original question relates to floating point representations in the processor that the code is being executed on and not much else. It takes me back to my good old days of assembly language programming although most of the processors I worked on back then didn't have floating point operations. – Deepstop Jun 30 '19 at 11:26
0

First of all, this statement is flat-out wrong:

printf("\n Float in hex: %x",uninObj.flotVal);

%x expects its corresponding argument to be unsigned int, and passing an argument of a different type (as you do here) results in undefined behavior - the output could literally be anything.

As of C99, you can use the %a or %A specifiers to print a hexadecimal representation of the floating-point value ([+|-]x.xxxx..., where each x is a hex digit). This is not the same thing as the value's binary representation in memory, though.

For the union, I'd suggest you use unsigned char for chrArray and use %hhx to print out each byte:

union unin
{
    unsigned char chrArray[sizeof (float)];
    float flotVal;
}uninObj;

printf("%hhx %hhx %hhx %hhx",uninObj.chrArray[0], uninObj.chrArray[1], 
       uninObj.chrArray[2], uninObj.chrArray[3]);    

The hh length modifier in %hhx says that the corresponding argument has type unsigned char, rather than the unsigned int %x usually expects.

John Bode
  • 119,563
  • 19
  • 122
  • 198