Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test if value stored in double fit in long? (rounding yes, but truncating no)

I think that question is pretty straight. but here is an examples.

Example below is OK. I can take rounding and no truncating was done here.

public static void main(String[] args) {
    double d = 9.9;
    long l = (long)d;
    System.out.println(l);
}

Output:

9

And now number out of range of long:

public static void main(String[] args) {
    double d = 99999999999999999999999999999999.9;
    long l = (long)d;
    System.out.println(l);
}

Output:

9223372036854775807

This one troubles me. I cannot continue work with completely different number. I would rather get an error or an exception.

Is there any way to detect this in Java?

like image 887
arenaq Avatar asked Sep 08 '15 11:09

arenaq


People also ask

Why is double not accurate?

Because floats and doubles cannot accurately represent the base 10 multiples that we use for money. This issue isn't just for Java, it's for any programming language that uses base 2 floating-point types. In base 10, you can write 10.25 as 1025 * 10-2 (an integer times a power of 10).

Does Java truncate or round?

This will tell us the remainder; however, it doesn't take away the fact that Java won't round the value. It only truncates, rounding down toward zero. If we want to view the real value, we would have to use type casting - changing from one type to another - to convert to a double or float to get the precision.

What is a double rounding error?

Double rounding is often harmless, giving the same result as rounding once, directly from n 0 digits to n 2 digits. However, sometimes a doubly rounded result will be incorrect, in which case we say that a double rounding error has occurred.

Does attaching the ‘F’ suffix prevent double rounding errors in C?

In this article, I’ll show example conversions in C that are tainted by double rounding errors, and how attaching the ‘f’ suffix to floating-point literals prevents them-in gcc C at least, but not in Visual C++!

How many ULP above the correctly rounded value is 1?

It’s 1 ULP above the correctly rounded value, due to a double rounding error. The compiler converted it by first rounding it up to 53 bits, giving 0.100000000000000000000001 1, and then rounding that up to 24 bits, giving 0.10000000000000000000001.

Why does Visual C++ double round decimal values?

As in example 1, Visual C++ ignores the ‘f’ suffix, resulting in double rounding for both f and fd. Summary Floating-point literals are subject to double rounding when assigned to single-precision variables, resulting in incorrectly rounded decimal to floating-point conversions.


2 Answers

You can compare it with Long.MIN_VALUE and Long.MAX_VALUE:

public static boolean fitsLong(double d) {
    return d >= Long.MIN_VALUE && d < Long.MAX_VALUE;
}

Somewhat more sofisticated approach is to use BigDecimal:

double value = 1234567.9;
long l = BigDecimal.valueOf(value)
                   .setScale(0, RoundingMode.HALF_EVEN)
                   .longValueExact(); // 1234568

double value = 99999999999999999999999999999999.9;
long l = BigDecimal.valueOf(value)
                   .setScale(0, RoundingMode.HALF_EVEN)
                   .longValueExact(); // ArithmeticException

This way you can control how the rounding is performed.

You may ask, why there's strict inequality in fitsLong: d < Long.MAX_VALUE. Actually that's because the Long.MAX_VALUE itself cannot be represented as double number. When you cast (double)Long.MAX_VALUE, there's not enough precision in double type to represent it, so the closest representable value is selected which is 9223372036854775808.0 (Long_MAX_VALUE+1.0). So were it d <= Long.MAX_VALUE it would return true for number which is actually a little bigger as in this comparison Long.MAX_VALUE constant is promoted to double type. On the other hand Long.MIN_VALUE can be exactly represented in double type, thus here we have >=.

Also it's interesting why the following works:

double value = -9223372036854775809.9; // Long.MIN_VALUE-1.9
System.out.println(fitsLong(value)); // returns true

That's because you actually did not subtract anything from the Long.MIN_VALUE. See:

double d1 = Long.MIN_VALUE;
double d2 = -9223372036854775809.9;
System.out.println(d1 == d2); // true

The double precision is not enough to distinguish between -9223372036854775808 and -9223372036854775809.9, so it's actually the same double number. During the compilation it's converted to binary form, and binary form for these two numbers is the same. Thus having compiled program you cannot distinguish whether -9223372036854775808 or -9223372036854775809.9 was in the source code.

If you feel that it's still the issue, construct the BigDecimal from the String:

long l = new BigDecimal("-9223372036854775808.2")
                   .setScale(0, RoundingMode.HALF_EVEN)
                   .longValueExact(); // ok, -9223372036854775808

long l = new BigDecimal("-9223372036854775808.9")
                   .setScale(0, RoundingMode.HALF_EVEN)
                   .longValueExact(); // ArithmeticException
like image 178
Tagir Valeev Avatar answered Oct 29 '22 00:10

Tagir Valeev


When you cast a floating point type to an int or long, the result is either the nearest integer (rounding towards zero), or the MIN_VALUE or MAX_VALUE for int or long. See JLS 5.1.3.

Hence, one alternative would be to do the typecast and then test for the appropriate MIN_VALUE or MAX_VALUE.

Note that Long.MAX_VALUE is 9223372036854775807 ... which is the number your test program outputs!

(However, this doesn't work if you are casting a floating point type to byte, char or short. See the link above for the explanation.)

like image 21
Stephen C Avatar answered Oct 29 '22 01:10

Stephen C