Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
BUG,TST: Fix condition for whether long double is “IBM double double”.
Some distributions changed the default for long double on 64-bit PowerPC
(at least little-endian) from the “IBM double double” format to the IEEE
754 binary128 format. The implementation already handles that by
detecting the format at build time (longdouble_format property). Before
this change, the tests checked only the CPU architecture name, possibly
falsely assuming that long double has the “IBM double double” format
when it actually has the IEEE 754 binary128 format. After this change,
the tests assume the “IBM double double” format only if the maximum
value, determined by calling numpy.finfo(), is the maximum value
characteristic for the “IBM double double” format. On CPU architectures
other than PowerPC, the “IBM double double” format is never assumed. I
found no evidence that any other CPU architecture ever used that format.

On a system where long double has the “IBM double double” format, the
test results don’t change:

47406 passed, 1047 skipped, 2845 deselected, 32 xfailed, 4 xpassed

On a system where long double has the IEEE 754 binary128 format, the
test results change from:

1 failed, 47404 passed, 1049 skipped, 2845 deselected, 32 xfailed, 4 xpassed

to:

47408 passed, 1048 skipped, 2845 deselected, 32 xfailed, 2 xpassed

Fixes #21094.
  • Loading branch information
manueljacob committed May 11, 2026
commit eba6eaba560d5c14e6e5878b62baa97357ab1906
4 changes: 2 additions & 2 deletions numpy/_core/tests/test_scalar_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"""
import fractions
import inspect
import platform
import sys
import types
from typing import Any, Literal
Expand All @@ -13,6 +12,7 @@
import numpy as np
from numpy._core import sctypes
from numpy.testing import assert_equal, assert_raises
from numpy.testing._private.utils import LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE


class TestAsIntegerRatio:
Expand Down Expand Up @@ -87,7 +87,7 @@ def test_against_known_values(self):
np.finfo(np.double) == np.finfo(np.longdouble),
reason="long double is same as double"),
pytest.mark.skipif(
platform.machine().startswith("ppc"),
LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE,
reason="IBM double double"),
]
)
Expand Down
3 changes: 2 additions & 1 deletion numpy/_core/tests/test_scalarmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
assert_raises,
check_support_sve,
)
from numpy.testing._private.utils import LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE

types = [np.bool, np.byte, np.ubyte, np.short, np.ushort, np.intc, np.uintc,
np.int_, np.uint, np.longlong, np.ulonglong,
Expand Down Expand Up @@ -521,7 +522,7 @@ def test_int_from_infinite_longdouble(self):

@pytest.mark.skipif(np.finfo(np.double) == np.finfo(np.longdouble),
reason="long double is same as double")
@pytest.mark.skipif(platform.machine().startswith("ppc"),
@pytest.mark.skipif(LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE,
reason="IBM double double")
def test_int_from_huge_longdouble(self):
# Produce a longdouble that would overflow a double,
Expand Down
6 changes: 3 additions & 3 deletions numpy/_core/tests/test_scalarprint.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
""" Test printing of scalar types.

"""
import platform

import pytest

import numpy as np
from numpy.testing import IS_MUSL, assert_, assert_equal, assert_raises
from numpy.testing._private.utils import LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE


class TestRealScalars:
Expand Down Expand Up @@ -329,8 +329,8 @@ def test_dragon4_scientific_interface(self, tp):
assert_equal(fsci(tp('1.0'), unique=False, precision=4),
"1.0000e+00")

@pytest.mark.skipif(not platform.machine().startswith("ppc64"),
reason="only applies to ppc float128 values")
@pytest.mark.skipif(not LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE,
reason="only applies to ppc double-double values")
def test_ppc64_ibm_double_double128(self):
# check that the precision decreases once we get into the subnormal
# range. Unlike float64, this starts around 1e-292 instead of 1e-308,
Expand Down
9 changes: 6 additions & 3 deletions numpy/_core/tests/test_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
assert_raises,
assert_raises_regex,
)
from numpy.testing._private.utils import _glibc_older_than
from numpy.testing._private.utils import (
LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE,
_glibc_older_than,
)

UFUNCS = [obj for obj in np._core.umath.__dict__.values()
if isinstance(obj, np.ufunc)]
Expand Down Expand Up @@ -4808,7 +4811,7 @@ def test_nextafterf():

@pytest.mark.skipif(np.finfo(np.double) == np.finfo(np.longdouble),
reason="long double is same as double")
@pytest.mark.xfail(condition=platform.machine().startswith("ppc64"),
@pytest.mark.xfail(condition=LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE,
reason="IBM double double")
def test_nextafterl():
return _test_nextafter(np.longdouble)
Expand Down Expand Up @@ -4847,7 +4850,7 @@ def test_spacingf():

@pytest.mark.skipif(np.finfo(np.double) == np.finfo(np.longdouble),
reason="long double is same as double")
@pytest.mark.xfail(condition=platform.machine().startswith("ppc64"),
@pytest.mark.xfail(condition=LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE,
reason="IBM double double")
def test_spacingl():
return _test_spacing(np.longdouble)
Expand Down
4 changes: 4 additions & 0 deletions numpy/testing/_private/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ class KnownFailureException(Exception):
NOGIL_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
IS_64BIT = np.dtype(np.intp).itemsize == 8

LONG_DOUBLE_IS_IBM_DOUBLE_DOUBLE = (platform.machine().startswith("ppc")
and str(np.finfo(np.longdouble).max)
== "1.79769313486231580793728971405301e+308")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to just drop the PPC entirely? I would also slightly prefer to nmant (or even also nexp), not quite as distinct in theory, but then we don't have to trust string formatters (I don't really trust them, although I guess that one test would maybe fail if they failed)...

(A bit pattern would work as well, but one special value or two seems distinct enough, I would agree.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should not make any difference to check for PPC or not, at least with the current way (comparing with max).

I would have expected that the string formatting code is stable/correct enough for this purpose. However, I can understand if experience suggests that it might not.

Comparing nmant and nexp would work in practice, however it doesn’t capture the characteristic fact that it is an addition of two numbers. (By the way, in general it doesn’t really make sense to specify nmant for this data type in the same sense as done for others, although I understand that it is needed for API compatibility.)

Comparing with a byte or bit pattern is a bit fiddly, as they differ on little-endian and big-endian (the order of the two numbers is the same on both, but the individual numbers have different order).

What would work is np.finfo(np.longdouble).max.as_integer_ratio() == (0x3ffffffffffffefffffffffffff << 918, 1), which is both characteristic for this data type (see the zero bit in the middle of the mantissa) and avoids string formatting.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, the as_integer_ratio seems like it should work for sure, in practice I guess also finfo(longdouble).max - finfo(double).max == something would probably also work. (I agree nmant at least is a bit shady, although I think the compiler and NumPy do define it to the size of the normalized mantissa.)
Another fair way is to use the bit pattern for something like np.longdouble(1)/10.

However, I can understand if experience suggests that it might not.

Hmmm, it might actually be completely fine and the problem was just (maybe musl) parsing strings, not printing (which we have our own code for). But either way, avoiding it seems nice.

Anyway, if you do a quick follow-up that's great, wotherwise I am happy to just put it in as is, it's a clear improvement!


def assert_(val, msg=''):
"""
Assert that works in release mode.
Expand Down
Loading