Skip to content

proposal: math: add IsDivisible(float64, float64) bool #26181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
AyushG3112 opened this issue Jul 2, 2018 · 12 comments
Closed

proposal: math: add IsDivisible(float64, float64) bool #26181

AyushG3112 opened this issue Jul 2, 2018 · 12 comments

Comments

@AyushG3112
Copy link

AyushG3112 commented Jul 2, 2018

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.

@gopherbot gopherbot added this to the Proposal milestone Jul 2, 2018
@ianlancetaylor
Copy link
Member

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

@AyushG3112
Copy link
Author

@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.

@as
Copy link
Contributor

as commented Jul 7, 2018

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)

@AyushG3112
Copy link
Author

AyushG3112 commented Jul 7, 2018

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.

@as
Copy link
Contributor

as commented Jul 7, 2018

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

@AyushG3112
Copy link
Author

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

@AyushG3112
Copy link
Author

AyushG3112 commented Jul 7, 2018

@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.

@as
Copy link
Contributor

as commented Jul 7, 2018

@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.

@AyushG3112
Copy link
Author

AyushG3112 commented Jul 7, 2018

@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.

@ALTree
Copy link
Member

ALTree commented Jul 7, 2018

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.

@as
Copy link
Contributor

as commented Jul 9, 2018

@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

@rsc
Copy link
Contributor

rsc commented Jul 9, 2018

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.

@rsc rsc closed this as completed Jul 9, 2018
@golang golang locked and limited conversation to collaborators Jul 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants