programacion - tipos de variables en java ejemplos
¿Cómo probar la conversión sin pérdida doble/entero? (3)
Además de la elaborada respuesta de Pascal Cuoq, y dado el contexto adicional que proporciona en los comentarios, agregaría una prueba para ceros negativos. Debe conservar los ceros negativos a menos que tenga buenas razones para no hacerlo. Necesita una prueba específica para evitar convertirlos a (int64_t)0
. Con su propuesta actual, los ceros negativos pasarán su prueba, se almacenarán como int64_t
y se leerán como ceros positivos.
No estoy seguro de cuál es la forma más eficiente de probarlos, tal vez esta:
int int64EqualsDouble(int64_t i, double d) {
return (d >= INT64_MIN)
&& (d < INT64_MAX)
&& (round(d) == d)
&& (i == (int64_t)d
&& (!signbit(d) || d != 0.0);
}
Tengo una doble, y una int64_t. Quiero saber si tienen exactamente el mismo valor y si la conversión de un tipo al otro no pierde ninguna información.
Mi implementación actual es la siguiente:
int int64EqualsDouble(int64_t i, double d) {
return (d >= INT64_MIN)
&& (d < INT64_MAX)
&& (round(d) == d)
&& (i == (int64_t)d);
}
Mi pregunta es: ¿es correcta esta implementación? Y si no, ¿cuál sería una respuesta correcta? Para ser correcto, no debe dejar ningún falso positivo, y ningún falso negativo.
Algunas entradas de muestra:
- int64EqualsDouble (0, 0.0) debe devolver 1
- int64EqualsDouble (1, 1.0) debe devolver 1
- int64EqualsDouble (0x3FFFFFFFFFFFFFFF, (double) 0x3FFFFFFFFFFFFFFF) debe devolver 0, porque 2 ^ 62 - 1 puede representarse exactamente con int64_t, pero no con double.
- int64EqualsDouble (0x4000000000000000, (double) 0x400000000000000000) debe devolver 1, porque 2 ^ 62 se pueden representar exactamente en int64_t y double.
- int64EqualsDouble (INT64_MAX, (double) INT64_MAX) debe devolver 0, porque INT64_MAX no puede representarse exactamente como un doble
- int64EqualsDouble (..., 1.0e100) debe devolver 0, porque 1.0e100 no puede representarse exactamente como int64_t.
El código de OP tiene una dependencia que se puede evitar.
Para una comparación exitosa, d
debe ser un número entero y round(d) == d
se encarga de eso. Incluso d
, como un NaN fallaría eso.
d
debe estar matemáticamente en el rango de [ INT64_MIN
... INT64_MAX
] y, si las condiciones son correctas, el i == (int64_t)d
finaliza la prueba.
Entonces, la cuestión se reduce a comparar los límites de INT64
con la double
d
.
Supongamos que FLT_RADIX == 2
, pero no necesariamente IEEE 754 binary64 .
d >= INT64_MIN
no es un problema ya que -INT64_MIN
es una potencia de 2 y se convierte exactamente a un double
del mismo valor, por lo que >=
es exacto.
El código quisiera hacer la matemática d <= INT64_MAX
, pero puede que no funcione, por lo que es un problema. INT64_MAX
es una "potencia de 2 - 1" y puede que no se convierta exactamente, depende de si la precisión del double
supera los 63 bits, lo que hace que la comparación no sea clara. Una solución es reducir a la mitad la comparación. d/2
no sufre pérdida de precisión e INT64_MAX/2 + 1
convierte exactamente en una double
potencia de 2
d/2 < (INT64_MAX/2 + 1)
[Editar]
// or simply
d < ((double)(INT64_MAX/2 + 1))*2
Por lo tanto, si el código no quiere confiar en que el double
tenga menos precisión que uint64_t
. (Algo que probablemente se aplique con el long double
) una solución más portátil sería
int int64EqualsDouble(int64_t i, double d) {
return (d >= INT64_MIN)
&& (d < ((double)(INT64_MAX/2 + 1))*2) // (d/2 < (INT64_MAX/2 + 1))
&& (round(d) == d)
&& (i == (int64_t)d);
}
Nota: No hay problemas de modo de redondeo.
[Editar] Una explicación más profunda del límite
Asegurando matemáticamente, INT64_MIN <= d <= INT64_MAX
, puede volver a establecerse como INT64_MIN <= d < (INT64_MAX + 1)
ya que estamos tratando con números enteros. Dado que la aplicación sin (double) (INT64_MAX + 1)
de (double) (INT64_MAX + 1)
en el código es ciertamente 0, una alternativa es ((double)(INT64_MAX/2 + 1))*2
. Esto se puede extender para máquinas raras con el double
de potencias mayores de 2 a ((double)(INT64_MAX/FLT_RADIX + 1))*FLT_RADIX
. Los límites de comparación son las potencias de 2 exactas , la conversión al double
no sufre pérdida de precisión y (lo_limit >= d) && (d < hi_limit)
es exacta, independientemente de la precisión del punto flotante. Nota: un raro punto flotante con FLT_RADIX == 10
sigue siendo un problema.
Sí, su solución funciona correctamente porque fue diseñada para hacerlo, porque int64_t
está representado en el complemento de dos por definición (C99 7.18.1.1:1), en plataformas que utilizan algo parecido al binario IEEE 754 de doble precisión para el tipo double
. Es básicamente el mismo que este .
Bajo estas condiciones:
d < INT64_MAX
es correcto porque es equivalente ad < (double) INT64_MAX
y en la conversión a doble, el númeroINT64_MAX
, igual a 0x7fffffffffffffff, se redondea. Por lo tanto, desea qued
sea estrictamente menor que eldouble
resultante para evitar disparar UB al ejecutar(int64_t)d
.Por otro lado,
INT64_MIN
, siendo -0x8000000000000000, es exactamente representable, lo que significa que undouble
que es igual a(double)INT64_MIN
puede ser igual a algunosint64_t
y no debe ser excluido (y taldouble
puede convertirse aint64_t
sin disparar comportamiento indefinido)
No hace falta decir que, dado que hemos utilizado específicamente los supuestos sobre el complemento de 2 para los enteros y el punto flotante binario, este razonamiento no garantiza la exactitud del código en plataformas que difieran. Tome una plataforma con un punto flotante binario de 64 bits y un tipo entero T
complemento de 64 bits. En esa plataforma, T_MIN
es -0x7fffffffffffffff
. La conversión al double
de ese número se redondea, resultando en -0x1.0p63
. En esa plataforma, usar su programa como está escrito, usar -0x1.0p63
para d
hace que las primeras tres condiciones sean verdaderas, lo que resulta en un comportamiento indefinido en (T)d
, porque el desbordamiento en la conversión de entero a punto flotante es un comportamiento indefinido .
Si tiene acceso a todas las funciones de IEEE 754, hay una solución más corta:
#include <fenv.h>
…
#pragma STDC FENV_ACCESS ON
feclearexcept(FE_INEXACT), f == i && !fetestexcept(FE_INEXACT)
Esta solución aprovecha la conversión de entero a punto flotante estableciendo el indicador INEXACT si la conversión es inexacta (es decir, si i
no se puede representar exactamente como un double
).
El indicador INEXACT permanece sin establecer y f
es igual a (double)i
si y solo si f
y i
representan el mismo valor matemático en sus respectivos tipos.
Este enfoque requiere que el compilador haya sido advertido de que el código accede al estado de la FPU, normalmente con #pragma STDC FENV_ACCESS on
pero que normalmente no es compatible y en su lugar debe usar un indicador de compilación.