watsonic
watsonic

Reputation: 3373

Python - safe conversion of quotient of floats to int

In my situation I am attempting to divide one float p by another q. The top is a multiple of the bottom and both have these properties:

  1. Exactly representable in decimal
  2. Have at most 3 or 4 significant figures
  3. Are between 1 and 1e-8.

(think, e.g., p=.0014 and q=.00002)

In a perfect world the division would come out to a perfect integer (here 70). But floating point arithmetic is often imperfect.

I would like the simplest, safest, and most efficient method to avoid an error of returning p/q - 1 when I cast the quotient to int.

My best solution right now is to do something like this:

int(p/q + 1e-10)

but that feels unclean and potentially less efficient that what may be possible.

Also, I am aware I can round, but that seems misleading in the code and potentially less efficient than a straight cast of some sort.

Upvotes: 3

Views: 1372

Answers (4)

watsonic
watsonic

Reputation: 3373

It appears that by far the most straightforward solution is to round to int:

int(round(p/q))

perhaps accompanied on the line by a short comment noting that p is a multiple of q to avoid the wayward implication that p/q is something with potentially significant distance from an integer.

Note that this solution is guaranteed to be perfectly safe since the cast via int() in this circumstance acts on a float which will be an exact representation of an integer, as returned by round(). This exactness is guaranteed up to 253 according to the IEEE floating point standard for double precision which Python float complies with.

It may be microscopically less efficient than what could be cooked up otherwise (a la the suggestion in the question), but certainly more efficient than processing through decimal or fractions modules. And probably on par with other solution that employed an extra multiplication and addition.

Upvotes: 1

watsonic
watsonic

Reputation: 3373

Working from an idea in the comment to the question, here is a solution through decimal:

from decimal import Decimal

p = .0014
q = .00002

quotient = int(Decimal(str(p)) / Decimal(str(q)))

which of course results in 70.

Note that the conversion through string appears necessary because of this:

>>>print decimal.Decimal(8.4)
8.4000000000000003552713678800500929355621337890625

whereas

>>>print decimal.Decimal(str(8.4))
8.4

Upvotes: 3

Aaron Hall
Aaron Hall

Reputation: 394955

How you handle these up the point you are doing the division is up to you. Perhaps you should be using Decimal or Fraction objects up to that point, but at the point of evaluating division, Python provides a module for that:

>>> import fractions
>>> fractions.Fraction(.0014/.00002)
Fraction(70, 1)
>>> int(fractions.Fraction(2.3))
2
>>> int(fractions.Fraction(8.35))
8

But after a careful reading of your question, I think your worries are not warranted. If you try to think of a fraction where, due to a rounding error, you would be below an integer that, if you could calculate with higher precision, you would be above, you can't.

For example, there's no way the fraction of numbers given below will ever round below 1:

>>> fractions.Fraction(1.000000000000001)
Fraction(4503599627370501, 4503599627370496)

In a comment, someone suggested arriving at a dividend that is no where near the 1.64. How he arrived at that, he doesn't say, but as I said in my introduction, how you calculate up to the point of division is up to you.

Upvotes: 2

tmyklebu
tmyklebu

Reputation: 14205

Floating-point division will give an exact answer if the numerator is a multiple of the denominator and the quotient is exactly representable. So dividing the top by the bottom is safe if that's what you're trying to do.

Often, however, you're working with numbers that have been converted from decimal or are the results of some computation. In these cases, you need to figure out how much error can occur in the computation (relative error of 1.11e-16 is a safe bet for conversion from decimal unless the numbers are really tiny) and scale the result up by that before converting to integer.

That is, int((top / bot) * (1 + 2.22e-16)) ought to do what you want when top and bot are in a reasonable range.

Upvotes: 2

Related Questions