classification
Title: test_cmath failures on OS X 10.8
Type: behavior Stage: patch review
Components: Extension Modules Versions: Python 3.2, Python 3.3, Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: mark.dickinson Nosy List: loewis, mark.dickinson, ned.deily, python-dev, trent
Priority: normal Keywords: patch

Created on 2012-07-28 10:03 by ned.deily, last changed 2013-10-25 17:22 by mark.dickinson. This issue is now closed.

Files
File name Uploaded Description Edit
issue15477.patch trent, 2012-08-17 19:39 review
log1p_sign_workaround.patch mark.dickinson, 2012-08-18 09:21 review
Messages (21)
msg166642 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2012-07-28 10:03
======================================================================
FAIL: testAtanSign (test.test_cmath.CMathTests)
----------------------------------------------------------------------
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

======================================================================
FAIL: testAtanhSign (test.test_cmath.CMathTests)
----------------------------------------------------------------------
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

======================================================================
FAIL: test_specific_values (test.test_cmath.CMathTests)
----------------------------------------------------------------------
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:

======================================================================
FAIL: test_specific_values (test.test_cmath.CMathTests)
----------------------------------------------------------------------
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?
msg166648 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-07-28 11:49
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.)
msg166649 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-07-28 11:51
Also:  what's HAVE_LOG1P for this build?
msg166691 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2012-07-28 21:01
> I assume the failure still happens in debug mode?

Yes

> Are there any math module failures?

No

> What do math.log1p(0.0) and math.log1p(-0.0) give?  (Answers *should* be 0.0 and -0.0 respectively.)

>>> math.log1p(0.0)
0.0
>>> math.log1p(-0.0)
0.0
>>> sysconfig.get_config_var('HAVE_LOG1P')
1
msg166698 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-07-28 22:17
>>> 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.
msg166700 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2012-07-28 23:18
>>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.'

That manpage is unchanged for 10.8.

>>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.

Ah, this rings a bell and points out yet another issue:

1. The configure test is working properly: when run on 10.8, LOG1P_DROPS_ZERO_SIGN is set to 1.  However, something I just ran into recently, the unittest skip idiom using sysconfig.get_config_vars results - as is being used here in test_cmath - doesn't seem to work properly.  I see it is used in test_math and test_subprocess as well.  I'll look into that.

2. These types of run-time tests based on configure-time tests are problematic for binary installations of Python, like the OS X installers.  For example, the OS X installers are built on earlier versions of OS X where a configuration test (like this one) may have one result but not necessarily the same result on all supported target configurations where the binary Pythons may be installed.  And, in general, multiple-architecture builds, in particular, OS X universal builds may have different configuration values per architecture embedded in one executable (i386 vs x86_64 or i386 vs ppc or i386 vs x86_64 vs ppc) that are not reflected in the single architecture ./configure results.  I guess it's time to open an issue on that can of worms.

