2010-01-06 5 views
5

¿Alguien tiene a mano los fragmentos de código para convertir un puerto IEEE 754 double a la inmediatamente inferior (resp. Superior) float, sin cambiar o asumir nada sobre el redondeo actual de la FPU modo?la conversión doble para flotar sin depender del modo de FPU redondeo

Nota: esta restricción probablemente implica no usar la FPU en absoluto. Supongo que la forma más sencilla de hacerlo en estas condiciones es leer los bits del doble en una longitud de 64 bits y trabajar con eso.

Se puede suponer el orden de bits de su elección para la simplicidad, y que el doble en cuestión está disponible a través del campo de la uni por debajo d:

union double_bits 
{ 
    long i; 
    double d; 
}; 

me gustaría tratar de hacerlo yo mismo pero estoy seguro Presentaría errores difíciles de notar para números desnormalizados o negativos.

+0

en sistemas glibc a encontrar un ieee754.h archivo de cabecera, que define las uniones de los tipos de coma flotante y una estructura de campo de bits, para que pueda trabajar con la mantisa y el exponente más fácil, lo siento pero no se puede dar verdadera código. – quinmars

Respuesta

3

Creo que las siguientes obras, pero voy a exponer mis suposiciones primera :

  • números de coma flotante se almacenan en formato IEEE-754 en su aplicación,
  • Sin desbordamiento,
  • Tiene nextafterf() disponible (está especificado en C99).

Además, lo más probable es que este método no sea muy eficiente.

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 

int main(int argc, char *argv[]) 
{ 
    /* Change to non-zero for superior, otherwise inferior */ 
    int superior = 0; 

    /* double value to convert */ 
    double d = 0.1; 

    float f; 
    double tmp = d; 

    if (argc > 1) 
     d = strtod(argv[1], NULL); 

    /* First, get an approximation of the double value */ 
    f = d; 

    /* Now, convert that back to double */ 
    tmp = f; 

    /* Print the numbers. %a is C99 */ 
    printf("Double: %.20f (%a)\n", d, d); 
    printf("Float: %.20f (%a)\n", f, f); 
    printf("tmp: %.20f (%a)\n", tmp, tmp); 

    if (superior) { 
     /* If we wanted superior, and got a smaller value, 
      get the next value */ 
     if (tmp < d) 
      f = nextafterf(f, INFINITY); 
    } else { 
     if (tmp > d) 
      f = nextafterf(f, -INFINITY); 
    } 
    printf("converted: %.20f (%a)\n", f, f); 

    return 0; 
} 

En mi máquina, imprime:

Double: 0.10000000000000000555 (0x1.999999999999ap-4) 
Float: 0.10000000149011611938 (0x1.99999ap-4) 
tmp: 0.10000000149011611938 (0x1.99999ap-4) 
converted: 0.09999999403953552246 (0x1.999998p-4) 

La idea es que estoy convirtiendo el valor double a un valor float — esto podría ser menor o mayor que el valor doble dependiendo de la modo de redondeo. Cuando se convierte de nuevo a double, podemos verificar si es menor o mayor que el valor original. Entonces, si el valor de float no está en la dirección correcta, nos fijamos en el siguiente número float del número convertido en la dirección del número original.

+0

Muchas gracias por este código. Poco a poco me fui convenciendo de que esta era la solución menos propensa a errores. Gracias por señalar 'nextafterf' también, eso es mucho mejor que en/decrementando los bits del' float' como si fuera un 'int'. Para aliviar el riesgo de que 'f + 1' sea igual a' f', ¿puedo escribir 'nextafterf (f, INFINITY)' en su lugar? –

+0

Acabo de leer las páginas man, el borrador estándar C, y lo probé, y parece que 'INFINITY' debería funcionar. –

+0

OK, he editado mi publicación. Gracias por el comentario. –

3

para hacer este trabajo con mayor precisión que acaba de volver a combinar la mantisa y el cheque de poco exponente esto:

http://www.mathworks.com/matlabcentral/fileexchange/23173

respecto

+0

Gracias. La función 'doubles2halfp' allí es tan complicada como me temía, pero al menos ya tiene la mitad de las constantes correctas, por lo que es un buen punto de partida. –

+0

Usaría el código dado como referencia y reescribiría un enfoque más simple, usando & >> seguido por o, y luego verificaría números muy pequeños y muy grandes. Tome el recuento de turnos y la posición de bits de un vistazo desde http://babbage.cs.qc.edu/IEEE-754/Decimal.html – stacker

2

He publicado el código para hacer esto aquí: https://stackoverflow.com/q/19644895/364818 y lo he copiado a continuación para su conveniencia.

// d is IEEE double, but double is not natively supported. 
    static float ConvertDoubleToFloat(void* d) 
    { 
     unsigned long long x; 
     float f; // assumed to be IEEE float 
     unsigned long long sign ; 
     unsigned long long exponent; 
     unsigned long long mantissa; 

     memcpy(&x,d,8); 

     // IEEE binary64 format (unsupported) 
     sign  = (x >> 63) & 1; // 1 
     exponent = ((x >> 52) & 0x7FF); // 11 
     mantissa = (x >> 0) & 0x000FFFFFFFFFFFFFULL; // 52 
     exponent -= 1023; 

     // IEEE binary32 format (supported) 
     exponent += 127; // rebase 
     exponent &= 0xFF; 
     mantissa >>= (52-23); // left justify 

     x = mantissa | (exponent << 23) | (sign << 31); 
     memcpy(&f,&x,4); 

     return f; 
    } 
+0

Gracias. La línea 'exponente & = 0xFF;' significa que cuando sea apropiado devolver '± FLT_MAX' o' ± inf', en su lugar se devuelve un 'float' con un exponente extraño (y los resultados negativos también están desactivados). –

Cuestiones relacionadas