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?
In most cases, char is unsigned on ARM (for performance reasons) and signed on other platforms. iOS differs from the normal convention for ARM, and char is signed by default. (In particular this differs from Android, where code compiled in the ndk defaults to unsigned char.)
Signed char and unsigned char both are used to store single character. The variable stores the ASCII value of the characters. For an example if 'A' is stored, actually it will hold 65. For signed char we need not to write the signed keyword.
unsigned char is a character datatype where the variable consumes all the 8 bits of the memory and there is no sign bit (which is there in signed char). So it means that the range of unsigned char data type ranges from 0 to 255.
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:
I'm going to answer to 3 in my own question but will not flag that as the accepted answer. The trick seems to be a simple cast in the coercion:
c = (char) f;
Using (int) or (short) works too. I'm still interested to find out where the cause of this problem lies: compiler or processor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With