>>(Is this clang or gcc, BTW?)

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.
msg166729 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-07-29 09:04
> These types of run-time tests based on configure-time tests are problematic
> for binary installations of Python, like the OS X installers.

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?
msg168445 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2012-08-17 14:03
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.
msg168470 - (view) Author: Trent Nelson (trent) * (Python committer) Date: 2012-08-17 19:39
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
    test_cmath:

        ======================================================================
        FAIL: test_specific_values (test.test_cmath.CMathTests)
        ----------------------------------------------------------------------
        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.

    Mountain Lion's atan/log1p appear to drop the negative sign when
    passed in -0.0, whereas previous versions of OS X didn't:

        Mountain Lion:
            % ~/log1p-viper

            log1p_drops_zero_sign_test:
                atan2(log1p(-0.), -1.) != atan2(-0., -1.)
                      3.14159         vs      -3.14159

            atan_drops_zero_sign_test:
                atan2(-0.,  0.): -0.00000
                atan2( 0., -0.): 3.14159
                atan2(-0., -0.): -3.14159
                atan2( 0.,  0.): 0.00000
                log1p(-0.):      0.00000
                log1p( 0.):      0.00000

        Lion:
            % ./log1p

            log1p_drops_zero_sign_test:
                atan2(log1p(-0.), -1.) == atan2(-0., -1.)
                      -3.14159         vs      -3.14159

            atan_drops_zero_sign_test:
                atan2(-0.,  0.): -0.00000
                atan2( 0., -0.): 3.14159
                atan2(-0., -0.): -3.14159
                atan2( 0.,  0.): 0.00000
                log1p(-0.):      -0.00000
                log1p( 0.):      0.00000

    (The C code for that is below.)

    configure.ac already has a test for this (it makes mention of AIX
    having similar behaviour), and the corresponding sysconfig entry
    named 'LOG1P_DROPS_ZERO_SIGN' is already being used on a few tests,
    i.e.:


      # 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)

    Taking a look at cmath_testcases.txt, and we can see this:

        -- These are tested in testAtanSign in test_cmath.py
        -- atan0000 atan 0.0 0.0 -> 0.0 0.0
        -- atan0001 atan 0.0 -0.0 -> 0.0 -0.0
        -- atan0002 atan -0.0 0.0 -> -0.0 0.0
        -- atan0003 atan -0.0 -0.0 -> -0.0 -0.0

    However, a few lines down, those tests crop up again:

        -- special values
        atan1000 atan -0.0 0.0 -> -0.0 0.0
        <snip>
        atan1014 atan 0.0 0.0 -> 0.0 0.0

    ....which is what causes the current test failures.  I hacked
    test_cmath.py a bit to spit out all the errors it finds after
    it's finished parsing the test file (instead of bombing out on
    the first one), and it yielded this:

        FAIL: test_specific_values (test.test_cmath.CMathTests)
        ----------------------------------------------------------------------
        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.

    This is the patch I came up with against test_cmath.py:

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))

    With that applied, all the test_cmath tests pass again (without any
    changes to the test file).

    Thoughts?

        Trent.

--
    C code for the example earlier:

    #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(
            "          %.5f         vs      %.5f\n",
            atan2(log1p(-0.), -1.),
            atan2(-0., -1.)
        );

        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.));

    }
    /* vim:set ts=8 sw=4 sts=4 tw=78 et: */


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:

---------------------------
-- atan: Inverse tangent --
---------------------------

-- zeros
-- These are tested in testAtanSign in test_cmath.py
-- atan0000 atan 0.0 0.0 -> 0.0 0.0
-- atan0001 atan 0.0 -0.0 -> 0.0 -0.0
-- atan0002 atan -0.0 0.0 -> -0.0 0.0
-- atan0003 atan -0.0 -0.0 -> -0.0 -0.0

So, given, that, let's review the affected tests further down in cmath_testcase.txt:

        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.

I propose we remove the faulty zero tests as follows:


    % hg diff
    diff -r 05274ab06182 Lib/test/cmath_testcases.txt
    --- a/Lib/test/cmath_testcases.txt	Sat May 05 21:57:17 2012 -0700
    +++ b/Lib/test/cmath_testcases.txt	Fri Aug 17 18:58:03 2012 +0000
    @@ -849,7 +849,6 @@
     atan0304 atan -9.9998886718268301e-321 -1.0 -> -0.78539816339744828 -368.76019403576692
     
     -- special values
    -atan1000 atan -0.0 0.0 -> -0.0 0.0
     atan1001 atan nan 0.0 -> nan 0.0
     atan1002 atan -0.0 1.0 -> -0.0 inf divide-by-zero
     atan1003 atan -inf 0.0 -> -1.5707963267948966 0.0
    @@ -863,7 +862,6 @@
     atan1011 atan -2.2999999999999998 nan -> nan nan
     atan1012 atan -inf nan -> -1.5707963267948966 0.0 ignore-imag-sign
     atan1013 atan nan nan -> nan nan
    -atan1014 atan 0.0 0.0 -> 0.0 0.0
     atan1015 atan 0.0 1.0 -> 0.0 inf divide-by-zero
     atan1016 atan inf 0.0 -> 1.5707963267948966 0.0
     atan1017 atan inf 2.2999999999999998 -> 1.5707963267948966 0.0
    @@ -873,7 +871,6 @@
     atan1021 atan 0.0 nan -> nan nan
     atan1022 atan 2.2999999999999998 nan -> nan nan
     atan1023 atan inf nan -> 1.5707963267948966 0.0 ignore-imag-sign
    -atan1024 atan 0.0 -0.0 -> 0.0 -0.0
     atan1025 atan nan -0.0 -> nan -0.0
     atan1026 atan 0.0 -1.0 -> 0.0 -inf divide-by-zero
     atan1027 atan inf -0.0 -> 1.5707963267948966 -0.0
    @@ -883,7 +880,6 @@
     atan1031 atan 2.2999999999999998 -inf -> 1.5707963267948966 -0.0
     atan1032 atan inf -inf -> 1.5707963267948966 -0.0
     atan1033 atan nan -inf -> nan -0.0
    -atan1034 atan -0.0 -0.0 -> -0.0 -0.0
     atan1035 atan -0.0 -1.0 -> -0.0 -inf divide-by-zero
     atan1036 atan -inf -0.0 -> -1.5707963267948966 -0.0
     atan1037 atan -inf -2.2999999999999998 -> -1.5707963267948966 -0.0

