Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to know if a BigDecimal can exactly convert to float or double?

Class BigDecimal has some useful methods to guarantee lossless conversion:

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

However, methods floatValueExact() and doubleValueExact() do not exist.

I read the OpenJDK source code for methods floatValue() and doubleValue(). Both appear to fallback to Float.parseFloat() and Double.parseDouble(), respectively, which may return positive or negative infinity. For example, parsing a string of 10,000 9s will return positive infinity. As I understand, BigDecimal does not have an internal concept of infinity. Further, parsing a string of 100 9s as double gives 1.0E100, which is not infinity, but loses precision.

What is a reasonable implementation floatValueExact() and doubleValueExact()?

I thought about a double solution by combining BigDecimal.doubleValue(), BigDecial.toString(), Double.parseDouble(String) and Double.toString(double), but it looks messy. I want to ask here because there may (must!) be a simpler solution.

To be clear, I don't need a high performance solution.

like image 754
kevinarpe Avatar asked Dec 01 '19 08:12

kevinarpe


1 Answers

From reading the docs, all it does with the numTypeValueExact variants is to check for existence of a fraction part or if the value is too big for the numeric type and throw exceptions.

As for floatValue() and doubleValue(), a similar overflow check is being done, but instead of throwing an exception, instead it returns Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY for doubles and Float.POSITIVE_INFINITY or Float.NEGATIVE_INFINITY for floats.

Therefore the most reasonable (and simplest) implementation of the exact methods for float and double, should simply check if the conversion returns POSITIVE_INFINITY or NEGATIVE_INFINITY.


Furthermore, remember that BigDecimal was designed to handle the lack of precision that comes from using float or double for large irrationals, therefore as @JB Nizet commented, another check you can add to the above would be to convert the double or float back to BigDecimal to see if you still get the same value. This should prove the conversion was correct.

Here is what such a method would look like for floatValueExact():

public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (!(Float.isNaN(result) || 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));
}

The use of compareTo instead of equals above is intentional so as to not become too strict with the checks. equals will only evaluate to true when the two BigDecimal objects have the same value and scale (size of the fraction part of the decimal), whereas compareTo will overlook this difference when it does not matter. For example 2.0 vs 2.00.

like image 78
smac89 Avatar answered Oct 03 '22 21:10

smac89