2011-01-20 13 views
5

Cuando ejecuto el siguiente código C en una máquina Intel ...Coaccionar flotador en unsigned char en ARM vs Intel

float f = -512; 
unsigned char c; 

while (f < 513) 
{ 
    c = f; 
    printf("%f -> %d\n", f, c); 
    f += 64; 
} 

... la salida es la siguiente:

-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 

Sin embargo, cuando ejecuto el mismo código en un dispositivo ARM (en mi caso un iPad), los resultados son bastante diferentes:

-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 

Como se puede imaginar, esto tipo de diferencia puede introducir horribles errores en proyectos multiplataforma. Mis preguntas son:

  1. que estaba equivocado suponer que coaccionar a un flotador en un unsigned char produciría los mismos resultados en todas las plataformas?

  2. ¿Podría ser un problema de compilación?

  3. ¿Hay una solución elegante?

+2

No estoy muy bien informado sobre esto, pero mi primera reacción es que es una mala idea tratar de forzar un flotador en un char sin signo en cualquier plataforma en cualquier arquitectura. –

+1

No estoy de acuerdo contigo, Brian. La precisión no es un problema aquí, pero el rendimiento sí lo es. Estoy usando la naturaleza de "envoltura" del carácter sin signo para mantenerme dentro de los límites de 0 a 255. Esto es, AFAIK (y he leído en varios artículos) no es una técnica poco común. – zmippie

+1

@Zmippie: el comportamiento que está viendo es la naturaleza de "saturación", que es bastante común en, por ejemplo, Conjuntos de instrucciones SIMD. – rwong

Respuesta

7

El estándar C no tiene reglas muy estrictas para lo que estás tratando de hacer. Aquí está el párrafo en cuestión, de la Sección 6.3.1 operandos aritméticos (específicamente la Sección 6.3.1.4 flotante real y el número entero):

Cuando un valor finito de tipo flotante verdadera se convierte en un tipo entero otra que _Bool, la parte fraccionaria se descarta (es decir, el valor se trunca hacia cero). Si el valor de la parte integral no puede representarse mediante el tipo entero, el comportamiento no está definido.

Incluso hay una nota más específica sobre el caso exacto que estás preguntando por:

La Venta en Saldo operación que se realiza cuando un valor de tipo entero se convierte en tipo sin signo no tiene por qué llevarse a cabo cuando un valor de tipo flotante real se convierte a tipo sin signo. Por lo tanto, el rango de valores flotantes reales portátiles es (−1, Utype_MAX+1).

UtypeMAX+1 para su caja es 256. Sus casos no coincidentes son todos números negativos. Después del truncamiento, siguen siendo negativos y están fuera del rango (-1, 256), por lo que están firmemente en la zona de "comportamiento indefinido". Incluso algunos de los casos coincidentes que ha mostrado, donde el número de punto flotante es mayor o igual que 256, no se garantiza que funcionen; solo tiene suerte.

Las respuestas a sus preguntas numeradas, por lo tanto:

  1. Sí, que estaban equivocados.
  2. Es un problema de compilación en el sentido de que sus diferentes compiladores dan resultados diferentes, pero dado que están permitidos por la especificación, realmente no lo llamaría culpa del compilador.
  3. Depende de lo que quiera hacer: si puede explicarlo mejor, es casi seguro que alguien de la comunidad SO pueda ayudarlo.
+0

Exactamente. Este es un comportamiento indefinido, claro y simple. –

+0

Gracias por las respuestas chicos (comentario de Stephen Canon a mi propia respuesta a continuación). Probablemente coloque el valor flotante en un rango razonable antes de la coacción, ya que de sus comentarios entiendo que es una mala práctica confiar en un "comportamiento indefinido". – zmippie

1

Voy a responder a 3 en mi propia pregunta, pero no lo marcaré como la respuesta aceptada. El truco parece ser un molde simple en la coerción:

c = (char) f; 

Utilizando (int) o (corta) que funciona también. Todavía estoy interesado en descubrir dónde está la causa de este problema: compilador o procesador.

+2

Tenga en cuenta que esto no es en realidad una solución portátil. Tiene un comportamiento indefinido tan pronto como excede el rango representable del tipo de destino para cualquier conversión de flotante a entero. Si realmente quiere ** garantizar ** el comportamiento multiplataforma, puede hacer algo como 'c = f < 0 ? 0 : f > UCHAR_MAX? UCHAR_MAX: f; ', pero incluso eso no define el comportamiento de los NaN. –

-1

El problema específico con el que está lidiando parece endiablado para mí. Intente reemplazar una u otra implementación con c = *((char *)&f + sizeof(float) - 1); o algo similar para obtener el último byte del flotante, y vea si coincide con el resultado para la otra plataforma.

En general, el comportamiento dependerá de la capacidad de endianness, longitud de palabra y coma flotante del procesador, y cómo el compilador apunta a eso. ARM es bi-endian, por lo que podría o no coincidir con el orden de bytes IA. Parece que tampoco hay garantía general de que una implementación de C sea compatible con el mismo formato de punto flotante que otro: Fixed-size floating point types.

¿Está utilizando esto en el código de producción? Miraría muy bien por qué esto tiene que hacerse. Es probable que uno u otro tipo no se use como se pretendía. Las soluciones no van a ser elegantes.

+1

Endianness no afectará este problema. Además, el procesador de iPad y los chips de Intel son ambos de poca monta. –