30 votos

La mitad de punto flotante de precisión en Java

Hay una biblioteca de Java en cualquier lugar que puede realizar cálculos en IEEE 754 de la mitad de precisión de los números o convertir a y de doble precisión?

Cualquiera de estos enfoques sería adecuado:

  • Mantener los números en la mitad-formato de precisión y calcular utilizando la aritmética de enteros y bit-cambiando (como Microflotado hace por simple y doble precisión)
  • Realizar todos los cálculos en base single o doble precisión, la conversión a/desde la mitad de la precisión para la transmisión (en cuyo caso lo que necesito es probado funciones de conversión.)

Edit: la conversión debe ser 100% exacto - allí son un montón de Nan, infinitos y subnormals en los archivos de entrada.


Relacionados con la pregunta, pero para JavaScript: Descomprimir la Mitad de Precisión Flota en Javascript

51voto

x4u Puntos 7436

Usted puede Utilizar Float.intBitsToFloat() y Float.floatToIntBits() a convertir a y desde los primitivos valores de coma flotante. Si usted puede vivir con truncadas de precisión (como opuesto al redondeo) la conversión debe ser posible implementar con unos pocos bits de turnos.

Ahora tengo que poner un poco más de esfuerzo en él y no resultó tan sencillo como esperaba al principio. Esta versión ya está probado y comprobado en todos los aspectos que se pueda imaginar y estoy muy seguro de que produce los resultados exactos para todos los posibles valores de entrada. Es compatible exacta de redondeo y los subnormales de conversión en cualquier dirección.

// ignores the higher 16 bits
public static float toFloat( int hbits )
{
    int mant = hbits & 0x03ff;            // 10 bits mantissa
    int exp =  hbits & 0x7c00;            // 5 bits exponent
    if( exp == 0x7c00 )                   // NaN/Inf
        exp = 0x3fc00;                    // -> NaN/Inf
    else if( exp != 0 )                   // normalized value
    {
        exp += 0x1c000;                   // exp - 15 + 127
        if( mant == 0 && exp > 0x1c400 )  // smooth transition
            return Float.intBitsToFloat( ( hbits & 0x8000 ) << 16
                                            | exp << 13 | 0x3ff );
    }
    else if( mant != 0 )                  // && exp==0 -> subnormal
    {
        exp = 0x1c400;                    // make it normal
        do {
            mant <<= 1;                   // mantissa * 2
            exp -= 0x400;                 // decrease exp by 1
        } while( ( mant & 0x400 ) == 0 ); // while not normal
        mant &= 0x3ff;                    // discard subnormal bit
    }                                     // else +/-0 -> +/-0
    return Float.intBitsToFloat(          // combine all parts
        ( hbits & 0x8000 ) << 16          // sign  << ( 31 - 15 )
        | ( exp | mant ) << 13 );         // value << ( 23 - 10 )
}

// returns all higher 16 bits as 0 for all results
public static int fromFloat( float fval )
{
    int fbits = Float.floatToIntBits( fval );
    int sign = fbits >>> 16 & 0x8000;          // sign only
    int val = ( fbits & 0x7fffffff ) + 0x1000; // rounded value

    if( val >= 0x47800000 )               // might be or become NaN/Inf
    {                                     // avoid Inf due to rounding
        if( ( fbits & 0x7fffffff ) >= 0x47800000 )
        {                                 // is or must become NaN/Inf
            if( val < 0x7f800000 )        // was value but too large
                return sign | 0x7c00;     // make it +/-Inf
            return sign | 0x7c00 |        // remains +/-Inf or NaN
                ( fbits & 0x007fffff ) >>> 13; // keep NaN (and Inf) bits
        }
        return sign | 0x7bff;             // unrounded not quite Inf
    }
    if( val >= 0x38800000 )               // remains normalized value
        return sign | val - 0x38000000 >>> 13; // exp - 127 + 15
    if( val < 0x33000000 )                // too small for subnormal
        return sign;                      // becomes +/-0
    val = ( fbits & 0x7fffffff ) >>> 23;  // tmp exp for subnormal calc
    return sign | ( ( fbits & 0x7fffff | 0x800000 ) // add subnormal bit
         + ( 0x800000 >>> val - 102 )     // round depending on cut off
      >>> 126 - val );   // div by 2^(1-(exp-127+15)) and >> 13 | exp=0
}

