开发者

Coercing float into unsigned char on ARM vs. Intel

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.00开发者_C百科0000 -> 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:

  1. Was I wrong to assume that coercing a float into an unsigned char would yield the same results on all platforms?

  2. Could his be a compiler issue?

  3. 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:

  1. Yes, you were wrong.
  2. It's a compiler issue in the sense that your different compilers give different results, but since they're allowed to by the spec, I wouldn't really call that the compiler's fault.
  3. It depends on what you want to do - if you can explain that better, someone on SO community is almost certain to be able to help you out.


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.


The specific issue you're dealing with looks like endianness to me. Try replacing one or the other implementation with c = *((char *)&f + sizeof(float) - 1); or something similar to get the last byte of the float, and see if it matches the result for the other platform.

In general, the behavior will depend on endianness, word length and floating point capabilities of the processor, and how the compiler targets that. ARM is bi-endian, so it might or might not match IA byte ordering. It seems there's also no general guarantee that one C implementation supports the same floating point format as another: Fixed-size floating point types .

Are you using this in production code? I'd look very hard at why this needs to be done. One or the other type is probably not being used as intended. Workarounds are not going to be elegant.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