Go: proposal: math: add `IsDivisible(float64, float64) bool`

Created on 2 Jul 2018  路  12Comments  路  Source: golang/go

In the application I'm working on, I came across a requirement to find out whether one float was divisible by another. I tried doing this by using the math.Mod and math.Remainder method to check if the result == 0.0.

package main

import "fmt"
import "math"

func main() {
  fmt.Println(math.Remainder(2000.0, 0.01))
  fmt.Println(math.Mod(2000.0, 0.01))
}

As you can see in this playground sample, neither of those work perfectly. While I was able to hack away the problem by a rather cumbersome hack, I think it would be pretty useful if the go standard library provides a method to check if a float is divisible by another.

FrozenDueToAge Proposal

Most helpful comment

What about something like x := f1 / f2; if math.Floor(x) == x { /* divisible */}. Is there a better way to write IsDivisible?

All 12 comments

What about something like x := f1 / f2; if math.Floor(x) == x { /* divisible */}. Is there a better way to write IsDivisible?

@ianlancetaylor possibly not, quite a simple solution too, thanks!

I would still like to keep this proposal open to discuss if it's worth incorporating that logic(or any other according to some standard) into the go standard library as a nice-to-have feature if the proposal is accepted, though.

I hope there is a better way, because it returns false negatives. The correct answer below is true because 11.1 / 0.1 = 111 and Floor(111) == 111, but the implementation returns false.

package main

import . "math"

func Divides(f1, f2 float64) bool {
    x := f1 / f2
    return Floor(x) == x // 2 != 2.9999999999999996

}

func main() { println(Divides(11.1, 0.1)) }

(For bike shedding purposes, I also dislike the name IsDivisible)

The hacky solution I use can be found here

For verbosity's sake, here's the solution:

func IsDivisible(num float64, den float64) bool {
    partsA := strings.Split(strconv.FormatFloat(num, 'f', -1, 64), ".")
    partsB := strings.Split(strconv.FormatFloat(den, 'f', -1, 64), ".")
    maxDecimalPlaces := 0.0
    if len(partsA) == 2 {
        maxDecimalPlaces = float64(len(partsA[1]))
    }
    if len(partsB) == 2 {
        maxDecimalPlaces = math.Max(float64(len(partsB[1])), maxDecimalPlaces)
    }
    n := num * math.Pow(10, maxDecimalPlaces)
    d := den * math.Pow(10, maxDecimalPlaces)
    return int64(n)%int64(d) == 0
}

I know it's not the most performant or efficient one, but it hasn't given me any false positives yet.

@AyushG3112 that panics on inputs like 3, 1/3
https://play.golang.org/p/oIE-wn8c__N

@as ah I see. The source for my inputs is a JSON so I never tested that.

@as actually, 1/3 is evaluated to 0 because 1 and 3 and ints, that's why it panics. 1.0/3.0 returns false.

@AyushG3112 good catch, I don't think a function operating on float64 values should panic though. In this implementation, the operation q := a/d sets q to math.NaN if d == 0. We only see a panic on integer values, not floats.

Also Divides(0, n) returns true (try n = 144), which is questionable.

@as technically, 0 is divisible by all numbers, so Divides(0, n) returning true is OK I believe.

Maybe we could treat the denominator being 0 as a special case, and change the method signature to:

func Divides(float64, float64) bool, error

and return an error if needed.

The IEEE754 floating point number 11.1 is not divisible by the IEEE754 floating point number 0.1

You are discussing about a function that is supposed to work with Go's float64 types, but also expect the same results you would get if you were manipulating purely ideal Real numbers. This is a bad idea.

@AyushG3112

technically, 0 is divisible by all numbers, so Divides(0, n) returning true is OK I believe.

By that definition 0|0

@ALTree
Isn't this also true for 2000.0? The number 0.1 is not a machine number.
fmt.Printf("%.55f\n", float64(0.1)) // 0.1000000000000000055511151231257827021181583404541015625

It doesn't seem like this proposal is reasonable without some kind of fuzz parameter

This is not well-defined, as the commenters have pointed out. IsDivisible couldn't possibly do better than math.Mod(x,y) == 0, which you can already write. (And doesn't give the right answer for 2000, 0.01, because 0.01 is more precisely 0.01000000000000000020816681711721685132943093776702880859375.

Was this page helpful?
0 / 5 - 0 ratings