This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author trent
Recipients loewis, mark.dickinson, ned.deily, trent
Date 2012-08-17.19:39:23
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1345232365.46.0.555730288556.issue15477@psf.upfronthosting.co.za>
In-reply-to
Content
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?
History
Date User Action Args
2012-08-17 19:39:25trentsetrecipients: + trent, loewis, mark.dickinson, ned.deily
2012-08-17 19:39:25trentsetmessageid: <1345232365.46.0.555730288556.issue15477@psf.upfronthosting.co.za>
2012-08-17 19:39:24trentlinkissue15477 messages
2012-08-17 19:39:23trentcreate