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: math.log of a long returns a different value of math.log of an int
Type: behavior Stage:
Components: Versions: Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: mark.dickinson Nosy List: christian.heimes, gregory.p.smith, mark.dickinson, python-dev, tim.peters
Priority: normal Keywords: patch

Created on 2013-08-14 15:04 by gregory.p.smith, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
issue18739.patch mark.dickinson, 2013-08-15 19:47 review
Messages (11)
msg195175 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2013-08-14 15:04
This is a very odd inconsistency in math.log behavior.  That said, it is probably only a single bit imprecision at the end of the float result.  Still, 10 == 10L so I'd expect math.log of both to give the same result.

oss/cpython/2.7:LOAS$ ./python
Python 2.7.5+ (2.7:395ac61ebe1a, Aug 14 2013, 07:11:35) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
>>> math.log(10) - math.log(10L)
4.440892098500626e-16
>>> math.log(10)
2.302585092994046
>>> math.log(10L)
2.3025850929940455
>>> 

In Python 3.3 things seem fine and match the int behavior from 2.7 above despite the internal number implementation being a PyLong in 3.x:

>>> math.log(10)
2.302585092994046
msg195176 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2013-08-14 15:05
Fascinating, how did you find the issue?
msg195185 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2013-08-14 16:48
Yup - 2.7 evaluates this in a less precise way, as

log(10L) = log(10./16 * 2**4) = log(0.625) + log(2)*4

>>> log(10L) == log(0.625) + log(2)*4
True

This patterns works well even for longs that are far too large to represent as a double; e.g.,

>>> log(1L << 50000)
34657.35902799726

which is evaluated internally as log(0.5) + log(2) * 50001:

>>> log(1L << 50000) == log(0.5) + log(2) * 50001
True

Python 3 is more careful, falling back to this pattern _only_ if converting the long to a double overflows.  Of course 10L can be represented exactly as a double, so Python 3 evaluates it directly as log(float(10L)) = log(10.0).  It's minor difference overall, but definitely visible ;-)
msg195187 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2013-08-14 17:03
We found this while refactoring an API to be more consistent about returning longs vs ints in specific cases.  This resulted in another unittest that was using math.log for a nicer display of the value by magnitude to fail as the result was slightly different for the two input types.

arguably that is a fragile test as it cares about the last couple bits of a float... but that such a test exists does suggest that making a future 2.7.6+ release more pedantic about the behavior on values that safely fit into a double might break an odd test or three out there.  OTOH, it makes things more consistent all around and match 3.x in both cases so I'm still be for doing it.
msg195193 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2013-08-14 18:47
FYI, the improvement was made in these 2 changesets:

c8dc4b5e54fb
a9349fd3d0f7

If desired, those could presumably be grafted back into the 2.7 branch.

The commit messages refer to issue #9599, but that appears to be mistaken.
msg195195 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2013-08-14 18:50
OK, the correct one is issue #9959.
msg195246 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-08-15 11:24
Ah, Tim saw through my cunningly-laid false trail of incorrect issue numbers.  Step 1 of my world domination plan is foiled!

Yes, let's fix this.  In my mind, it's definitely a bug that ints and longs aren't interchangeable here, and it would be nice to have 2.7 and 3.x behaving the same way.  I do have some worries about the fix breaking / changing existing code, but given that it'll only affect log of a long, those worries barely register.
msg195274 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2013-08-15 18:38
+1 on fixing it in 2.7, for the reasons Mark gave.

Way back when I introduced the original scheme, log(a_long) raised an exception, and the `int` and `long` types had a much higher wall between them.  The original scheme changed an annoying failure into a "pretty good" approximation, and because int operations never returned a long back then the relatively rare code using longs forced their use.

In the meantime, you can change failing cases of log(some_long) to log(int(some_long)).  int(some_long) will convert to int if possible (avoiding this path in the log code), but return some_long unchanged otherwise.  In the old days, int(some_long) would fail if some_long was too big to fit in an int - that's different now too - and for the better :-)
msg195280 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-08-15 19:47
Here's a patch that simply backports the Python 3.x code to Python 2.7.
msg199681 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2013-10-13 09:55
New changeset f543863f4e43 by Mark Dickinson in branch '2.7':
Issue #18739: Fix inconsistent results from math.log(n) and math.log(long(n))
http://hg.python.org/cpython/rev/f543863f4e43
msg199682 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-10-13 09:56
Patch applied.
History
Date User Action Args
2022-04-11 14:57:49adminsetgithub: 62939
2013-10-13 09:56:13mark.dickinsonsetstatus: open -> closed
resolution: fixed
messages: + msg199682
2013-10-13 09:55:55python-devsetnosy: + python-dev
messages: + msg199681
2013-08-15 19:47:55mark.dickinsonsetassignee: mark.dickinson
2013-08-15 19:47:47mark.dickinsonsetfiles: + issue18739.patch
keywords: + patch
messages: + msg195280
2013-08-15 18:38:23tim.peterssetmessages: + msg195274
2013-08-15 11:24:15mark.dickinsonsettype: behavior
messages: + msg195246
2013-08-14 18:50:14tim.peterssetmessages: + msg195195
2013-08-14 18:47:18tim.peterssetmessages: + msg195193
2013-08-14 17:03:29gregory.p.smithsetmessages: + msg195187
2013-08-14 16:48:58tim.peterssetnosy: + tim.peters
messages: + msg195185
2013-08-14 15:05:49christian.heimessetnosy: + mark.dickinson, christian.heimes
messages: + msg195176
2013-08-14 15:04:47gregory.p.smithcreate