New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test_cmath failures on OS X 10.8 #59682
Comments
====================================================================== Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/test/test_cmath.py", line 526, in testAtanSign
self.assertComplexIdentical(cmath.atan(z), z)
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/test/test_cmath.py", line 96, in assertComplexIdentical
self.assertFloatIdentical(x.imag, y.imag)
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/test/test_cmath.py", line 86, in assertFloatIdentical
self.fail(msg.format(x, y))
AssertionError: floats -0.0 and 0.0 are not identical: zeros have different signs ====================================================================== Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/test/test_cmath.py", line 533, in testAtanhSign
self.assertComplexIdentical(cmath.atanh(z), z)
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/test/test_cmath.py", line 95, in assertComplexIdentical
self.assertFloatIdentical(x.real, y.real)
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/test/test_cmath.py", line 86, in assertFloatIdentical
self.fail(msg.format(x, y))
AssertionError: floats 0.0 and -0.0 are not identical: zeros have different signs ====================================================================== Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/test/test_cmath.py", line 382, in test_specific_values
msg=error_message)
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/test/test_cmath.py", line 128, in rAssertAlmostEqual
'got {!r}'.format(a, b))
AssertionError: atan1000: atan(complex(-0.0, 0.0))
Expected: complex(-0.0, 0.0)
Received: complex(-0.0, -0.0)
Received value insufficiently close to expected value. Failures seen across various compilers and deployment targets and running the tests with the same binaries on earlier OS X versions do not fail. FWIW, the Apple-supplied Python 2.7.2 in 10.8 (that's the most recent version supplied) also has a test_cmath failure: ====================================================================== Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/test/test_cmath.py", line 352, in test_specific_values
msg=error_message)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/test/test_cmath.py", line 94, in rAssertAlmostEqual
'got {!r}'.format(a, b))
AssertionError: atan0000: atan(complex(0.0, 0.0))
Expected: complex(0.0, 0.0)
Received: complex(0.0, -0.0)
Received value insufficiently close to expected value. Is there any reason to not consider this a platform bug? If it is, what should the bug report be? |
Judging by previous reports of this type, it's probably either a bug in the platform math library or a compiler optimization bug (or possibly a combination of the two: one previous OS X bug involved calls to sin / cos being 'optimized' to a badly implemented single call to cexp). I assume the failure still happens in debug mode? If so, a math library bug seems more likely. Are there any math module failures? It's not immediately obvious what the bug is, since the cmath module functions are not just simple wrappers around library functions. In this case it looks like either atan2 or log1p is doing the wrong thing with signs of zeros. What do math.log1p(0.0) and math.log1p(-0.0) give? (Answers *should* be 0.0 and -0.0 respectively.) |
Also: what's HAVE_LOG1P for this build? |
Yes
No
>>> math.log1p(0.0)
0.0
>>> math.log1p(-0.0)
0.0
>>> sysconfig.get_config_var('HAVE_LOG1P')
1 |
>>> math.log1p(0.0)
0.0
>>> math.log1p(-0.0)
0.0 Ah, that would do it, then. It looks as though the system's log1p function is buggy, in the sense that it doesn't follow C99 Annex F (F.9.3.9). It also doesn't agree with what 'man log1p' says on my OS X 10.6 Macbook: under 'SPECIAL VALUES', it says: 'log1p(+-0) returns +-0.' I'm puzzled about one thing, though: there's a test for this problem in the configure script, and LOG1P_DROPS_ZERO_SIGN should be defined in this case---in that case, the two testAtanSign and testAtanhSign tests are skipped. So it looks as though LOG1P_DROPS_ZERO_SIGN isn't being defined on this machine; I'm not sure why---there may be a compiler optimization kicking in in the configure test. (Is this clang or gcc, BTW?) So definitely worth a bug report, I'd say, though perhaps it's too much to hope for a fix within the lifetime of 10.8. |
That manpage is unchanged for 10.8.
Ah, this rings a bell and points out yet another issue:
It's clang which is now the only supported option with Xcode 4.4 on either 10.7 or 10.8. More importantly, 10.8 is the first OS X release which itself was built with clang; 10.7 was built with the transitional llvm-gcc compiler. |
Understood. I'm wondering how to fix this for Python, given that we're unlikely to see an OS-level fix for a while. There's an easy workaround, namely to add an "if (x == 0.0) return x;" before any use of the system log1p; the issue is how and when to apply this workaround. One option is always to apply the workaround, but this feels to me as though it's unnecessarily penalising those OSs that get it right; maybe just always apply the workaround on OS X? |
Without looking into the details of the issue: conditionalizing a work-around on OSX sounds right. On the one hand, it may penalize OSX releases which get it right. OTOH, it's all Apple's fault (IIUC), so they deserve it :-) Further, using a build-time check for the OS release is inappropriate, since the binary should work on other releases as well; a run-time check is probably more costly than the work-around. |
I ran into this last night and posted to python-dev before realizing this bug had been raised: http://mail.python.org/pipermail/python-dev/2012-August/121359.html I'll reproduce it here as I made a few observations that haven't yet been mentioned in this issue: The Mountain Lion build slave I set up earlier this evening fails on
Traceback (most recent call last):
File "/Volumes/bay2/buildslave/cpython/2.7.snakebite-mountainlion-amd64/build/Lib/test/test_cmath.py", line 352, in test_specific_values
msg=error_message)
File "/Volumes/bay2/buildslave/cpython/2.7.snakebite-mountainlion-amd64/build/Lib/test/test_cmath.py", line 94, in rAssertAlmostEqual
'got {!r}'.format(a, b))
AssertionError: atan0000: atan(complex(0.0, 0.0))
Expected: complex(0.0, 0.0)
Received: complex(0.0, -0.0)
Received value insufficiently close to expected value.
# The algorithm used for atan and atanh makes use of the system
# log1p function; If that system function doesn't respect the sign
# of zero, then atan and atanh will also have difficulties with
# the sign of complex zeros.
@requires_IEEE_754
@unittest.skipIf(sysconfig.get_config_var('LOG1P_DROPS_ZERO_SIGN'),
"system log1p() function doesn't preserve the sign")
def testAtanSign(self):
for z in complex_zeros:
self.assertComplexIdentical(cmath.atan(z), z)
@requires_IEEE_754
@unittest.skipIf(sysconfig.get_config_var('LOG1P_DROPS_ZERO_SIGN'),
"system log1p() function doesn't preserve the sign")
def testAtanhSign(self):
for z in complex_zeros:
self.assertComplexIdentical(cmath.atanh(z), z)
Traceback (most recent call last):
File "/Volumes/bay2/buildslave/cpython/3.2.snakebite-mountainlion-amd64/build/Lib/test/test_cmath.py", line 446, in test_specific_values
self.fail("\n".join(failures))
AssertionError: atan1000: atan(complex(-0.0, 0.0))
Expected: complex(-0.0, 0.0)
Received: complex(-0.0, -0.0)
Received value insufficiently close to expected value.
atan1014: atan(complex(0.0, 0.0))
Expected: complex(0.0, 0.0)
Received: complex(0.0, -0.0)
Received value insufficiently close to expected value.
atanh0225: atanh(complex(-0.0, 5.6067e-320))
Expected: complex(-0.0, 5.6067e-320)
Received: complex(0.0, 5.6067e-320)
Received value insufficiently close to expected value.
atanh0227: atanh(complex(-0.0, -3.0861101e-316))
Expected: complex(-0.0, -3.0861101e-316)
Received: complex(0.0, -3.0861101e-316)
Received value insufficiently close to expected value.
atanh1024: atanh(complex(-0.0, -0.0))
Expected: complex(-0.0, -0.0)
Received: complex(0.0, -0.0)
Received value insufficiently close to expected value.
atanh1034: atanh(complex(-0.0, 0.0))
Expected: complex(-0.0, 0.0)
Received: complex(0.0, 0.0)
Received value insufficiently close to expected value.
xenon% hg diff Lib/test/test_cmath.py
diff -r ce49599b9fdf Lib/test/test_cmath.py
--- a/Lib/test/test_cmath.py Thu Aug 16 22:14:43 2012 +0200
+++ b/Lib/test/test_cmath.py Fri Aug 17 07:54:05 2012 +0000
@@ -121,8 +121,10 @@
# if both a and b are zero, check whether they have the same sign
# (in theory there are examples where it would be legitimate for a
# and b to have opposite signs; in practice these hardly ever
- # occur).
- if not a and not b:
+ # occur) -- the exception to this is if we're on a system that drops
+ # the sign on zeros.
+ drops_zero_sign = sysconfig.get_config_var('LOG1P_DROPS_ZERO_SIGN')
+ if not drops_zero_sign and not a and not b:
if math.copysign(1., a) != math.copysign(1., b):
self.fail(msg or 'zero has wrong sign: expected {!r}, '
'got {!r}'.format(a, b))
-- #include <math.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf("\nlog1p_drops_zero_sign_test:\n");
if (atan2(log1p(-0.), -1.) == atan2(-0., -1.))
printf(" atan2(log1p(-0.), -1.) == atan2(-0., -1.)\n");
else
printf(" atan2(log1p(-0.), -1.) != atan2(-0., -1.)\n");
printf("\natan_drops_zero_sign_test:\n");
printf(" atan2(-0., 0.): %0.5f\n", atan2(-0., 0.));
printf(" atan2( 0., -0.): %0.5f\n", atan2( 0., -0.));
printf(" atan2(-0., -0.): %0.5f\n", atan2(-0., -0.));
printf(" atan2( 0., 0.): %0.5f\n", atan2( 0., 0.));
printf(" log1p(-0.): %0.5f\n", log1p(-0.));
printf(" log1p( 0.): %0.5f\n", log1p( 0.));
After thinking about it a bit longer, I think patching rAssertAlmostEqual is unnecessarily hacky. The problem is that the 'special value' atan tests in the cmath test file shouldn't have any zero tests -- these are handled in testAtanSign, which is what cmath_testcase.txt states: --------------------------- -- zeros So, given, that, let's review the affected tests further down in cmath_testcase.txt:
I propose we remove the faulty zero tests as follows:
That leaves us with:
Those aren't duplicate tests like the 0.0 -> -0.0 ones, so, how should they be handled? The proposed patch to rAssertAlmostEqual will skip these ones, but perhaps something like this would be more appropriate:
With this patch applied, test_cmath passes on both Mountain Lion and Lion. I've attached this patch as bpo-15477.patch. Thoughts? |
Trent, many thanks for the report and analysis. The LOG1P_DROPS_ZERO_SIGN hackery was a result of a previous attempt to 'fix' the tests on another platform whose log1p didn't preserve zero signs. But now that this behaviour has emerged on OS X too, I think it might be time to fix this in the Python core code (i.e., add a workaround for the buggy OS behaviour) instead of trying to make the tests pass again. Attached is a patch that adds a workaround for this on all platforms that have a system log1p. I was originally thinking of adding this workaround only on OS X (so as not to unnecessarily punish those platforms that get it right), but after running some timings (see below) it seems that the workaround is insignificant performance-wise, so we might as well keep things simple and add it on all platforms. Trent, if you have time, please could you try this patch on OS X 10.8 and see if it fixes all the test_cmath and test_math failures? Some timings on OS X 10.6. Current default branch (pass value directly onto system log1p): iwasawa:cpython mdickinson$ ./python.exe -m timeit -s 'from math import log1p; x = 2.3' 'log1p(x)' With the attached patch (do zero check, then pass value directly onto system log1p): iwasawa:cpython mdickinson$ ./python.exe -m timeit -s 'from math import log1p; x = 2.3' 'log1p(x)' |
Updated patch that also removes the sysconfig checks from the cmath tests. |
See also http://bugs.python.org/issue9920; the patch here *should* fix that issue, too. |
Happy to report your patch does the trick Mark. test_cmath passes on 10.7 and 10.8 with it applied. |
BTW, did anybody file a bug report with Apple? Even to my untrained eye, that looks like a clear violation of the POSIX standard: If x is ±0, or +Inf, x shall be returned. C seems to make it implementation-defined, unless the implementation defines __STDC_IEC_559__, in which case log1p also needs to return ±0 (C99 F.9.3.9, C11 F.10.3.9). |
I'll file one. |
New changeset 08418369da7b by Mark Dickinson in branch '3.2': |
New changeset 336653319112 by Mark Dickinson in branch 'default': |
Apple bug report filed: Bug ID# 12128251. Closing the issue. |
Update: I just received this from Apple, for bug #12128251. """ |
I built Python at 3fba718e46e5 on both 10.8 and 10.9 which was just before your changes for this issue went in, Mark. On 10.8: test_cmath fails and:
>>> math.log1p(-0.0)
0.0
And, indeed, on 10.9, it passes and:
>>> math.log1p(-0.0)
-0.0 |
Perfect. Thanks, Ned! I'll update the Apple issue. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: