gbtimmon
gbtimmon

Reputation: 4332

Python floating point is divisible by another floating point

Does anyone know a good way in Python to check if a number is divisible by another in floating point in python?

The first thing I tried was ...

3.5 % 0.1 == 0.0

But this returns False so then maybe

3.5 % 0.1 >= 1e-6 

But also False ... bummer ... it turns out that

3.5 % 0.1
>> 0.099999999924

So then this works:

LAMBDA = 1e-9
def is_divisible_by(x, y):
   m = x % y
   dy = abs(y - m)
   return m < LAMBDA or dy < LAMBDA

is_divisible_by(3.5, 0.1)

But this seems dangerous because I have to pick a LAMBDA. What about if y = LAMBDA / 2...

is_divisible_by(LAMBDA/2, (LAMBDA/2) + 1e-10)
>>> True

So then

  def is_divisible_by(x, y):
      l = y * 1e-2
      m = x % y
      dy = abs(y - m)
      return m < l or dy < l

  is_divisible_by(3.5 * 1e-10, 0.1 * 1e-10)
  >>> True

  is_divisible_by(0.21, 0.211)
  >>> True
  

Bummer.

Is there anyway to solve this without going down a massive rabbit hole?

Upvotes: 3

Views: 1901

Answers (3)

Vaalsta
Vaalsta

Reputation: 31

A potential solution is to multiply both floats by a "sufficiently large" scaling factor and then cast (round) to integers.

def check_floats_divisible(x: float, y: float, scaling_factor: float = 1e6):
    scaled_x = int(x * scaling_factor)
    scaled_y = int(y * scaling_factor)
    
    return (scaled_x % scaled_y) == 0

check_floats_divisible(3.5, 0.1)
>>> True

Notes:

  1. "Sufficiently large" may not be appropriate for your use case. It works well if, for example your two floats were time durations in seconds, and a particular time precision was acceptable in your use-case (e.g. microseconds).
  2. Need to be careful your scaling is sufficient to ensure the denominator is never zero after conversion to int.

Upvotes: 1

asky
asky

Reputation: 1742

floating point numbers are "fuzzy". A good high-level mental model for floating point numbers is that they represent a small range of numbers (e.g 1.5 really means some number between 1.4999 and 1.5002). Because of this, there is not good way to check if one is divisible by another. Instead, to check if non-integer numbers are divisible by each other, you might want to use rational-type numbers. In python, there's a module for this, called Fraction. You can use it like this

from fractions import Fraction
a = Fraction(35,10) # 3.5
b = Fraction(1,10) # .1
a%b # evaluates to Fraction(0, 1)

Another answer mentioned the decimal python module. Fraction and decimal are interoperable.

from fractions import Fraction
from decimal import Decimal
a = Fraction(Decimal('3.5')) # 3.5
b = Fraction(Decimal('0.1)) # 0.1
a%b # evaluates to Fraction(0, 1)

I'm going to advocate for using Fraction as it is a bit more flexible

from fractions import Fraction
from decimal import Decimal
c = Decimal('1')
d = Decimal('.3')
c/d  # evaluates to Decimal('3.333333333333333333333333333')
c/d*d  # evaluates to Decimal('0.9999999999999999999999999999')
c = Fraction(c)
d = Fraction(d)
c/d # evaluates to Fraction(10, 3)
c/d*d # evaluates to Fraction(1, 1)

Upvotes: 3

Peter DeGlopper
Peter DeGlopper

Reputation: 37364

Depending on the source of your floating point numbers, the decimal module might be useful.

>>> import decimal
>>> decimal.Decimal("3.5") % decimal.Decimal("0.1")
Decimal('0.0')

Upvotes: 6

Related Questions