Python floating point is divisible by another floating point

Question:

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?

Asked By: gbtimmon

||

Answers:

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')
Answered By: Peter DeGlopper

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)
Answered By: asky

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.
Answered By: Vaalsta
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.