That leaves us with:

        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.

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:


    diff -r 12c062dbe746 Lib/test/cmath_testcases.txt
    --- a/Lib/test/cmath_testcases.txt	Thu Aug 16 22:18:37 2012 +0200
    +++ b/Lib/test/cmath_testcases.txt	Fri Aug 17 15:37:11 2012 -0400
    @@ -849,7 +849,6 @@
     atan0304 atan -9.9998886718268301e-321 -1.0 -> -0.78539816339744828 -368.76019403576692
     
     -- special values
    -atan1000 atan -0.0 0.0 -> -0.0 0.0
     atan1001 atan nan 0.0 -> nan 0.0
     atan1002 atan -0.0 1.0 -> -0.0 inf divide-by-zero
     atan1003 atan -inf 0.0 -> -1.5707963267948966 0.0
    @@ -863,7 +862,6 @@
     atan1011 atan -2.2999999999999998 nan -> nan nan
     atan1012 atan -inf nan -> -1.5707963267948966 0.0 ignore-imag-sign
     atan1013 atan nan nan -> nan nan
    -atan1014 atan 0.0 0.0 -> 0.0 0.0
     atan1015 atan 0.0 1.0 -> 0.0 inf divide-by-zero
     atan1016 atan inf 0.0 -> 1.5707963267948966 0.0
     atan1017 atan inf 2.2999999999999998 -> 1.5707963267948966 0.0
    @@ -873,7 +871,6 @@
     atan1021 atan 0.0 nan -> nan nan
     atan1022 atan 2.2999999999999998 nan -> nan nan
     atan1023 atan inf nan -> 1.5707963267948966 0.0 ignore-imag-sign
    -atan1024 atan 0.0 -0.0 -> 0.0 -0.0
     atan1025 atan nan -0.0 -> nan -0.0
     atan1026 atan 0.0 -1.0 -> 0.0 -inf divide-by-zero
     atan1027 atan inf -0.0 -> 1.5707963267948966 -0.0
    @@ -883,7 +880,6 @@
     atan1031 atan 2.2999999999999998 -inf -> 1.5707963267948966 -0.0
     atan1032 atan inf -inf -> 1.5707963267948966 -0.0
     atan1033 atan nan -inf -> nan -0.0
    -atan1034 atan -0.0 -0.0 -> -0.0 -0.0
     atan1035 atan -0.0 -1.0 -> -0.0 -inf divide-by-zero
     atan1036 atan -inf -0.0 -> -1.5707963267948966 -0.0
     atan1037 atan -inf -2.2999999999999998 -> -1.5707963267948966 -0.0
    @@ -1011,9 +1007,7 @@
     atanh0222 atanh -5.9656816081325078e-317 9.9692253555416263e-313 -> -5.9656816081325078e-317 9.9692253555416263e-313
     atanh0223 atanh -6.5606671178400239e-313 -2.1680936406357335e-309 -> -6.5606671178400239e-313 -2.1680936406357335e-309
     atanh0224 atanh 0.0 2.5230944401820779e-319 -> 0.0 2.5230944401820779e-319
    -atanh0225 atanh -0.0 5.6066569490064658e-320 -> -0.0 5.6066569490064658e-320
     atanh0226 atanh 0.0 -2.4222487249468377e-317 -> 0.0 -2.4222487249468377e-317
    -atanh0227 atanh -0.0 -3.0861101089206037e-316 -> -0.0 -3.0861101089206037e-316
     atanh0228 atanh 3.1219222884393986e-310 0.0 -> 3.1219222884393986e-310 0.0
     atanh0229 atanh 9.8926337564976196e-309 -0.0 -> 9.8926337564976196e-309 -0.0
     atanh0230 atanh -1.5462535092918154e-312 0.0 -> -1.5462535092918154e-312 0.0
    @@ -1051,7 +1045,6 @@
     atanh1021 atanh nan -0.0 -> nan nan
     atanh1022 atanh nan -2.3 -> nan nan
     atanh1023 atanh nan -inf -> 0.0 -1.5707963267948966     ignore-real-sign
    -atanh1024 atanh -0.0 -0.0 -> -0.0 -0.0
     atanh1025 atanh -0.0 nan -> -0.0 nan
     atanh1026 atanh -1.0 -0.0 -> -inf -0.0                  divide-by-zero
     atanh1027 atanh -0.0 -inf -> -0.0 -1.5707963267948966
    @@ -1061,7 +1054,6 @@
     atanh1031 atanh -inf -2.3 -> -0.0 -1.5707963267948966
     atanh1032 atanh -inf -inf -> -0.0 -1.5707963267948966
     atanh1033 atanh -inf nan -> -0.0 nan
    -atanh1034 atanh -0.0 0.0 -> -0.0 0.0
     atanh1035 atanh -1.0 0.0 -> -inf 0.0                    divide-by-zero
     atanh1036 atanh -0.0 inf -> -0.0 1.5707963267948966
     atanh1037 atanh -2.3 inf -> -0.0 1.5707963267948966
    diff -r 12c062dbe746 Lib/test/test_cmath.py
    --- a/Lib/test/test_cmath.py	Thu Aug 16 22:18:37 2012 +0200
    +++ b/Lib/test/test_cmath.py	Fri Aug 17 15:37:11 2012 -0400
    @@ -532,6 +532,36 @@
             for z in complex_zeros:
                 self.assertComplexIdentical(cmath.atanh(z), z)
     
    +    @requires_IEEE_754
    +    @unittest.skipIf(sysconfig.get_config_var('LOG1P_DROPS_ZERO_SIGN'),
    +                     "system log1p() function doesn't preserve the sign")
    +    def testAtanhSignNearZero(self):
    +        for z in (-30861101e-316, 5.6067e-320):
    +            expected = complex(-0.0, z)
    +            actual = cmath.atanh(complex(-0.0, z))
    +            error_message = (
    +                'Expected: complex({!r}, {!r})\n'
    +                'Received: complex({!r}, {!r})\n'
    +                'Received value insufficiently close to expected value.'
    +                ).format(
    +                    expected.real,
    +                    expected.imag,
    +                    actual.real,
    +                    actual.imag
    +                )
    +
    +            self.rAssertAlmostEqual(
    +                expected.real,
    +                actual.real,
    +                abs_err=5e-323,
    +                msg=error_message,
    +            )
    +
    +            self.rAssertAlmostEqual(
    +                expected.imag,
    +                actual.imag,
    +                msg=error_message,
    +            )
     
     def test_main():
         run_unittest(CMathTests)

