sympy / sympy

A computer algebra system written in pure Python

Home Page:https://sympy.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

sympy.Float(0.0).is_integer is True, but sympy.Float(1.0).is_integer is None

ezyang opened this issue · comments

>>> import sympy
>>> sympy.Float(0.0).is_integer
True
>>> sympy.Float(1.0).is_integer
>>> sympy.__version__
'1.13.dev

Seems inconsistent to me. Personally, for my use case I'd prefer it if none of these were treated as integers, but I understand this is counter to what most users of Sympy want. Related #16918

I've always found this decision to be a bit odd. I think the reasoning had something to do with the fact that 0.0 has effectively "infinite precision" unlike other floats. But I do agree that for the purposes of the assumptions making 0.0 not special would be better.

In #25875 we left the Float(0).is_integer behavior intact. @smichr do you remember if there was a specific reason for this, or if it was just done because it was simplest?

The first step here is to try changing Float(0).is_integer to give None and seeing what tests break.

See OP of #25875 and your comments in the PR. Floats other than 0.0 do not behave as rationals in operations.

So I think actually the main reason for this is that there is a distinct is_zero assumption, and we want Float(0.0).is_zero to give True. But in the assumptions, zero -> integer -> rational, so you can't have one without the others.

Actually, given where is_zero is used, I'm not so sure this would be straightforward to fix. (a - b).is_zero is used a way of testing if a == b. If we change this, a lot of code will probably start working differently. It could be a similar situation to the recent change in Float equality, where we decide it's worth doing anyways.

Just tried remove it and I discovered something else: even if you remove _eval_is_zero from Float, Float(0).is_zero still evaluates to True. That's because Float.is_extended_real is set to True, and Float.is_extended_positive and Float.is_extended_negative both evaluate based on the sign of the Float. But the assumptions have the fact

    'extended_real        ==  extended_negative | zero | extended_positive',

So it is able to deduce is_zero = True.

We definitely don't want to remove the ability for the assumptions to evaluate inequalities on Floats, and we don't want to remove any of these basic facts from the real number assumptions.

So to make it work, you have to Float.is_real = True and Float.is_extended_real = True, i.e.,

diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
index b497a1bedc..0ddb382188 100644
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -758,8 +758,8 @@ class Float(Number):
     is_irrational = None
     is_number = True
 
-    is_real = True
-    is_extended_real = True
+    # is_real = True
+    # is_extended_real = True
 
     is_Float = True
 
@@ -974,8 +974,8 @@ def _eval_is_infinite(self):
         return False
 
     def _eval_is_integer(self):
-        if self._mpf_ == fzero:
-            return True
+        # if self._mpf_ == fzero:
+        #     return True
         if not int_valued(self):
             return False
 
@@ -1003,8 +1003,8 @@ def _eval_is_extended_positive(self):
             return False
         return self.num > 0
 
-    def _eval_is_zero(self):
-        return self._mpf_ == fzero
+    # def _eval_is_zero(self):
+    #     return self._mpf_ == fzero
 
     def __bool__(self):
         return self._mpf_ != fzero

This makes Float(0).is_zero give None (and also is_integer and is_rational).

I'm seeing what tests fail with this now and will report back.

Here are the test failures:

================================================================================ FAILURES =================================================================================
_______________________________________________________________________ test_all_classes_are_tested _______________________________________________________________________
sympy/core/tests/test_args.py:50: in test_all_classes_are_tested
    with open(os.path.join(root, file), encoding='utf-8') as f:
E   FileNotFoundError: [Errno 2] No such file or directory: '/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/algebras/.#quaternion.py'
_______________________________________________________________________________ test_round ________________________________________________________________________________
sympy/core/tests/test_expr.py:2080: in test_round
    assert all(S(fi).round(p).is_Float for p in (-1, 0, 1))
E   assert False
E    +  where False = all(<generator object test_round.<locals>.<genexpr> at 0x16ddb3bc0>)
_______________________________________________________________________________ test_Float ________________________________________________________________________________
sympy/core/tests/test_numbers.py:502: in test_Float
    assert Float('0.0').is_zero is True
E   AssertionError: assert None is True
E    +  where None = 0.0.is_zero
E    +    where 0.0 = Float('0.0')
_________________________________________________________________________ test_Mul_Infinity_Zero __________________________________________________________________________
sympy/core/tests/test_numbers.py:881: in test_Mul_Infinity_Zero
    assert _inf*Float(0) is nan
E   assert (inf * 0.0) is nan
E    +  where 0.0 = Float(0)
____________________________________________________________________________ test_cse_multiple ____________________________________________________________________________
sympy/printing/tests/test_llvmjit.py:184: in test_cse_multiple
    assert isclose(res[0], jit_res[0])
E   assert False
E    +  where False = isclose(0.0100000000000000, 2.25000000000000)
____________________________________________________________________________ test_lambdify_cse ____________________________________________________________________________
sympy/utilities/tests/test_lambdify.py:1691: in test_lambdify_cse
    case.assertAllClose(result)
sympy/utilities/tests/test_lambdify.py:1636: in assertAllClose
    assert abs_err/abs(r) < reltol
sympy/core/relational.py:516: in __bool__
    raise TypeError("cannot determine truth value of Relational")
E   TypeError: cannot determine truth value of Relational
                                                                             DO *NOT* COMMIT!
========================================================================= short test summary info =========================================================================
FAILED sympy/core/tests/test_args.py::test_all_classes_are_tested - FileNotFoundError: [Errno 2] No such file or directory: '/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/algebras/.#quaternion.py'
FAILED sympy/core/tests/test_expr.py::test_round - assert False
FAILED sympy/core/tests/test_numbers.py::test_Float - AssertionError: assert None is True
FAILED sympy/core/tests/test_numbers.py::test_Mul_Infinity_Zero - assert (inf * 0.0) is nan
FAILED sympy/printing/tests/test_llvmjit.py::test_cse_multiple - assert False
FAILED sympy/utilities/tests/test_lambdify.py::test_lambdify_cse - TypeError: cannot determine truth value of Relational

Here's a PR with the change so you can see the failures on CI #26621

The zero-ness of 0.0 is, I suppose, the crux of other issues like this one:

>>> sympy.Float(0.0) + sympy.Float(0.0)
0

I'd prefer not to lose the float-ness in this case haha

I don't know if that's directly related. It still does that in my test branch.