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.

Title: Clarify docs for math.log1p()
Type: Stage: resolved
Components: Documentation Versions: Python 3.9, Python 3.8
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: docs@python Nosy List: docs@python, mark.dickinson, rhettinger, steven.daprano, tim.peters
Priority: low Keywords:

Created on 2019-06-30 22:19 by rhettinger, last changed 2022-04-11 14:59 by admin. This issue is now closed.

File name Uploaded Description Edit rhettinger, 2019-06-30 22:19 Accuracy tests
Messages (6)
msg346942 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2019-06-30 22:19
Currently the docs say, "The result is calculated in a way which is accurate for x near zero."

That is somewhat vague.  Some quick tests show that it is often more accurate than log() for the whole range of 0.0 < x < 1.0; however, for x < 0.0 it is always worse.

Since the function is just a pass-through to the C library, we probably can't make any guarantees.  On the other hand, it is hard to know when this function is preferred without some guidance.
msg346966 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2019-07-01 07:00
> however, for x < 0.0 it is always worse

That's quite surprising. Which platform? And can you give some example inputs and outputs? With a good math library, I'd expect `log1p(x)` to almost always be at least as accurate as `log(1 + x)`, and substantially _more_ accurate for small x (say abs(x) < 1e-5). If that's not happening, we may want to substitute our own implementation for the system implementation on some platforms (in the same was as we did for lgamma, erf, and friends).
msg346969 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2019-07-01 07:18
Ah, now I've looked at the script. There's an issue with using `random.random()` to create "small" values for testing, since its result is always an integer multiple of 2**-53. That means in particular that if x = random.random(), then 1 - x is always *exactly* representable (and 1 + x is also exactly representable approximately half of the time), so there's no loss of accuracy in the intermediate step of computing log(1 + x) if x = -random.random().

Here's what I get if I run your script exactly as it stands (Python 3.7.3, macOS 10.14.5)

    mirzakhani:Desktop mdickinson$ python
    Counter({'equal': 51839, 'offset_log': 41988, 'regular_log': 6173})
    Counter({'equal': 93727, 'regular_log': 6273})

But if I replace each `random.random()` call with `1e-3 * random.random()`, in order to test small values of `x`, here's what I get:

    mirzakhani:Desktop mdickinson$ python
    Counter({'offset_log': 99945, 'equal': 55})
    Counter({'offset_log': 99893, 'equal': 107})
msg347034 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2019-07-01 15:38
Mark's analysis is spot-on - good eye :-)

Here under 3.7.3 [MSC v.1916 64 bit (AMD64)] on win32, in the original script it makes no difference at all for negative "small x" (where, as Mark said, `1 - random.random()` is exactly representable):

Counter({'equal': 50058, 'offset_log': 41936, 'regular_log': 8006})
Counter({'equal': 100000})

Changing to Mark's 1e-3 * random.random():

Counter({'offset_log': 99958, 'equal': 42})
Counter({'offset_log': 99884, 'equal': 116})

And going on to use 1e-6 instead:

Counter({'offset_log': 100000})
Counter({'offset_log': 100000})
msg350257 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2019-08-23 06:59
Sometimes you guys make me feel dumb as a rock.  Sorry for the distraction.
msg350305 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2019-08-23 15:08
> Sometimes you guys make me feel dumb as a rock.

I expect we all do that favor for each other at times ;-)
Date User Action Args
2022-04-11 14:59:17adminsetgithub: 81635
2019-08-23 15:08:15tim.peterssetmessages: + msg350305
2019-08-23 06:59:20rhettingersetstatus: open -> closed
resolution: not a bug
messages: + msg350257

stage: resolved
2019-07-01 15:38:47tim.peterssetmessages: + msg347034
2019-07-01 14:36:19steven.dapranosetnosy: + steven.daprano
2019-07-01 07:18:55mark.dickinsonsetmessages: + msg346969
2019-07-01 07:00:32mark.dickinsonsetmessages: + msg346966
2019-06-30 22:19:38rhettingercreate