tipos programacion ejemplos ejemplo datos conversion c floating-point integer double

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 a d < (double) INT64_MAX y en la conversión a doble, el número INT64_MAX , igual a 0x7fffffffffffffff, se redondea. Por lo tanto, desea que d sea ​​estrictamente menor que el double resultante para evitar disparar UB al ejecutar (int64_t)d .

  • Por otro lado, INT64_MIN , siendo -0x8000000000000000, es exactamente representable, lo que significa que un double que es igual a (double)INT64_MIN puede ser igual a algunos int64_t y no debe ser excluido (y tal double puede convertirse a int64_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.