classification
Title: Add math.ulp(x): unit in the last place
Type: Stage: resolved
Components: Library (Lib) Versions: Python 3.9
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: brett.cannon, mark.dickinson, miss-islington, rhettinger, steven.daprano, stutzbach, tim.peters, vstinner
Priority: normal Keywords: patch

Created on 2020-01-12 12:33 by vstinner, last changed 2020-01-15 22:50 by tim.peters. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 17965 merged vstinner, 2020-01-12 12:38
PR 17982 merged vstinner, 2020-01-13 11:48
PR 17994 merged miss-islington, 2020-01-13 19:24
Messages (14)
msg359846 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2020-01-12 12:33
In bpo-39288, I added math.nextafter(x, y) function. I propose to now add math.ulp() companion function.

Examples from tests of my PR:

    self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
    self.assertEqual(math.ulp(2.0 ** 52), 1.0)
    self.assertEqual(math.ulp(2.0 ** 53), 2.0)
    self.assertEqual(math.ulp(2.0 ** 64), 4096.0)

Unit in the last place:

* https://en.wikipedia.org/wiki/Unit_in_the_last_place
* Java provides a java.lang.Math.ulp(x) function: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#ulp-double-

In numpy, I found two references to ULP:

* numpy.testing.assert_array_almost_equal_nulp:
https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.testing.assert_array_almost_equal_nulp.html

* numpy.testing.assert_array_max_ulp:
https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.testing.assert_array_max_ulp.html

Attached PR implements math.ulp(x).
msg359859 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2020-01-12 17:45
See also math.isclose() and its PEP 485 which mentions ULP (Unit in the Last Place)... in the "Inappropriate uses" section :-)

* https://docs.python.org/dev/library/math.html#math.isclose
* https://www.python.org/dev/peps/pep-0485/#inappropriate-uses

Extract of an old python-ideas discussion on adding nextafter():

"It's also a little weird to jump from nextafter to isclose, since AFAIK
they have pretty much non-overlapping use cases..."

https://mail.python.org/pipermail/python-ideas/2017-February/044832.html

Other links:

* https://en.wikipedia.org/wiki/Unit_in_the_last_place
* https://en.wikipedia.org/wiki/Machine_epsilon
* https://en.wikipedia.org/wiki/IEEE_754
msg359882 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2020-01-13 02:01
+1
msg359883 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2020-01-13 02:29
Thank you Victor!

Any chance you could look at fma too? #29282

(The reward for a job well done is to be given more jobs :-)
msg359891 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2020-01-13 07:53
[Steven]

> Any chance you could look at fma too? #29282

fma is hard, for reasons explained in the issue you linked to. If you have suggestions for resolving the difficulties, please do add them to that issue.
msg359898 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2020-01-13 11:44
New changeset 0b2ab21956fbab8eab6d064060d4544499730316 by Victor Stinner in branch 'master':
bpo-39310: Add math.ulp(x) (GH-17965)
https://github.com/python/cpython/commit/0b2ab21956fbab8eab6d064060d4544499730316
msg359927 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2020-01-13 19:24
New changeset 9362f8526e42157baf27df982b16f23f212c3c3a by Victor Stinner in branch '3.8':
bpo-39310: Update sys.float_info documentation (GH-17982)
https://github.com/python/cpython/commit/9362f8526e42157baf27df982b16f23f212c3c3a
msg359929 - (view) Author: miss-islington (miss-islington) Date: 2020-01-13 19:30
New changeset dfe159ca552870f801e34cc57e9bb7d6836ce7df by Miss Islington (bot) in branch '3.7':
bpo-39310: Update sys.float_info documentation (GH-17982)
https://github.com/python/cpython/commit/dfe159ca552870f801e34cc57e9bb7d6836ce7df
msg359937 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2020-01-13 23:05
Can I just say that "ulp" is totally non-obvious what that even means unless you have a specific math background?
msg359938 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2020-01-13 23:08
And sorry if that last response from me came off as grumpy; it wasn't meant to. I just had no clue what you were adding based on the name.
msg359939 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2020-01-13 23:32
> Can I just say that "ulp" is totally non-obvious what that even means unless you have a specific math background?

The math.ulp() documentation explicitly says: https://docs.python.org/dev/library/math.html#math.ulp

   ULP stands for “Unit in the Last Place”.