With this patch applied, test_cmath passes on both Mountain Lion and Lion.  I've attached this patch as issue15477.patch.

Thoughts?
msg168498 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-08-18 09:17
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)'
10000000 loops, best of 3: 0.0685 usec per loop

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)'
10000000 loops, best of 3: 0.0686 usec per loop
msg168499 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-08-18 09:21
Updated patch that also removes the sysconfig checks from the cmath tests.
msg168500 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-08-18 09:32
See also http://bugs.python.org/issue9920;  the patch here *should* fix that issue, too.
msg168501 - (view) Author: Trent Nelson (trent) * (Python committer) Date: 2012-08-18 09:33
Happy to report your patch does the trick Mark.  test_cmath passes on 10.7 and 10.8 with it applied.
msg168503 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2012-08-18 10:27
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.
http://pubs.opengroup.org/onlinepubs/009604599/functions/log1p.html

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).
msg168506 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-08-18 10:59
> BTW, did anybody file a bug report with Apple?

I'll file one.
msg168507 - (view) Author: Roundup Robot (python-dev) Date: 2012-08-18 11:24
New changeset 08418369da7b by Mark Dickinson in branch '3.2':
Issue #15477: Add workaround for log1p(-0.0) on platforms where it's broken.
http://hg.python.org/cpython/rev/08418369da7b
msg168508 - (view) Author: Roundup Robot (python-dev) Date: 2012-08-18 11:26
New changeset 336653319112 by Mark Dickinson in branch 'default':
Issue #15477: Merge fix from 3.2
http://hg.python.org/cpython/rev/336653319112
msg168509 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-08-18 11:32
Apple bug report filed: Bug ID# 12128251.

