¿Cómo saber si un BigDecimal puede convertir exactamente a flotante o doble?

10

La clase BigDecimaltiene algunos métodos útiles para garantizar una conversión sin pérdidas:

  • byteValueExact()
  • shortValueExact()
  • intValueExact()
  • longValueExact()

Sin embargo, los métodos floatValueExact()y doubleValueExact()no existen.

Leí el código fuente de OpenJDK para métodos floatValue()y doubleValue(). Ambos parecen retroceder Float.parseFloat()y Double.parseDouble(), respectivamente, lo que puede devolver un infinito positivo o negativo. Por ejemplo, analizar una cadena de 10,000 9s devolverá un infinito positivo. Según tengo entendido, BigDecimalno tiene un concepto interno de infinito. Además, analizando una cadena de 100 9s como doubleda 1.0E100, que no es infinito, pero pierde precisión.

¿Qué es una implementación razonable floatValueExact()y doubleValueExact()?

Pensé en una doublesolución mediante la combinación de BigDecimal.doubleValue(), BigDecial.toString(), Double.parseDouble(String)y Double.toString(double), pero se ve desordenado. Quiero preguntar aquí porque puede (¡debe!) Haber una solución más simple.

Para ser claros, no necesito una solución de alto rendimiento.

kevinarpe
fuente
77
Supongo que podría transformarse en doble, luego volver a transformar el doble en BigDecimal y ver si obtiene el mismo valor. Sin embargo, no estoy seguro de cuál es el caso de uso. Si usa dobles, ya acepta tener valores no exactos. Si desea valores exactos, entonces quédese con BigDecimal.
JB Nizet

Respuestas:

6

De leer los documentos , todo lo que hace con elnumTypeValueExact variantes es verificar la existencia de una fracción o si el valor es demasiado grande para el tipo numérico y lanzar excepciones.

En cuanto a floatValue()y doubleValue(), se está realizando una comprobación de desbordamiento similar, pero en lugar de lanzar una excepción, en su lugar, devuelve Double.POSITIVE_INFINITYo Double.NEGATIVE_INFINITYpara dobles y Float.POSITIVE_INFINITYoFloat.NEGATIVE_INFINITY flotantes.

Por lo tanto, la implementación más razonable (y más simple) de los exactmétodos para flotante y doble, simplemente debe verificar si la conversión regresa POSITIVE_INFINITYo NEGATIVE_INFINITY.


Además , recuerde que BigDecimalfue diseñado para manejar la falta de precisión que proviene del uso floato doublepara grandes irracionales, por lo tanto, como comentó @JB Nizet , otra comprobación que puede agregar a lo anterior sería convertir o volver adoublefloatBigDecimal a ver si todavía obtiene El mismo valor. Esto debería probar que la conversión fue correcta.

Así es como se vería este método floatValueExact():

public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (!Float.isInfinite(result)) {
        if (new BigDecimal(String.valueOf(result)).compareTo(decimal) == 0) {
            return result;
        }
    }
    throw new ArithmeticException(String.format("%s: Cannot be represented as float", decimal));
}

El uso de en compareTolugar de lo equalsanterior es intencional para no ser demasiado estricto con los controles. equalssolo se evaluará como verdadero cuando los dos BigDecimalobjetos tengan el mismo valor y escala (tamaño de la fracción de la parte decimal), mientras que compareTopasará por alto esta diferencia cuando no importe. Por ejemplo 2.0vs 2.00.

smac89
fuente
Muy astuto ¡Exactamente lo que necesitaba! Nota: También puede desear usar Float.isFinite()o Float.isInfinite(), pero esto es opcional. :)
kevinarpe
3
También debería preferir compareTo sobre equals, porque equals () en BigDecimal considerará 2.0 y 2.00 como valores diferentes.
JB Nizet
Los valores que no se pueden representar con precisión como flotante no funcionarán. Ejemplo: 123.456f. Supongo que esto se debe al diferente tamaño de significado (mantisa) entre el flotante de 32 bits y el doble de 64 bits. En el código anterior, consigo mejores resultados con: if (new BigDecimal(result, MathContext.DECIMAL32).equals(decimal)) {. ¿Es este un cambio razonable ... o me falta otro caso de esquina de valores de coma flotante?
kevinarpe
1
@kevinarpe: 123.456fes conceptualmente igual que su ejemplo 1.0E100. Me sorprende que si necesita verificar que la conversión de BigDecimal -> punto flotante binario sea exacta, entonces esa necesidad es el problema; es decir, la conversión no debe contemplarse.
Stephen C