When I run the following C code on an Intel machine...
float f = -512;
unsigned char c;
while ( f < 513 )
{
c = f;
printf( "%f -> %d\n", f, c );
f += 64;
}
...the output is as follows:
-512.000000 -> 0
-448.000000 -> 64
-384.000000 -> 128
-320.000000 -> 192
-256.000000 -> 0
-192.000000 -> 64
-128.000000 -> 128
-64.000000 -> 192
0.000000 -> 0
64.000000 -> 64
128.000000 -> 128
192.000000 -> 192
256.000000 -> 0
320.000000 -> 64
384.000000 -> 128
448.000000 -> 192
512.000000 -> 0
However, when I run the same code on an ARM device (in my case an iPad), the results are quite different:
-512.000000 -> 0
-448.000000 -> 0
-384.000000 -> 0
-320.000000 -> 0
-256.000000 -> 0
-192.000000 -> 0
-128.000000 -> 0
-64.000000 -> 0
0.000000 -> 0
64.000000 -> 64
128.000000 -> 128
192.000000 -> 192
256.000000 -> 0
320.000000 -> 64
384.000000 -> 128
448.000000 -> 192
512.000000 -> 0
As you can imagine, this sort of difference can introduce horrible bugs in cross-platform projects. My questions are:
Was I wrong to assume that coercing a float into an unsigned char would yield the same results on all platforms?
Could his be a compiler issue?
Is there an elegant workaround?
The C standard doesn't have very hard rules for what you're trying to do. Here's the paragraph in question, from Section 6.3.1 Arithmetic operands (specifically Section 6.3.1.4 Real floating and integer):
When a finite value of real floating type is converted to an integer type other than
_Bool
, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined.
There's even a more specific footnote about the exact case you're asking about:
The remaindering operation performed when a value of integer type is converted to unsigned type need not be performed when a value of real floating type is converted to unsigned type. Thus, the range of portable real floating values is
(−1, Utype_MAX+1)
.
UtypeMAX+1
for your case is 256
. Your mismatched cases are all negative numbers. After the truncation, they're still negative and are outside the range (-1, 256), so they're firmly in the 'undefined behaviour' zone. Even some of the matching cases you've shown, where the floating point number is greater than or equal to 256
, aren't guaranteed to work - you're just getting lucky.
The answers to your numbered questions, therefore: