c++ - programacion - punto flotante metodos numericos
Comparando valores de punto flotante convertidos de cadenas con literales (4)
Este no es un duplicado de la matemática de punto flotante roto , incluso si se parece a uno a primera vista.
Estoy leyendo un double
de un archivo de texto usando fscanf(file, "%lf", &value);
y comparándolo con el operador ==
contra un doble literal. Si la cadena es la misma que la literal, ¿la comparación con ==
será true
en todos los casos?
Ejemplo
Contenido del archivo de texto:
7.7
Fragmento de código:
double value;
fscanf(file, "%lf", &value); // reading "7.7" from file into value
if (value == 7.7)
printf("strictly equal/n");
La salida esperada y real es
strictly equal
Pero esto supone que el compilador convierte el doble literal 7.7
en un doble exactamente de la misma manera que lo hace la función fscanf
, pero el compilador puede o no usar la misma biblioteca para convertir las cadenas al doble.
O pregunte de otra manera: ¿la conversión de cadena a doble resulta en una representación binaria única o puede haber pequeñas diferencias dependientes de la implementación?
Si la cadena es igual a la literal, ¿la comparación con
==
será verdadera en todos los casos?
Una consideración común aún no explorada: FLT_EVAL_METHOD
#include <float.h>
...
printf("%d/n", FLT_EVAL_METHOD);
2 evalúe todas las operaciones y constantes para el rango y la precisión del tipo
long double
.
Si esto devuelve 2, entonces la matemática utilizada en el value == 7.7
es long double
y 7.7
tratada como 7.7L
. En el caso de OP, esto puede ser falso.
Para tener en cuenta esta precisión más amplia, asigne valores que eliminen todo el rango y la precisión adicionales.
scanf(file, "%lf", &value);
double seven_seven = 7.7;
if (value == seven_seven)
printf("strictly equal/n");
En mi opinión, este es un problema que ocurre con mayor probabilidad que los modos de redondeo de variantes o las variaciones en las conversiones de biblioteca / compilador.
Tenga en cuenta que este caso es similar al siguiente, un problema bien conocido.
float value;
fscanf(file, "%f", &value);
if (value == 7.7)
printf("strictly equal/n");
Demostración
#include <stdio.h>
#include <float.h>
int main() {
printf("%d/n", FLT_EVAL_METHOD);
double value;
sscanf("7.7", "%lf", &value);
double seven_seven = 7.7;
if (value == seven_seven) {
printf("value == seven_seven/n");
} else {
printf("value != seven_seven/n");
}
if (value == 7.7) {
printf("value == 7.7/n");
} else {
printf("value != 7.7/n");
}
return 0;
}
Salida
2
value == seven_seven
value != 7.7
Comparación alternativa
Para comparar 2 double
que están "cerca" entre sí, necesitamos una definición de "cerca". Un enfoque útil es considerar todos los valores double
finitos ordenados en una secuencia ascendente y luego comparar sus números de secuencia entre sí. double_distance(x, nextafter(x, 2*x)
-> 1
El siguiente código hace varias suposiciones acerca del diseño y tamaño double
.
#include <assert.h>
unsigned long long double_order(double x) {
union {
double d;
unsigned long long ull;
} u;
assert(sizeof(double) == sizeof(unsigned long long));
u.d = x;
if (u.ull & 0x8000000000000000) {
u.ull ^= 0x8000000000000000;
return 0x8000000000000000 - u.ull;
}
return u.ull + 0x8000000000000000;
}
unsigned long long double_distance(double x, double y) {
unsigned long long ullx = double_order(x);
unsigned long long ully = double_order(y);
if (x > y) return ullx - ully;
return ully - ullx;
}
....
printf("%llu/n", double_distance(value, 7.7)); // 0
printf("%llu/n", double_distance(value, nextafter(value,value*2))); // 1
printf("%llu/n", double_distance(value, nextafter(value,value/2))); // 1
O simplemente usar
if (nextafter(7.7, -INF) <= value && value <= nextafter(7.7, +INF)) {
puts("Close enough");
}
Acerca de C ++, de cppreference se puede leer :
[lex.fcon]
(§6.4.4.2)
El resultado de evaluar una constante flotante es el valor representable más cercano o el valor representable mayor o menor inmediatamente adyacente al valor representable más cercano, elegido de una manera definida por la implementación (en otras palabras, la dirección de redondeo predeterminada durante la traducción está definida por la implementación) .
Dado que la representación de un literal flotante no está especificada, supongo que no puede concluir acerca de su comparación con un resultado de scanf
.
Sobre C11 (norma ISO / IEC 9899: 2011):
[lex.fcon]
(§6.4.4.2)
Práctica recomendada
7 La conversión en tiempo de conversión de las constantes flotantes debe coincidir con la conversión en tiempo de ejecución de las cadenas de caracteres mediante funciones de biblioteca, como
strtod
, dadas entradas coincidentes adecuadas para ambas conversiones, el mismo formato de resultado y el redondeo predeterminado del tiempo de ejecución.
Así que claramente para C11, esto no está garantizado para que coincida.
Desde el estándar de C ++:
[lex.fcon]
... Si el valor escalado se encuentra en el rango de valores representables para su tipo, el resultado es el valor escalado si es representable, de lo contrario, el valor representable mayor o menor más cercano al valor escalado, elegido de una manera definida por la implementación ...
énfasis mio
Por lo tanto, solo puede confiar en la igualdad si el valor es estrictamente representable por un doble.
No hay garantía.
Puede esperar que el compilador use un algoritmo de alta calidad para la conversión de literales, y que la implementación de la biblioteca estándar también use una conversión de alta calidad, y dos algoritmos de alta calidad deberían estar de acuerdo con bastante frecuencia.
También es posible que ambos usen exactamente el mismo algoritmo (por ejemplo, el compilador convierte el literal colocando los caracteres en una matriz de caracteres y llamando a sscanf).
Por cierto Tuve un error causado por el hecho de que un compilador no convirtió exactamente el literal 999999999.5. Lo reemplazó con 9999999995 / 10.0 y todo estaba bien.