The term "ulp" is commonly used when talking about IEEE 754 floating point numbers. It is used in numpy and Java for example.

test_math.py already had an ulp() function which was a pure Python implementation.

The term is also commonly used in math articles.

If you don't know the term "ulp", it's likely a *good thing*. You didn't have to suffer with rounding issues :-D
msg359940 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2020-01-13 23:51
I hear what you are saying, but "ulp" is the standard term of art. Sure 
it is non-obvious until you learn it, just like other technical terms 
like "mro", "abc" or "ast".

Mathematics and numeric programming are rife with short names that are 
non-obvious and often ambiguous with "ordinary" words, e.g.

    sin, tan, log, nan

to mention just a few. "ulp" is a technical, and subtle, concept to 
grasp, and no easier to understand when spelled out as "unit in last 
place".

At least ulp is a TLA from English, unlike (say) "sine" which ultimately 
derives from the Sanscrit word "jya" (chord), via Arabic and Latin. If 
you've ever wondered if the trigonometric sin() function is related to 
the sinus cavities in your nose, yes it is :-)
msg360032 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2020-01-15 09:31
[Brett]

> Can I just say that "ulp" is totally non-obvious what that even means unless you have a specific math background?

It's a good point. I guess we have a choice between using the domain-specific standard-ish name (which should be immediately meaningful to experts, but doesn't give much of a hint to non-experts) or using something more descriptive (which then risks confusing experts until they figure out "oh, that's just ulp").

There's also the option of spelling it out as "unit_in_last_place", but I'm not sure that benefits anyone.

For meaningful descriptive names, "float_resolution" or "gap_to_next" are about the best I can come up with. "precision" is too ambiguous.

NumPy has "numpy.spacing". But this exhibits exactly the trap of *not* using the "ulp" name: on a first glance, I incorrectly decided that NumPy *didn't* implement a ulp function. Then, having found `numpy.spacing`, I had to read the description carefully in order to recognise that "oh, this is just ulp". (Actually, I had to do more, since the description doesn't make all the corner cases clear, and in fact is currently wrong for powers of 2.)

If we can get people can coalesce around a preferred alternative name, we could consider changing this.
msg360082 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2020-01-15 22:50
`ulp()` is the right name:  universally understood by those who know how to use it, and easy to find exhaustive web explanations for those who don't.

In a different context, spelling out some variant of Hypertext_Transfer_Protocol would be as wrong-headed to avoid the "cryptic" http.
History
Date User Action Args
2020-01-15 22:50:31tim.peterssetmessages: + msg360082
2020-01-15 09:31:56mark.dickinsonsetmessages: + msg360032
2020-01-13 23:51:49steven.dapranosetmessages: + msg359940
2020-01-13 23:32:50vstinnersetmessages: + msg359939
2020-01-13 23:08:14brett.cannonsetmessages: + msg359938
2020-01-13 23:05:01brett.cannonsetnosy: + brett.cannon
messages: + msg359937
2020-01-13 19:30:04miss-islingtonsetnosy: + miss-islington
messages: + msg359929
2020-01-13 19:24:49miss-islingtonsetpull_requests: + pull_request17397
2020-01-13 19:24:15vstinnersetmessages: + msg359927
2020-01-13 11:48:47vstinnersetpull_requests: + pull_request17387
2020-01-13 11:47:40vstinnersetstatus: open -> closed
resolution: fixed
stage: patch review -> resolved
2020-01-13 11:44:49vstinnersetmessages: + msg359898
2020-01-13 07:53:39mark.dickinsonsetmessages: + msg359891
2020-01-13 02:29:16steven.dapranosetnosy: + steven.daprano
messages: + msg359883
2020-01-13 02:01:35tim.peterssetnosy: + tim.peters
messages: + msg359882
2020-01-12 17:45:51vstinnersetmessages: + msg359859
2020-01-12 14:47:21xtreaksetnosy: + rhettinger, mark.dickinson, stutzbach
2020-01-12 12:38:30vstinnersettitle: Add math.ulp(x) -> Add math.ulp(x): unit in the last place
2020-01-12 12:38:19vstinnersetkeywords: + patch
stage: patch review
pull_requests: + pull_request17374
2020-01-12 12:33:34vstinnercreate