Skip to content

Commit 19a5015

Browse files
authored
Merge pull request #1185 from mathics/ExactNumber-comparisions
Exact number comparisions
2 parents c03218a + 8619fc2 commit 19a5015

File tree

3 files changed

+60
-12
lines changed

3 files changed

+60
-12
lines changed

mathics/builtin/comparison.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
# -*- coding: utf-8 -*-
33

44

5+
from mathics.version import __version__ # noqa used in loading to check consistency.
6+
57
import itertools
68
from typing import Optional, Union
79

810
import sympy
9-
from mathics.version import __version__ # noqa used in loading to check consistency.
1011

1112
from mathics.builtin.base import (
1213
BinaryOperator,
@@ -21,6 +22,7 @@
2122
Expression,
2223
Integer,
2324
Number,
25+
Real,
2426
String,
2527
Symbol,
2628
SymbolFalse,
@@ -32,6 +34,9 @@ def cmp(a, b) -> int:
3234
"Returns 0 if a == b, -1 if a < b and 1 if a > b"
3335
return (a > b) - (a < b)
3436

37+
def is_number(sympy_value) -> bool:
38+
return hasattr(sympy_value, "is_number") or isinstance(sympy_value, sympy.Float)
39+
3540
class SameQ(BinaryOperator):
3641
"""
3742
<dl>
@@ -231,17 +236,30 @@ def do_compare(self, l1, l2) -> Union[bool, None]:
231236
return result
232237
return True
233238

239+
# Use Mathics' built-in comparisons for Real and Integer. These use
240+
# WL's interpretation of Equal[] which allows for slop in Reals
241+
# in the least significant digit of precision, while for Integers, comparison
242+
# has to be exact.
243+
244+
if ((isinstance(l1, Real) and isinstance(l2, Real)) or
245+
(isinstance(l1, Integer) and isinstance(l2, Integer))):
246+
return l1 == l2
247+
248+
# For everything else, use sympy.
249+
234250
l1_sympy = l1.to_sympy(evaluate=True, prec=COMPARE_PREC)
235251
l2_sympy = l2.to_sympy(evaluate=True, prec=COMPARE_PREC)
236252

237253
if l1_sympy is None or l2_sympy is None:
238254
return None
239255

240-
if not hasattr(l1_sympy, "is_number"):
256+
257+
if not is_number(l1_sympy):
241258
l1_sympy = mp_convert_constant(l1_sympy, prec=COMPARE_PREC)
242-
if not hasattr(l2_sympy, "is_number"):
259+
if not is_number(l2_sympy):
243260
l2_sympy = mp_convert_constant(l2_sympy, prec=COMPARE_PREC)
244261

262+
245263
if l1_sympy.is_number and l2_sympy.is_number:
246264
# assert min_prec(l1, l2) is None
247265
prec = COMPARE_PREC # TODO: Use $MaxExtraPrecision
@@ -254,8 +272,12 @@ def do_compare(self, l1, l2) -> Union[bool, None]:
254272
def apply(self, items, evaluation):
255273
"%(name)s[items___]"
256274
items_sequence = items.get_sequence()
257-
if len(items_sequence) <= 1:
275+
n = len(items_sequence)
276+
if n <= 1:
258277
return SymbolTrue
278+
is_exact_vals = [Expression("ExactNumberQ", arg).evaluate(evaluation) for arg in items_sequence]
279+
if all(val == SymbolTrue for val in is_exact_vals):
280+
return self.apply_other(items, evaluation)
259281
args = self.numerify_args(items, evaluation)
260282
wanted = operators[self.get_name()]
261283
for x, y in itertools.combinations(args, 2):
@@ -274,7 +296,7 @@ def apply(self, items, evaluation):
274296
return SymbolTrue
275297

276298
def apply_other(self, args, evaluation):
277-
"%(name)s[args___?(!RealNumberQ[#]&)]"
299+
"%(name)s[args___?(!ExactNumberQ[#]&)]"
278300
args = args.get_sequence()
279301
for x, y in itertools.combinations(args, 2):
280302
c = self.do_compare(x, y)
@@ -285,6 +307,7 @@ def apply_other(self, args, evaluation):
285307
return SymbolTrue
286308

287309

310+
288311
class _ComparisonOperator(_InequalityOperator):
289312
"Compares arguments in a chain e.g. a < b < c compares a < b and b < c."
290313

@@ -355,7 +378,6 @@ def apply(self, items, evaluation):
355378
]
356379
return Expression("And", *groups)
357380

358-
359381
def do_cmp(x1, x2) -> Optional[int]:
360382

361383
# don't attempt to compare complex numbers
@@ -369,8 +391,10 @@ def do_cmp(x1, x2) -> Optional[int]:
369391
s1 = x1.to_sympy()
370392
s2 = x2.to_sympy()
371393

372-
# use internal comparisons only for Reals
373-
# and use sympy for everything else
394+
# Use internal comparisons only for Real which is uses
395+
# WL's interpretation of equal (which allows for slop
396+
# in the least significant digit of precision), and use
397+
# use sympy for everything else
374398
if s1.is_Float and s2.is_Float:
375399
if x1 == x2:
376400
return 0
@@ -450,8 +474,9 @@ class Equal(_EqualityOperator, SympyComparison):
450474
## TODO Needs power precision tracking
451475
## >> 0.1 ^ 10000 == 0.1 ^ 10000 + 0.1 ^ 10012
452476
## = False
453-
## >> 0.1 ^ 10000 == 0.1 ^ 10000 + 0.1 ^ 10013
454-
## = True
477+
478+
#> 0.1 ^ 10000 == 0.1 ^ 10000 + 0.1 ^ 10013
479+
= True
455480
456481
#> 0.1111111111111111 == 0.1111111111111126
457482
= True

mathics/core/expression.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,7 +2352,7 @@ def __new__(cls, value) -> "MachineReal":
23522352
def to_python(self, *args, **kwargs) -> float:
23532353
return self.value
23542354

2355-
def to_sympy(self):
2355+
def to_sympy(self, *args, **kwargs):
23562356
return sympy.Float(self.value)
23572357

23582358
def to_mpmath(self):
@@ -2427,7 +2427,7 @@ def __new__(cls, value) -> "PrecisionReal":
24272427
def to_python(self, *args, **kwargs):
24282428
return float(self.value)
24292429

2430-
def to_sympy(self):
2430+
def to_sympy(self, *args, **kwargs):
24312431
return self.value
24322432

24332433
def to_mpmath(self):

test/test_compare.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# -*- coding: utf-8 -*-
2+
from .helper import check_evaluation
3+
4+
def test_compare():
5+
for str_expr, str_expected in (
6+
(
7+
"I == I",
8+
"True",
9+
),
10+
(
11+
"I == 0",
12+
"False",
13+
),
14+
(
15+
"I + 0 == 1 I - 0",
16+
"True",
17+
),
18+
(
19+
"I + 5 == I",
20+
"False",
21+
),
22+
):
23+
check_evaluation(str_expr, str_expected)

0 commit comments

Comments
 (0)