1

According to the comment description for Double.rem:

    /**
     * Calculates the remainder of truncating division of this value by the other value.
     * 
     * The result is either zero or has the same sign as the _dividend_ and has the absolute value less than the absolute value of the divisor.
     */
    @SinceKotlin("1.1")
    public operator fun rem(other: Double): Double

However, when I run the following code:

fun main() {
    println(10.0 % 0.2)
}

It outputs 0.19999999999999946 when it should output 0.0. How do I run an accurate rem for doubles?

Note: try it yourself here

Matt Groth
  • 33
  • 4
  • 1
    This is just a classic `Double` issue, you should have a look here: https://stackoverflow.com/questions/322749/retain-precision-with-double-in-java (it is Java but it applies to Kotlin and other languages too) – Joffrey Jun 05 '21 at 12:19
  • I can't believe I've been writing java for years and never knew about this. Thank you. – Matt Groth Jun 05 '21 at 12:25
  • 1
    Also [this question](/questions/588004/is-floating-point-math-broken). – gidds Jun 05 '21 at 13:55

1 Answers1

4

Floats and doubles are by design imprecise. They can only hold and calculate approximations of values.

If you need precision similar to regular ints and use them with fractions, you can use BigDecimal:

BigDecimal("10.0") % BigDecimal("0.2")

Just note it is sometimes more complicated and you need to understand their properties like: scale, rounding mode, etc. For your case I believe above code should be sufficient.

broot
  • 21,588
  • 3
  • 30
  • 35
  • This is the perfect answer, thanks. I understand what rounding mode is but I don't know what you are referring to by "scale". Could you please elaborate on that? – Matt Groth Jun 05 '21 at 12:27
  • 2
    It is described in the documentation of `BigDecimal`. Simply saying, BigDecimals are internally similar to floats/doubles, they get imprecise results for math operations, but they are smart at guessing, what is your preferred decimal precision. In your case, rem was probably also 0.1999..., but BigDecimal guessed that it should round the fraction to a single digit and then remove it entirely. It guessed such because input values had similar precision. – broot Jun 05 '21 at 12:42
  • 2
    I added a warning in my answer, because BigDecimals aren't fully universal utils for calculating anything with guaranteed 100% precision. Sometimes, we need to manually control precision and other parameters. In your case I believe we don't need this. – broot Jun 05 '21 at 12:44
  • 1
    The problem with floats and doubles isn't just that they're imprecise; it's also that what they _are_ precise about — binary fractions — is mostly invisible to us.  What we know — and what we see in computer programs and output — is _decimal_ fractions.  I think most of us understand why you can't represent 1/3 as a finite decimal fraction; it's much less obvious that you can't represent 1/10 as a finite binary fraction — which is why you can't store even short decimals like 0.1 exactly in a float or double field. – gidds Jun 05 '21 at 14:02
  • 1
    @broot But you _can_ control the precision and scale of `BigDecimal`s.  As [the docs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/BigDecimal.html) say, it “gives its user complete control over rounding behavior. If no rounding mode is specified and the exact result cannot be represented, an exception is thrown; otherwise, calculations can be carried out to a chosen precision and rounding mode by supplying an appropriate MathContext object to the operation.” – gidds Jun 05 '21 at 14:06
  • 1
    (…In fact, the main reason for imprecise BigDecimals is creating them from floats or doubles which are _already_ compromised.  I'm pleased to see that this answer creates them from strings, avoiding that problem!) – gidds Jun 05 '21 at 14:08