Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

  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?

like image 405
zmippie Avatar asked Jan 20 '11 20:01

zmippie


People also ask

Is char signed or unsigned arm?

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.)

Why do we need signed and 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.

What is an unsigned char?

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.


2 Answers

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.
like image 183
Carl Norum Avatar answered Sep 21 '22 18:09

Carl Norum


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.

like image 38
zmippie Avatar answered Sep 22 '22 18:09

zmippie