Closing the issue.
msg201148 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-10-24 16:00
Update: I just received this from Apple, for bug #12128251.

"""
We believe this issue has been addressed in OS X Mavericks GM build 13A603. Please verify with this release, and update this report with your results.
"""
msg201185 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2013-10-24 20:54
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
msg201262 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-10-25 17:22
Perfect.  Thanks, Ned!  I'll update the Apple issue.
History
Date User Action Args
2013-10-25 17:22:35mark.dickinsonsetmessages: + msg201262
2013-10-24 20:54:18ned.deilysetmessages: + msg201185
2013-10-24 16:00:11mark.dickinsonsetmessages: + msg201148
2013-03-14 13:44:00alefsetnosy: - alef
2013-03-14 13:43:24alefsetnosy: + alef
2012-08-18 11:32:35mark.dickinsonsetstatus: open -> closed
resolution: fixed
messages: + msg168509
2012-08-18 11:26:27python-devsetmessages: + msg168508
2012-08-18 11:24:51python-devsetnosy: + python-dev
messages: + msg168507
2012-08-18 10:59:08mark.dickinsonsetmessages: + msg168506
2012-08-18 10:27:01loewissetmessages: + msg168503
2012-08-18 09:33:09trentsetmessages: + msg168501
2012-08-18 09:32:24mark.dickinsonsetmessages: + msg168500
2012-08-18 09:25:50mark.dickinsonsetstage: patch review
type: behavior
components: + Extension Modules
versions: + Python 2.7, Python 3.2
2012-08-18 09:22:01mark.dickinsonsetfiles: - log1p_sign_workaround.patch
2012-08-18 09:21:40mark.dickinsonsetfiles: + log1p_sign_workaround.patch

messages: + msg168499
2012-08-18 09:17:14mark.dickinsonsetfiles: + log1p_sign_workaround.patch

messages: + msg168498
2012-08-18 08:43:12mark.dickinsonsetassignee: mark.dickinson
2012-08-17 19:39:24trentsetfiles: + issue15477.patch

nosy: + trent
messages: + msg168470

keywords: + patch
2012-08-17 14:03:20loewissetnosy: + loewis
messages: + msg168445
2012-07-29 09:04:49mark.dickinsonsetmessages: + msg166729
2012-07-28 23:18:29ned.deilysetmessages: + msg166700
2012-07-28 22:17:23mark.dickinsonsetmessages: + msg166698
2012-07-28 21:01:43ned.deilysetmessages: + msg166691
2012-07-28 11:51:57mark.dickinsonsetmessages: + msg166649
2012-07-28 11:49:11mark.dickinsonsetmessages: + msg166648
2012-07-28 10:03:57ned.deilycreate