He implementado dos pequeñas extensiones en comparación con el libro , porque el general la precisión de 16 bits flota es bastante baja, lo que podría hacer que el inherente a las anomalías de punto flotante formatos perceptibles visualmente comparación con las grandes tipos de punto flotante donde están generalmente no se nota debido a la suficiente precisión.

El primero de estos dos líneas en toFloat() función de:

if( mant == 0 && exp > 0x1c400 )  // smooth transition
    return Float.intBitsToFloat( ( hbits & 0x8000 ) << 16 | exp << 13 | 0x3ff );

Números de punto flotante en el rango normal del tamaño del tipo de adoptar el exponente y por lo tanto la precisión a la magnitud del valor. Pero esto no es un suave adopción, sucede en los pasos: cambiar a la siguiente mayor exponente resultados en la mitad de la precisión. La precisión de ahora sigue siendo el mismo para todos los valores de la mantisa hasta el siguiente salto a la siguiente más alto exponente. La extensión de código anterior hace que estas transiciones más suaves, al devolver un valor que está en el centro geográfico de la cubierta de 32 bits en coma flotante gama para este particular, la mitad de valor de tipo float. Cada media normal float valor se corresponde exactamente con 8192 32 bits en coma flotante valores. El valor devuelto se supone que ser exactamente en el medio de estos valores. Pero en la transición de la mitad de flotación exponente de la parte inferior 4096 valores tienen el doble de la precisión como la parte superior de 4096 valores y así cubrir un espacio de número que es sólo la mitad tan grande como en el otro lado. Todos estos 8192 32 bits en coma flotante valores se asignan a la misma la mitad de valor de tipo float, por lo que la conversión de un medio flotante de 32 bits y los resultados en el mismo medio float valor, independientemente de que de la 8192 intermedio de 32 bits de los valores fue el elegido. La extensión de ahora los resultados en algo así como un suave medio paso por un factor de sqrt(2) en la transición, como se muestra a la derecha de la imagen de abajo, mientras que la izquierda de la imagen se supone que es para visualizar el paso fuerte por un factor de dos, sin anti aliasing. Usted puede eliminar de forma segura estas dos líneas de código para obtener el comportamiento estándar.

covered number space on either side of the returned value:
       6.0E-8             #######                  ##########
       4.5E-8             |                       #
       3.0E-8     #########               ########

La segunda extensión se encuentra en la fromFloat() función de:

    {                                     // avoid Inf due to rounding
        if( ( fbits & 0x7fffffff ) >= 0x47800000 )
...
        return sign | 0x7bff;             // unrounded not quite Inf
    }

Esta extensión ligeramente extiende el rango de número de la mitad de flotación formato por el ahorro de algunos de los 32 bits de los valores de forma de obtener un ascenso hasta el Infinito. Los afectados son los valores a los que haya sido menor que Infinito sin redondeo y se convertiría en el Infinito sólo debido al redondeo. Usted puede eliminar de forma segura las líneas que se muestra arriba, si usted no desea que esta extensión.

He tratado de optimizar la ruta de acceso para los valores normales en la fromFloat() función tanto como es posible, que lo hizo un poco menos legible debido a la utilización de precalculadas y unshifted constantes. No puse tanto esfuerzo en 'toFloat ()", ya que no superan el rendimiento de una tabla de búsqueda de todos modos. Así que si la velocidad es realmente importante podría utilizar el toFloat() función sólo para llenar una estática de la tabla de búsqueda con 0x10000 elementos y que utilice esta tabla para la conversión real. Esto es aproximadamente 3 veces más rápido con un servidor x64 VM y cerca de 5 veces más rápido con el 86 cliente VM.

Pongo el código por medio de la presente en el dominio público.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X