Generation of unary plus in exponent is un-killable
Closed this issue · 7 comments
Hello, thanks for the lovely tool!
mutmut generates mutants like the following, which are identical to the original code and thus un-killable (immortal?):
--- mvce/mvce.py
+++ mvce/mvce.py
@@ -1,3 +1,3 @@
def multiply_by_1e100(number):
- return number * 1e100
+ return number * 1e+100
Here's a test so that you can reproduce the whole thing:
from mvce import multiply_by_1e100
def test_multiply_by_1e100():
assert multiply_by_1e100(4) == 4e100
1e16
seems to be the smallest number to which this happens. Smaller numbers get expanded from e.g. 1e2
to 101.0
Interesting! Yea this is because 1+1e16 = 1e16. The mutation code should try to increase it with exponentially bigger and bigger values until the values actually change I think. You want to give it a shot to implement this?
Sure, that could just be something like
exponent = 0
result = parsed + (10 ** exponent)
while result == parsed:
exponent += 1
result = parsed + (10 ** exponent)
result = repr(result)
which would always result in numbers differing by 1e-16 for any number >= 1e16. This could be a problem for numerical software which might be using numpy.isclose
with a much looser precision.
So maybe something like:
if parsed == 0:
result = repr(parsed + 1)
else:
result = repr(parsed * 2)
Yea, that seems to do the right thing.
This could be a problem for numerical software which might be using numpy.isclose with a much looser precision.
Well.. maybe. But then I think we can wait for that bug report :P Seems like you'd have to have VERY loose boundaries for that big numbers to be "close" though, right?
I am preemptively reporting that bug then :)
numpy.isclose
has default relative and absolute tolerances of 1e-5 and 1e-8 respectively, so my first suggestion above would not get killed. Changing the loop check to while np.isclose(result, parsed)
results in 1.0001e+16
.
I figure you probably don't want a dependency on numpy, so probably the simplest thing is my second suggestion
The second suggestion is too big of a hammer I think. It will change 7.0
to 14.0
right? That seems a bit much to me.
This is true, but the current version also changes any number smaller than 1e-16
to 1.0
which is comparatively much larger!
What is an appropriate change for numbers? For int
, changing by one definitely seems like a good idea, as it helps catch off-by-one errors. For floats, I'm not so sure what would be appropriate across the entire domain -- adding a constant feels wrong for both very small and very large numbers. Multiplication by a constant feels safer to me, although zero needs special handling.
How about:
if 1e-5 < abs(parsed) < 1e5:
result = repr(parsed + 1)
else:
result = repr(parsed * 2) # or 1.1 perhaps
Hmm. All good points! Yea ok then any of your suggestions sound great. You clearly have thought this through more than me :)