Skip to content

API: Raise when mixing SLEEF and longdouble backends - #122

Open
SwayamInSync wants to merge 22 commits into
numpy:mainfrom
SwayamInSync:raise-mixed-backends
Open

SwayamInSync wants to merge 22 commits into
numpy:mainfrom
SwayamInSync:raise-mixed-backends

Conversation

@SwayamInSync

Copy link
Copy Markdown
Member

Addresses #113 for now, until #112 makes casts between backends exact.

Values of the SLEEF and longdouble backends currently move between each other in two flawed ways. Every conversion goes through float64 (#112), so it silently loses precision in both directions. Operations also disagree on the common backend (#113): np.result_type and np.concatenate pick SLEEF, while ufuncs pick longdouble.

This PR makes the policy consistent by refusing every such operation with a TypeError until there is an exact conversion and one agreed promotion rule. Everything that does not combine the two backends is unchanged, and NumPy's own dtypes, including np.longdouble, work with both backends as before.

Behaviour

Upstream main (888042c) vs this branch, on x86-64 Linux:

Operation main This PR
np.result_type(sleef_dt, ld_dt) QuadPrecDType(backend='sleef') TypeError
(sleef_arr + ld_arr).dtype QuadPrecDType(backend='longdouble') TypeError
np.concatenate([sleef_arr, ld_arr]).dtype QuadPrecDType(backend='sleef') TypeError
SLEEF 1 + 2**-112 .astype(ld_dt), minus 1 0.0 TypeError
longdouble 1 + 2**-60 .astype(sleef_dt), minus 1 0.0 TypeError
ld_arr[0] = QuadPrecision(1.5) stores 0.0 TypeError
np.sqrt(sleef [4.0], out=ld_arr, casting="unsafe") writes 0.0 TypeError
QuadPrecision(q) == q for q = 1 + 2**-112 False True
ld_arr + 1 [2.5] TypeError
ld_arr + np.float64(1) [2.5] [2.5]
ld_arr.astype(np.longdouble) [1.5] [1.5]

The error is TypeError: Cannot mix QuadPrecision backends 'sleef' and 'longdouble'. Where NumPy checks the casting rule before calling our resolver (gufuncs, astype(..., casting="same_kind")), it is NumPy's own Cannot cast ... TypeError instead.

Backwards-incompatible changes

  • Ufuncs whose operands or out= arrays have different backends, astype, np.copyto, slice and item assignment, np.result_type, np.promote_types, np.concatenate, and QuadPrecision(value, backend=...) given a QuadPrecision of the other backend now raise. np.array of mixed QuadPrecision scalars gives an object array.
  • Python int and float use the default SLEEF descriptor, so they raise with longdouble arrays too. This includes NumPy functions that pass Python scalars internally: np.ones, np.full, np.ones_like, mean, var, std, np.median, np.isclose, np.allclose and np.round. The workarounds are NumPy scalars or arrays, e.g. ld / np.float64(2), np.full(3, np.longdouble(1), dtype=ld.dtype) or np.ones(3).astype(ld.dtype). SLEEF arrays are unaffected.

Bug fixes

  • Assigning a QuadPrecision into an array of the other backend, or casting an object array of them, stored the bytes under the wrong backend (0.0 instead of 1.5 above).
  • Unary ufuncs with an out= array of the other backend and casting="unsafe" wrote values in the wrong format.
  • QuadPrecision(q) rounded a same-backend q through float64; it now copies exactly.

Implementation

  • quad_to_quad_resolve_descriptors is the single point that refuses a cross-backend cast. The cast loop's cross-backend branch and quad_to_quad_same_value_check are kept as they are, so BUG: inter-backend casts lose precision by converting through double #112 only has to replace the float64 conversions and allow the cast in the resolver.
  • common_instance, the binary, comparison and unary resolvers, setitem and the scalar paths raise through shared helpers in dtype.h (quad_set_backend_mismatch_error, quad_loop_backend, quad_out_loop_descr).
  • The float64-based same_value tests for cross-backend casts are kept as strict xfails tagged gh-112. Once casts between backends are allowed, they XPASS and fail CI until the markers are removed.

Tests and docs

  • New tests/test_backend_mixing.py covers every path above, the literal limitation, the workarounds, and that NumPy dtypes are unaffected. The new tests fail on main.
  • Docs: a "Mixing backends" section in docs/api/core.md, and the changes above in the README changelog under "Unreleased".

CI

big_endian.yml skips pandas 3.1.0 on s390x. Its sdist does not build there (pandas-dev/pandas#71548, fix planned for 3.1.1), and s390x has no pandas wheels. pandas!=3.1.0 keeps test_pandas_strrep running on 3.0.6 and moves to 3.1.1 automatically.

AI Disclosure

Used claude code as assistant for testing and writing PR/docs

@SwayamInSync SwayamInSync added bug Something isn't working v1.1 labels Nov 3, 2026
@SwayamInSync

Copy link
Copy Markdown
Member Author

I believe the earlier behaviour was pretty non-deterministic and buggy, while I will be working on adding support of proper intermediate (f64) free inter-backend working (planned for future releases). We can strictly error-out the any inter-backend use for now instead of being sloppy with the implementation

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working v1.1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant