15

How do you deal with Java's weird behaviour with the modulus operator when using doubles?

For example, you would expect the result of 3.9 - (3.9 % 0.1) to be 3.9 (and indeed, Google says I'm not going crazy), but when I run it in Java I get 3.8000000000000003.

I understand this is a result of how Java stores and processes doubles, but is there a way to work around it?

Andy
  • 2,764
  • 6
  • 24
  • 33

4 Answers4

20

Use a precise type if you need a precise result:

    double val = 3.9 - (3.9 % 0.1);
    System.out.println(val); // 3.8000000000000003

    BigDecimal x = new BigDecimal( "3.9" );
    BigDecimal bdVal = x.subtract( x.remainder( new BigDecimal( "0.1" ) ) );
    System.out.println(bdVal); // 3.9

Why 3.8000...003? Because Java uses the FPU to calculate the result. 3.9 is impossible to store exactly in IEEE double precision notation, so it stores 3.89999... instead. And 3.8999%0.01 gives 0.09999... hence the result is a little bit bigger than 3.8.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 1
    +1 I hated working with IEEE floating point numbers by hand in my Electrical Engineering class, but it was worth it. – Denis Tulskiy Jul 12 '10 at 13:21
  • Nice answer. So why is the FPU able to figure out that `((3.9D / 0.1D) % 1D) = 0.0` (basically the same problem slightly re-organized) when due to round off error it thinks `(3.9D % 0.1D) = 0.0999999999999997` ? – Dave Johnson Aug 20 '13 at 07:22
  • @DaveJohnson: That's not easy to say. Rounding errors might accumulate in your favor when you do it this way (i.e. the module creates a fraction is that too small). – Aaron Digulla Aug 20 '13 at 09:25
  • 1
    @bcsb1001: As I explained in my answer: It doesn't work as well. Some rounding errors leak from `double` to `BigDecimal`. Plus: You often get strings from the input side (text files, UI input fields) and converting them to `double` before creating the `BigDecimal` is a waste of time plus a (small) risk. – Aaron Digulla Sep 02 '15 at 11:21
  • 1
    @bcsb1001: In OpenJDK 8u91 `BigDecimal#valueOf(double)` is implemented as `new BigDecimal(Double.toString(x))`. The reason is apparently that the Java Language Specification requires float-to-string conversion to have a higher precision than IEEE requires for general floating point arithmetics. – David Foerster Oct 31 '16 at 19:42
5

From The Java Language Specification:

The result of a floating-point remainder operation as computed by the % operator is not the same as that produced by the remainder operation defined by IEEE 754. The IEEE 754 remainder operation computes the remainder from a rounding division, not a truncating division, and so its behavior is not analogous to that of the usual integer remainder operator. Instead, the Java programming language defines % on floating-point operations to behave in a manner analogous to that of the integer remainder operator; this may be compared with the C library function fmod. The IEEE 754 remainder operation may be computed by the library routine Math.IEEEremainder.

In other words, this is due to the fact that Java rounds the result of the division involved in computing the remainder, while IEEE754 specifies truncating the answer of the division. This particular case seems to expose this difference very clearly.

You can get the answer you expect using Math.IEEEremainder:

System.out.println(3.9 - (3.9 % 0.1));
System.out.println(3.9 - Math.IEEEremainder(3.9, 0.1));
MAK
  • 26,140
  • 11
  • 55
  • 86
2

You could use java.math.BigDecimal and its method divideAndRemainder().

Sjoerd
  • 74,049
  • 16
  • 131
  • 175
1

If you know the amount of decimals you're dealing with, you could try to convert to integers first. This is just a classic case of floating point inacuraccy. Instead of doing 3.9 % 0.1 you're doing something like 3.899 % 0.0999

Wolph
  • 78,177
  • 11
  • 137
  • 148
  • 1
    And Google will claim that 3.9 % 0.1 = -4.4408921 × 10-16 which doesn't quite fit in with modulus either. – Stroboskop Jul 12 '10 at 09:49