diff options
| author | Paul Eggert | 2018-07-18 03:16:54 -0700 |
|---|---|---|
| committer | Paul Eggert | 2018-07-18 03:18:53 -0700 |
| commit | c70d22f70b77b053d01c7380122d166ecb728610 (patch) | |
| tree | 1fe0dee176da15fca2907c84abc7ec096b7e4116 | |
| parent | ba6cc1d04cef8e25534a72e90a8f0f8db0026c9f (diff) | |
| download | emacs-c70d22f70b77b053d01c7380122d166ecb728610.tar.gz emacs-c70d22f70b77b053d01c7380122d166ecb728610.zip | |
Fix bug with eql etc. on NaNs
Fix a bug where eql, sxhash-eql, memql, and make-hash-table
were not consistent on NaNs. Likewise for equal,
sxhash-equal, member, and make-hash-table. Some of these
functions ignored NaN significands, whereas others treated
them as significant. It's more logical to treat significands
as significant, and this typically makes eql a bit more
efficient on floats, with just one integer comparison instead
of one to three floating-point comparisons.
* doc/lispref/numbers.texi (Float Basics): Document that
NaNs are never numerically equal, but might be eql.
* src/fns.c (WORDS_PER_DOUBLE): Move to top level of this file.
(union double_and_words): Now named, and at the top level of this file.
(same_float): New function.
(Fmemql, Feql, internal_equal, cmpfn_eql): Use it, so that
the corresponding functions treat NaNs consistently.
(sxhash_float): Simplify based on above-mentioned changes.
* test/src/fns-tests.el (fns-tests-equality-nan): New test.
| -rw-r--r-- | doc/lispref/numbers.texi | 9 | ||||
| -rw-r--r-- | src/fns.c | 68 | ||||
| -rw-r--r-- | test/src/fns-tests.el | 11 |
3 files changed, 53 insertions, 35 deletions
diff --git a/doc/lispref/numbers.texi b/doc/lispref/numbers.texi index 2fed2b642fd..6c51b849d35 100644 --- a/doc/lispref/numbers.texi +++ b/doc/lispref/numbers.texi | |||
| @@ -232,13 +232,18 @@ distinguish them. | |||
| 232 | @cindex negative infinity | 232 | @cindex negative infinity |
| 233 | @cindex infinity | 233 | @cindex infinity |
| 234 | @cindex NaN | 234 | @cindex NaN |
| 235 | @findex eql | ||
| 236 | @findex sxhash-eql | ||
| 235 | The @acronym{IEEE} floating-point standard supports positive | 237 | The @acronym{IEEE} floating-point standard supports positive |
| 236 | infinity and negative infinity as floating-point values. It also | 238 | infinity and negative infinity as floating-point values. It also |
| 237 | provides for a class of values called NaN, or ``not a number''; | 239 | provides for a class of values called NaN, or ``not a number''; |
| 238 | numerical functions return such values in cases where there is no | 240 | numerical functions return such values in cases where there is no |
| 239 | correct answer. For example, @code{(/ 0.0 0.0)} returns a NaN@. | 241 | correct answer. For example, @code{(/ 0.0 0.0)} returns a NaN@. |
| 240 | Although NaN values carry a sign, for practical purposes there is no other | 242 | A NaN is never numerically equal to any value, not even to itself. |
| 241 | significant difference between different NaN values in Emacs Lisp. | 243 | NaNs carry a sign and a significand, and non-numeric functions like |
| 244 | @code{eql} and @code{sxhash-eql} treat two NaNs as equal when their | ||
| 245 | signs and significands agree. Significands of NaNs are | ||
| 246 | machine-dependent and are not directly visible to Emacs Lisp. | ||
| 242 | 247 | ||
| 243 | Here are read syntaxes for these special floating-point values: | 248 | Here are read syntaxes for these special floating-point values: |
| 244 | 249 | ||
| @@ -1419,6 +1419,29 @@ DEFUN ("elt", Felt, Selt, 2, 2, 0, | |||
| 1419 | return Faref (sequence, n); | 1419 | return Faref (sequence, n); |
| 1420 | } | 1420 | } |
| 1421 | 1421 | ||
| 1422 | enum { WORDS_PER_DOUBLE = (sizeof (double) / sizeof (EMACS_UINT) | ||
| 1423 | + (sizeof (double) % sizeof (EMACS_UINT) != 0)) }; | ||
| 1424 | union double_and_words | ||
| 1425 | { | ||
| 1426 | double val; | ||
| 1427 | EMACS_UINT word[WORDS_PER_DOUBLE]; | ||
| 1428 | }; | ||
| 1429 | |||
| 1430 | /* Return true if X and Y are the same floating-point value. | ||
| 1431 | This looks at X's and Y's representation, since (unlike '==') | ||
| 1432 | it returns true if X and Y are the same NaN. */ | ||
| 1433 | static bool | ||
| 1434 | same_float (Lisp_Object x, Lisp_Object y) | ||
| 1435 | { | ||
| 1436 | union double_and_words | ||
| 1437 | xu = { .val = XFLOAT_DATA (x) }, | ||
| 1438 | yu = { .val = XFLOAT_DATA (y) }; | ||
| 1439 | EMACS_UINT neql = 0; | ||
| 1440 | for (int i = 0; i < WORDS_PER_DOUBLE; i++) | ||
| 1441 | neql |= xu.word[i] ^ yu.word[i]; | ||
| 1442 | return !neql; | ||
| 1443 | } | ||
| 1444 | |||
| 1422 | DEFUN ("member", Fmember, Smember, 2, 2, 0, | 1445 | DEFUN ("member", Fmember, Smember, 2, 2, 0, |
| 1423 | doc: /* Return non-nil if ELT is an element of LIST. Comparison done with `equal'. | 1446 | doc: /* Return non-nil if ELT is an element of LIST. Comparison done with `equal'. |
| 1424 | The value is actually the tail of LIST whose car is ELT. */) | 1447 | The value is actually the tail of LIST whose car is ELT. */) |
| @@ -1457,7 +1480,7 @@ The value is actually the tail of LIST whose car is ELT. */) | |||
| 1457 | FOR_EACH_TAIL (tail) | 1480 | FOR_EACH_TAIL (tail) |
| 1458 | { | 1481 | { |
| 1459 | Lisp_Object tem = XCAR (tail); | 1482 | Lisp_Object tem = XCAR (tail); |
| 1460 | if (FLOATP (tem) && equal_no_quit (elt, tem)) | 1483 | if (FLOATP (tem) && same_float (elt, tem)) |
| 1461 | return tail; | 1484 | return tail; |
| 1462 | } | 1485 | } |
| 1463 | CHECK_LIST_END (tail, list); | 1486 | CHECK_LIST_END (tail, list); |
| @@ -2175,7 +2198,7 @@ Floating-point numbers of equal value are `eql', but they may not be `eq'. */) | |||
| 2175 | (Lisp_Object obj1, Lisp_Object obj2) | 2198 | (Lisp_Object obj1, Lisp_Object obj2) |
| 2176 | { | 2199 | { |
| 2177 | if (FLOATP (obj1)) | 2200 | if (FLOATP (obj1)) |
| 2178 | return equal_no_quit (obj1, obj2) ? Qt : Qnil; | 2201 | return FLOATP (obj2) && same_float (obj1, obj2) ? Qt : Qnil; |
| 2179 | else | 2202 | else |
| 2180 | return EQ (obj1, obj2) ? Qt : Qnil; | 2203 | return EQ (obj1, obj2) ? Qt : Qnil; |
| 2181 | } | 2204 | } |
| @@ -2266,13 +2289,7 @@ internal_equal (Lisp_Object o1, Lisp_Object o2, enum equal_kind equal_kind, | |||
| 2266 | switch (XTYPE (o1)) | 2289 | switch (XTYPE (o1)) |
| 2267 | { | 2290 | { |
| 2268 | case Lisp_Float: | 2291 | case Lisp_Float: |
| 2269 | { | 2292 | return same_float (o1, o2); |
| 2270 | double d1 = XFLOAT_DATA (o1); | ||
| 2271 | double d2 = XFLOAT_DATA (o2); | ||
| 2272 | /* If d is a NaN, then d != d. Two NaNs should be `equal' even | ||
| 2273 | though they are not =. */ | ||
| 2274 | return d1 == d2 || (d1 != d1 && d2 != d2); | ||
| 2275 | } | ||
| 2276 | 2293 | ||
| 2277 | case Lisp_Cons: | 2294 | case Lisp_Cons: |
| 2278 | if (equal_kind == EQUAL_NO_QUIT) | 2295 | if (equal_kind == EQUAL_NO_QUIT) |
| @@ -3706,24 +3723,20 @@ HASH_INDEX (struct Lisp_Hash_Table *h, ptrdiff_t idx) | |||
| 3706 | return XINT (AREF (h->index, idx)); | 3723 | return XINT (AREF (h->index, idx)); |
| 3707 | } | 3724 | } |
| 3708 | 3725 | ||
| 3709 | /* Compare KEY1 which has hash code HASH1 and KEY2 with hash code | 3726 | /* Compare KEY1 and KEY2 in hash table HT using `eql'. Value is true |
| 3710 | HASH2 in hash table H using `eql'. Value is true if KEY1 and | 3727 | if KEY1 and KEY2 are the same. KEY1 and KEY2 must not be eq. */ |
| 3711 | KEY2 are the same. */ | ||
| 3712 | 3728 | ||
| 3713 | static bool | 3729 | static bool |
| 3714 | cmpfn_eql (struct hash_table_test *ht, | 3730 | cmpfn_eql (struct hash_table_test *ht, |
| 3715 | Lisp_Object key1, | 3731 | Lisp_Object key1, |
| 3716 | Lisp_Object key2) | 3732 | Lisp_Object key2) |
| 3717 | { | 3733 | { |
| 3718 | return (FLOATP (key1) | 3734 | return FLOATP (key1) && FLOATP (key2) && same_float (key1, key2); |
| 3719 | && FLOATP (key2) | ||
| 3720 | && XFLOAT_DATA (key1) == XFLOAT_DATA (key2)); | ||
| 3721 | } | 3735 | } |
| 3722 | 3736 | ||
| 3723 | 3737 | ||
| 3724 | /* Compare KEY1 which has hash code HASH1 and KEY2 with hash code | 3738 | /* Compare KEY1 and KEY2 in hash table HT using `equal'. Value is |
| 3725 | HASH2 in hash table H using `equal'. Value is true if KEY1 and | 3739 | true if KEY1 and KEY2 are the same. */ |
| 3726 | KEY2 are the same. */ | ||
| 3727 | 3740 | ||
| 3728 | static bool | 3741 | static bool |
| 3729 | cmpfn_equal (struct hash_table_test *ht, | 3742 | cmpfn_equal (struct hash_table_test *ht, |
| @@ -3734,9 +3747,8 @@ cmpfn_equal (struct hash_table_test *ht, | |||
| 3734 | } | 3747 | } |
| 3735 | 3748 | ||
| 3736 | 3749 | ||
| 3737 | /* Compare KEY1 which has hash code HASH1, and KEY2 with hash code | 3750 | /* Compare KEY1 and KEY2 in hash table HT using HT->user_cmp_function. |
| 3738 | HASH2 in hash table H using H->user_cmp_function. Value is true | 3751 | Value is true if KEY1 and KEY2 are the same. */ |
| 3739 | if KEY1 and KEY2 are the same. */ | ||
| 3740 | 3752 | ||
| 3741 | static bool | 3753 | static bool |
| 3742 | cmpfn_user_defined (struct hash_table_test *ht, | 3754 | cmpfn_user_defined (struct hash_table_test *ht, |
| @@ -4328,18 +4340,8 @@ static EMACS_UINT | |||
| 4328 | sxhash_float (double val) | 4340 | sxhash_float (double val) |
| 4329 | { | 4341 | { |
| 4330 | EMACS_UINT hash = 0; | 4342 | EMACS_UINT hash = 0; |
| 4331 | enum { | 4343 | union double_and_words u = { .val = val }; |
| 4332 | WORDS_PER_DOUBLE = (sizeof val / sizeof hash | 4344 | for (int i = 0; i < WORDS_PER_DOUBLE; i++) |
| 4333 | + (sizeof val % sizeof hash != 0)) | ||
| 4334 | }; | ||
| 4335 | union { | ||
| 4336 | double val; | ||
| 4337 | EMACS_UINT word[WORDS_PER_DOUBLE]; | ||
| 4338 | } u; | ||
| 4339 | int i; | ||
| 4340 | u.val = val; | ||
| 4341 | memset (&u.val + 1, 0, sizeof u - sizeof u.val); | ||
| 4342 | for (i = 0; i < WORDS_PER_DOUBLE; i++) | ||
| 4343 | hash = sxhash_combine (hash, u.word[i]); | 4345 | hash = sxhash_combine (hash, u.word[i]); |
| 4344 | return SXHASH_REDUCE (hash); | 4346 | return SXHASH_REDUCE (hash); |
| 4345 | } | 4347 | } |
diff --git a/test/src/fns-tests.el b/test/src/fns-tests.el index d9cca557cf2..e4b9cbe25a4 100644 --- a/test/src/fns-tests.el +++ b/test/src/fns-tests.el | |||
| @@ -23,6 +23,17 @@ | |||
| 23 | 23 | ||
| 24 | (require 'cl-lib) | 24 | (require 'cl-lib) |
| 25 | 25 | ||
| 26 | ;; Test that equality predicates work correctly on NaNs when combined | ||
| 27 | ;; with hash tables based on those predicates. This was not the case | ||
| 28 | ;; for eql in Emacs 26. | ||
| 29 | (ert-deftest fns-tests-equality-nan () | ||
| 30 | (dolist (test (list #'eq #'eql #'equal)) | ||
| 31 | (let* ((h (make-hash-table :test test)) | ||
| 32 | (nan 0.0e+NaN) | ||
| 33 | (-nan (- nan))) | ||
| 34 | (puthash nan t h) | ||
| 35 | (should (eq (funcall test nan -nan) (gethash -nan h)))))) | ||
| 36 | |||
| 26 | (ert-deftest fns-tests-reverse () | 37 | (ert-deftest fns-tests-reverse () |
| 27 | (should-error (reverse)) | 38 | (should-error (reverse)) |
| 28 | (should-error (reverse 1)) | 39 | (should-error (reverse 1)) |