Home > front end >  What is the best practice to avoid precision error while taking floor on math.log?
What is the best practice to avoid precision error while taking floor on math.log?

Time:10-25

Recently we encountered an issue with math.log() . Since 243 is a perfect power of 3 , assumption that taking floor should be fine was wrong as it seems to have precision error on lower side. So as a hack we started adding a small value before taking logarithm. Is there a way to configue math.log upfront or something similar that that we dont have to add EPS every time.

To clarify some of the comments Note we are not looking to round to nearest integer. Our goal is to keep the value exact or at times take floor. But if the precision error is on lower side floor screws up big time, that's what we are trying to avoid.

code:

import math
math.log(243, 3)
int(math.log(243, 3))

output:

4.999999999999999
4

code:

import math
EPS = 1e-09
math.log(243   EPS, 3)
int(math.log(243   EPS, 3))

output:

5.0000000000037454
5

CodePudding user response:

Instead of trying to solve log it might be easier to look at pow and just solve this iteratively, taking advantage of Python's integer type. This way you can avoid the float domain, and its associated precision loss, entirely.

Here's a rough attempt:

def ilog(a: int, p: int) -> tuple[int, bool]:
    """
    find the largest b such that p ** b <= a
    return tuple of (b, exact)
    """
    if p == 1:
        return a, True
    b = 0
    x = 1
    while x < a:
        x *= p
        b  = 1
    if x == a:
        return b, True
    else:
        return b - 1, False

There are plenty of opportunities for optimization if this is too slow (consider Newton's method, binary search...)

CodePudding user response:

You can use decimals and play with precision and rounding instead of floats in this case

Like this:

from decimal import Decimal, Context, ROUND_HALF_UP, ROUND_HALF_DOWN
ctx1 = Context(prec=20, rounding=ROUND_HALF_UP)
ctx2 = Context(prec=20, rounding=ROUND_HALF_DOWN)

ctx1.divide(Decimal(243).ln( ctx1) , Decimal(3).ln( ctx2))

Output:

Decimal('5')

First, the rounding works like the epsilon - the numerator is rounded up and denominator down. You always get a slightly higher answer

Second, you can adjust precision you need

However, fundamentally the problem is unsolvable.

  • Related