This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: int(math.log(4,2)) == 1 instead of 2
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.2
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: mark.dickinson Nosy List: akira, mark.dickinson, rhettinger
Priority: low Keywords: patch

Created on 2010-09-27 13:23 by akira, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
math_log_power_two.diff akira, 2010-09-27 18:14
test_log_power_two.py akira, 2010-09-27 18:15 Find n such as int(math.log(2**n, 2)) != n
Messages (7)
msg117444 - (view) Author: Akira Li (akira) * Date: 2010-09-27 13:23
$ python3.1 -c'import math; f = math.log(4,2); print(int(f), f.as_integer_ratio())'
2 (2, 1)

$ python3.2 -c'import math; f = math.log(4,2); print(int(f), f.as_integer_ratio())'
1 (9007199254740991, 4503599627370496)

Python 3.2a2+ (py3k:85028, Sep 27 2010, 16:46:11) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
>>> f = math.log(32, 2)
>>> f
4.999999999999999
>>> int(f)
4
>>> f.as_integer_ratio()
(5629499534213119, 1125899906842624)

I'm not sure whether it is a bug, but it is an unexpected change in behavior between 3.1 and 3.2.
msg117447 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-09-27 14:09
No, it's not really a bug:  math.log(x, 2) isn't an atomic operation: it's computed internally as something like log(x) / log(2), and since each of the three steps (computation of the logs, division) can introduce a small rounding error, you shouldn't be surprised if the result doesn't come out exactly.

The difference between 3.1 and 3.2 was a side-effect of a change to remove the dependence of the math module on the internal long integer representation.

Having said that, it would be possible to improve the algorithm that's used to compute log of an integer (see the loghelper function in Modules/mathmodule.c): currently, for positive k, log(2**k) ends up being computed as log(0.5) + (k+1)*log(2), which doesn't help;  it would be slightly better if it were computed directly as k*log(2) instead.  But you still can't (and shouldn't) rely on log(x, 2) giving exact results for powers of 2.

I'm not sure what you're using math.log(x, 2) for, but you may be interested in the int.bit_length method.
msg117457 - (view) Author: Akira Li (akira) * Date: 2010-09-27 18:14
> No, it's not really a bug: math.log(x, 2) isn't an atomic operation:

It is not a bug, but values of math.log(4) differs between 3.1 and 3.2
i.e., math.log(4.0) in 3.2 returns value that is consistent with
math.log(4) in 3.1 but math.log(4) in 3.2 doesn't.

> I'm not sure what you're using math.log(x, 2) for, but you may be
interested in the int.bit_length method.

The docs for int.bit_length say: 

> if x is nonzero, then x.bit_length() is the unique positive integer
k such that 2**(k-1) <= abs(x) < 2**k. Equivalently, when abs(x) is
small enough to have a correctly rounded logarithm, then k = 1 +
int(log(abs(x), 2)).

It is expected that int(log(n,2)) produces correct result for small n.

> Having said that, it would be possible to improve the algorithm
 that's used to compute log of an integer (see the loghelper function
 in Modules/mathmodule.c): currently, for positive k, log(2**k) ends
 up being computed as log(0.5) + (k+1)*log(2), which doesn't help; it
 would be slightly better if it were computed directly as k*log(2)
 instead.  But you still can't (and shouldn't) rely on log(x, 2)
 giving exact results for powers of 2.

I've changed loghelper() to return improved result for power of 2
msg117470 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2010-09-27 20:28
It is not a reasonable request for math float functions to produce exact integer values and there is some harm in making further alterations to the existing algorithm (the more you tweak it in one place, the more you create oddities somewhere else).

There may be a case for a separate math.log2() function but this has never been requested before, so it is a low priority.

If the docs for bit_length() bug you, I can change them.  They are meant to be the exact math definition of bit_length, not an executable exact result using limited precision floating point.   Those docs are trying to inform users about bit_length() and are not making a promise about the behavior of math.log().

If you're wanting exact log results for integer inputs and bases, the math module is going to disappoint you no matter what we do.  This is a limited-precision-floating-point fact of life.
msg117507 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-09-28 07:22
I've tweaked the loghelper algorithm in r85048.

Looking at [n for n in range(100) if log(2**n) != n], I get:

Python 3.1:  14 bad values out of 1st 100;  first is 29
Python 3.2 (patched): 10 bad values;  first is 29
Python 3.2 (unpatched): 25 bad values; first is 2.
msg117508 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-09-28 07:23
> [n for n in range(100) if log(2**n) != n]

Should be:

[n for n in range(100) if log(2**n, 2) != n]
msg117645 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-09-29 19:14
Applied further tweaks in r85120:  for an integer n, log(n) is now computed as log(float(n)), provided only that n is small enough to be converted to a float without overflow.  This puts log on a more equal footing with all the other math module functions, and satisfies the (reasonable, IMO) expectation that log(n) == log(float(n)) for small integers n.

As a nice side-effect, this change guarantees that on a machine where log10 has reasonable accuracy (e.g., accurate to within 0.9 ulps or so), log10(10**n)==n for any nonnegative integer n such that 10**n is within the range of a double.
History
Date User Action Args
2022-04-11 14:57:06adminsetgithub: 54168
2010-09-29 19:14:07mark.dickinsonsetmessages: + msg117645
2010-09-28 07:23:40mark.dickinsonsetmessages: + msg117508
2010-09-28 07:22:44mark.dickinsonsetstatus: open -> closed
resolution: fixed
messages: + msg117507
2010-09-27 20:28:28rhettingersetpriority: normal -> low
nosy: + rhettinger
messages: + msg117470

2010-09-27 18:38:49akirasettype: behavior -> enhancement
2010-09-27 18:15:54akirasetfiles: + test_log_power_two.py
2010-09-27 18:14:22akirasetfiles: + math_log_power_two.diff
keywords: + patch
messages: + msg117457
2010-09-27 14:09:22mark.dickinsonsetassignee: mark.dickinson

messages: + msg117447
nosy: + mark.dickinson
2010-09-27 13:23:02akiracreate