double - programacion - punto flotante ieee 754 ejemplos
siguiente número de precisión doble IEEE superior/inferior (6)
Estoy haciendo cálculos científicos de alta precisión. Al buscar la mejor representación de varios efectos, sigo encontrando razones para querer obtener el siguiente número de precisión doble más alto (o más bajo) disponible. Esencialmente, lo que quiero hacer es agregar uno al bit menos significativo en la representación interna de un doble.
La dificultad es que el formato IEEE no es totalmente uniforme. Si se usara un código de bajo nivel y se agregara uno al bit menos significativo, el formato resultante podría no ser el siguiente doble disponible. Por ejemplo, podría ser un número de caso especial como PositiveInfinity o NaN. También hay valores subnormales, que no pretendo entender, pero que parecen tener patrones de bits específicos diferentes del patrón "normal".
Hay un valor "épsilon" disponible, pero nunca he entendido su definición. Dado que los valores dobles no están espaciados uniformemente, no se puede agregar un valor único a un doble para dar como resultado el siguiente valor más alto.
Realmente no entiendo por qué IEEE no ha especificado una función para obtener el siguiente valor más alto o más bajo. No puedo ser el único que lo necesite.
¿Hay alguna manera de obtener el siguiente valor (sin algún tipo de bucle que intente agregar valores cada vez más pequeños)?
Como dice Thorsten S., esto se puede hacer con la clase BitConverter
, pero su método asume que el método DoubleToInt64Bits
devuelve la estructura de bytes interna del double
, que no lo hace. El entero devuelto por ese método en realidad devuelve el número de dobles representables entre 0 y el tuyo. Es decir, el doble positivo más pequeño está representado por 1, el siguiente doble más grande es 2, etc. Los números negativos comienzan en long.MinValue
y desaparecen de 0d.
Así que puedes hacer algo como esto:
public static double NextDouble(double value) {
// Get the long representation of value:
var longRep = BitConverter.DoubleToInt64Bits(value);
long nextLong;
if (longRep >= 0) // number is positive, so increment to go "up"
nextLong = longRep + 1L;
else if (longRep == long.MinValue) // number is -0
nextLong = 1L;
else // number is negative, so decrement to go "up"
nextLong = longRep - 1L;
return BitConverter.Int64BitsToDouble(nextLong);
}
Esto no tiene que ver con Infinity
y NaN,
pero puede buscarlos y tratarlos como le plazca, si está preocupado.
Con respecto a la función épsilon, es una estimación de qué tan lejos de la aproximación de un valor decimal podría estar el doble binario. Esto se debe a que, para números decimales positivos o negativos muy grandes o números decimales positivos o negativos muy pequeños, muchos de ellos se asignan a la misma representación binaria como un doble. Pruebe algunos números decimales muy, muy grandes o muy, muy pequeños, cree dobles a partir de ellos y luego vuelva a transformarlos en un número decimal. Encontrarás que no obtendrás el mismo número decimal, sino el que más se acerca al doble.
Para valores cercanos (cercanos al rango vasto de valores decimales que pueden representar los dobles) 1 o -1, épsilon será cero o muy, muy pequeño. Para los valores que progresivamente se dirigen hacia + o - infinito o cero, epsilon comenzará a crecer. A valores extremadamente cercanos a cero o bien a infinito, épsilon será muy grande porque las representaciones binarias disponibles para los valores decimales en esos rangos son muy, muy escasas.
Hay funciones disponibles para hacer exactamente eso, pero pueden depender del idioma que use. Dos ejemplos:
si tiene acceso a una biblioteca matemática de C99 decente, puede usar
nextafter
(y sus variantes flotantes y largas dobles,nextafterf
ynextafterl
); o la familia siguiente (que toma un doble largo como segundo argumento).Si escribes Fortran, tienes el intrínseco
nearest
disponible.
Si no puede acceder a estos directamente desde su idioma, también puede ver cómo se implementan de forma gratuita, como este .
La mayoría de los idiomas tienen funciones intrínsecas o de biblioteca para adquirir el número de precisión simple (32 bits) siguiente o anterior o doble precisión (64 bits).
Para los usuarios de aritmética de punto flotante de 32 y 64 bits, una buena comprensión de las construcciones básicas es muy útil para evitar algunos peligros con ellos. El estándar IEEE se aplica de manera uniforme, pero aún deja una serie de detalles a los implementadores. Por lo tanto, una solución universal de plataforma basada en manipulaciones de bits de las representaciones de palabras de máquina puede ser problemática y puede depender de problemas como endian, etc. Si bien la comprensión de todos los detalles sangrientos de cómo podría o debería funcionar a nivel de bits puede demostrar destreza intelectual, aún es mejor utilizar una solución de biblioteca o intrínseca que se adapte a cada plataforma y tenga una API universal en todas las plataformas compatibles.
Noté soluciones para C # y C ++. Aquí hay algunos para Java:
Math.nextUp:
pública siguiente doble estática (doble d):
- Devuelve el valor de punto flotante adyacente a d en la dirección del infinito positivo. Este método es semánticamente equivalente a nextAfter (d, Double.POSITIVE_INFINITY); sin embargo, una implementación nextUp puede ejecutarse más rápido que su llamada nextAfter equivalente.
Casos especiales:
- Si el argumento es NaN, el resultado es NaN.
- Si el argumento es infinito positivo, el resultado es infinito positivo.
- Si el argumento es cero, el resultado es Double.MIN_VALUE
Parámetros:
- d - valor inicial de punto flotante
Devoluciones:
- El valor de punto flotante adyacente más cercano al infinito positivo.
flotador estático público nextUp (float f):
- Devuelve el valor de punto flotante adyacente a f en la dirección del infinito positivo. Este método es semánticamente equivalente a nextAfter (f, Float.POSITIVE_INFINITY); sin embargo, una implementación nextUp puede ejecutarse más rápido que su llamada nextAfter equivalente.
Casos especiales:
- Si el argumento es NaN, el resultado es NaN.
- Si el argumento es infinito positivo, el resultado es infinito positivo.
- Si el argumento es cero, el resultado es Float.MIN_VALUE
Parámetros:
- f - valor inicial de punto flotante
Devoluciones:
- El valor de punto flotante adyacente más cercano al infinito positivo.
Los dos siguientes son un poco más complejos de usar. Sin embargo, una dirección hacia el cero o hacia el infinito positivo o negativo parecen los usos más probables y útiles. Otro uso es ver que existe un valor intermedio entre dos valores. Uno puede determinar cuántos existen entre dos valores con un bucle y un contador. Además, parece que, junto con los métodos nextUp, podrían ser útiles para aumentar / disminuir en bucles for.
Math.nextAfter:
public static double nextAfter (doble inicio, doble dirección)
- Devuelve el número de punto flotante adyacente al primer argumento en la dirección del segundo argumento. Si ambos argumentos se comparan como iguales, se devuelve el segundo argumento.
Casos especiales:
- Si cualquiera de los argumentos es un NaN, entonces se devuelve NaN.
- Si ambos argumentos tienen ceros firmados, la dirección se devuelve sin cambios (como lo indica el requisito de devolver el segundo argumento si los argumentos se comparan como iguales).
- Si el inicio es ± Double.MIN_VALUE y la dirección tiene un valor tal que el resultado debe tener una magnitud menor, se devuelve un cero con el mismo signo que el inicio.
- Si el inicio es infinito y la dirección tiene un valor tal que el resultado debe tener una magnitud menor, se devuelve Double.MAX_VALUE con el mismo signo que el inicio.
- Si el inicio es igual a ± Double.MAX_VALUE y la dirección tiene un valor tal que el resultado debe tener una magnitud mayor, se devuelve un infinito con el mismo signo que el inicio.
Parámetros:
- inicio - inicio valor de punto flotante
- dirección - valor que indica cuál de los vecinos de inicio o inicio debe devolverse
Devoluciones:
- El número de punto flotante adyacente para comenzar en la dirección de la dirección.
flotador estático público siguiente Después (inicio flotante, doble dirección)
- Devuelve el número de punto flotante adyacente al primer argumento en la dirección del segundo argumento. Si ambos argumentos se comparan como iguales, se devuelve un valor equivalente al segundo argumento.
Casos especiales:
- Si cualquiera de los argumentos es un NaN, entonces se devuelve NaN.
- Si ambos argumentos tienen ceros firmados, se devuelve un valor equivalente a la dirección.
- Si el inicio es ± Float.MIN_VALUE y la dirección tiene un valor tal que el resultado debe tener una magnitud menor, se devuelve un cero con el mismo signo que el inicio.
- Si el inicio es infinito y la dirección tiene un valor tal que el resultado debe tener una magnitud menor, se devuelve Float.MAX_VALUE con el mismo signo que el inicio.
- Si el inicio es igual a ± Float.MAX_VALUE y la dirección tiene un valor tal que el resultado debe tener una magnitud mayor, se devuelve un infinito con el mismo signo que el inicio.
Parámetros:
- inicio - inicio valor de punto flotante
- dirección - valor que indica cuál de los vecinos de inicio o inicio debe devolverse
Devoluciones:
- El número de punto flotante adyacente para comenzar en la dirección de la dirección.
No estoy seguro de estar siguiendo tu problema. ¿Seguramente el estándar IEEE es totalmente uniforme? Por ejemplo, mira este extracto del artículo de wikipedia para los números de doble precisión.
3ff0 0000 0000 0000 = 1
3ff0 0000 0000 0001 = 1.0000000000000002, the next higher number > 1
3ff0 0000 0000 0002 = 1.0000000000000004
¿Qué hay de malo con solo incrementar el bit menos significativo, en una representación binaria o hexadecimal?
En lo que respecta a los números especiales (infinito, NaN, etc.), están bien definidos y no hay muchos de ellos. Los límites se definen de manera similar.
Como obviamente has investigado esto, supongo que tengo el extremo equivocado del palo. Si esto no es suficiente para su problema, ¿podría intentar aclarar lo que quiere lograr? ¿Cuál es tu objetivo aquí?
Sí, hay una manera. Cía#:
public static double getInc (double d)
{
// Check for special values
if (double.IsPositiveInfinity(d) || double.IsNegativeInfinity(d))
return d;
if (double.IsNaN(d))
return d;
// Translate the double into binary representation
ulong bits = (ulong)BitConverter.DoubleToInt64Bits(d);
// Mask out the mantissa bits
bits &= 0xfff0000000000000L;
// Reduce exponent by 52 bits, so subtract 52 from the mantissa.
// First check if number is great enough.
ulong testWithoutSign = bits & 0x7ff0000000000000L;
if (testWithoutSign > 0x0350000000000000L)
bits -= 0x0350000000000000L;
else
bits = 0x0000000000000001L;
return BitConverter.Int64BitsToDouble((long)bits);
}
El aumento se puede sumar y restar.