error - ¿Cómo encuentras el valor no igual más cercano de un flotador?
floating point error (3)
La forma estándar de encontrar vecinos de un valor de punto flotante es la función nextafter
para double
y nextafterf
para float
. El segundo argumento da la dirección. Recuerde que los infinitos son valores legales en el punto flotante IEEE 754, por lo que puede llamar a nextafter(x, +1.0/0.0)
para obtener el valor inmediatamente por encima de x
, y esto funcionará incluso para DBL_MAX
(mientras que si escribió nextafter(x, DBL_MAX)
, devolverá DBL_MAX
cuando se aplique para x == DBL_MAX
).
Dos formas no estándar que a veces son útiles son:
acceda a la representación de
float
/double
como un entero sin signo del mismo tamaño y aumente o disminuya este entero. El formato de punto flotante se diseñó cuidadosamente para que, para los flotadores positivos y, respectivamente, para los flotadores negativos, los bits de la representación, vistos como un número entero, evolucionen de manera monótona con el flotante representado.cambie el modo de redondeo a ascendente y agregue el número de punto flotante positivo más pequeño. El número de punto flotante positivo más pequeño es también el incremento más pequeño que puede haber entre dos flotantes, por lo que nunca omitirá ningún flotante. El número de punto flotante positivo más pequeño es
FLT_MIN * FLT_EPSILON
.
Para completar, (1.0f + FLT_EPSILON)
que, incluso sin cambiar el modo de redondeo de su valor predeterminado "más cercano", al multiplicar un valor flotante por (1.0f + FLT_EPSILON)
produce un número que es el vecino inmediato lejos de cero o el vecino después de esto. Probablemente sea el más barato si ya conoce el signo del flotador que desea aumentar / disminuir y no le importa que a veces no produzca el vecino inmediato. Las funciones nextafter
y nextafterf
se especifican de tal manera que una implementación correcta en el x86 debe probar una serie de valores especiales y estados de FPU, y por lo tanto es bastante costosa por lo que hace.
Para ir hacia cero, multiplica por 1.0f - FLT_EPSILON
.
Esto no funciona para 0.0f
, obviamente, y en general para los números desnormalizados más pequeños.
Los valores por los cuales la multiplicación por 1.0f + FLT_EPSILON
avance por 2 ULPS están justo debajo de una potencia de dos, específicamente en el intervalo [0.75 * 2 p … 2 p ). Si no te importa hacer una multiplicación y una suma, x + (x * (FLT_EPSILON * 0.74))
debería funcionar para todos los números normales (pero aún no para cero ni para todos los números denormales pequeños).
Esta pregunta ya tiene una respuesta aquí:
Un valor float
(también conocido como simple) es un valor de 4 bytes, y se supone que representa cualquier número de valor real. Debido a la forma en que está formateado y al número finito de bytes que está hecho, hay un valor mínimo y un valor máximo que puede representar, y tiene una precisión finita, dependiendo de su propio valor.
Me gustaría saber si hay una manera de obtener el valor más cercano posible por encima o por debajo de algún valor de referencia, dada la precisión finita de un flotador. Con los enteros, esto es trivial: uno simplemente suma o resta 1. Pero con un valor float
, no puede simplemente sumar o restar el valor flotante mínimo y esperar que sea diferente de su valor original. Es decir
float FindNearestSmaller (const float a)
{
return a - FLT_MIN; /* This doesn''t necessarily work */
}
De hecho, lo anterior casi nunca funcionará. En el caso anterior, el retorno generalmente será igual a
, ya que FLT_MIN
está muy lejos de la precisión de a
. Puedes probar esto fácilmente por ti mismo: funciona para, por ejemplo, 0.0f
, o para números muy pequeños de orden FLT_MIN
, pero no para nada entre 0 y 100.
Entonces, ¿cómo obtendría el valor más cercano pero más pequeño o más grande que a
, dada la precisión de punto flotante?
Nota: Aunque estoy principalmente interesado en una respuesta C / C ++, asumo que la respuesta será aplicable para la mayoría de los lenguajes de programación.
Lo probé en mi máquina. Y los tres enfoques:
1. añadiendo con 1 y memcopying
2. añadiendo FLT_EPSILON
3. multiplicando por (1.0f + FLT_EPSILON)
Parece dar la misma respuesta.
bash-3.2 $ cc float_test.c -o float_test; ./float_test 1.023456 10
Numero original: 1.023456
Int agregado = 1.023456 01-eps agregado = 1.023456 mult por 01 * (eps + 1) = 1.023456
int agregado = 1.023456 02-eps agregado = 1.023456 mult por 02 * (eps + 1) = 1.023456
Int agregado = 1.023456 03-eps agregado = 1.023456 mult por 03 * (eps + 1) = 1.023456
int agregado = 1.023456 04-eps agregado = 1.023456 mult por 04 * (eps + 1) = 1.023456
Int agregado = 1.023457 05-eps agregado = 1.023457 mult por 05 * (eps + 1) = 1.023457
Int agregado = 1.023457 06-eps agregado = 1.023457 mult por 06 * (eps + 1) = 1.023457
Int agregado = 1.023457 07-eps agregado = 1.023457 mult por 07 * (eps + 1) = 1.023457
Int agregado = 1.023457 08-eps agregado = 1.023457 mult por 08 * (eps + 1) = 1.023457
Int agregado = 1.023457 09-eps agregado = 1.023457 mult por 09 * (eps + 1) = 1.023457
int agregado = 1.023457 10-eps agregado = 1.023457 multis por 10 * (eps + 1) = 1.023457
Código
#include <float.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
int main(int argc, char *argv[])
{
if(argc != 3) {
printf("Usage: <binary> <floating_pt_num> <num_iter>/n");
exit(0);
}
float f = atof(argv[1]);
int count = atoi(argv[2]);
assert(count > 0);
int i;
int num;
float num_float;
printf("Original num: %f/n", f);
for(i=1; i<=count; i++) {
memcpy(&num, &f, 4);
num += i;
memcpy(&num_float, &num, 4);
printf("int added = %f /t%02d-eps added = %f /tmult by %2d*(eps+1) = %f/n", num_float, i, f + i*FLT_EPSILON, i, f*(1.0f + i*FLT_EPSILON));
}
return 0;
}
Mire la función "nextafter", que forma parte del Estándar C (y probablemente C ++, pero no lo comprobé).