Skip to content

Conversation

@SwayamInSync
Copy link
Member

closes #153

As per the title

@SwayamInSync SwayamInSync added this to the v1.0 milestone Dec 26, 2025
@SwayamInSync
Copy link
Member Author

Details for easing the review process

Only casts.cpp and test_quaddtype requires the major review, I added comments wherever I made the changes to give reasoning for that step, the rest files are modified for the caues listed below

Small refactoring

  • quad_value union with __float128 causes lot of ABI & compiler optimization issues in C++, hence refactoring the methods to not perform any kind of copy of this passed union instead use pointers
  • Inter-backend operation is broken in previous builds, not sure why someone wants to go in that direction, but just for the sake of completeness I fixed it here as well, to implement the same_value casting between QuadPrecision with different backends
  • Remove the aligned/unlaigned separate loops with their templated versions

@SwayamInSync
Copy link
Member Author

Tagging everyone involved in the prev PR discussions to have a look
@seberg @ngoldbaum @mattip @juntyr


if (given_descrs[0]->backend != given_descrs[1]->backend) {
// Different backends require actual conversion, no view possible
*view_offset = NPY_MIN_INTP;
Copy link
Contributor

Choose a reason for hiding this comment

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

what does the view_offset mean?

Copy link
Member Author

Choose a reason for hiding this comment

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

it signals numpy that whether this cast is possible without allocating new memory i.e. return a simple memory view (which in this case is casting to same backends). If memory must need to be allocated (inter-backend casts) then we set this flag to a "special" value, which is here NPY_MIN_INTP (defined as minimum value of long int)

loop_descrs[1] = given_descrs[1];

if (given_descrs[0]->backend != given_descrs[1]->backend) {
// Different backends require actual conversion, no view possible
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we stored them in a union so that views would be possible? Do we know why not?

In any case explicit casting is better here since it better communicates that loss may happen.

I know we also export a function to check if long double is float128, if so should we allow the cast from sleef to long double as safe?

Copy link
Member Author

Choose a reason for hiding this comment

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

Union is a convenient and efficient way to store only one of the values, that does not gurantee that stored bytes will be interpreted in same manner (atleast on systems where __float128 is not defined and longdouble != 128 bits)
On systems Sleef_quad can be __float128 or a struct of 2 int64, the interpretation and handling of bytes will be needed for cross-platform support.

I know we also export a function to check if long double is float128, if so should we allow the cast from sleef to long double as safe?

This is indeed possible but I believe this flag should represent a general scenario, as if somebody on their system has longdouble == 128 bits and they checked and found "oh cast is safe" and they thought its universal as in NumPy we try to keep things supported cross-platform. Although I am against of users performing inter-backend operations.

}

// Compare in SLEEF domain
if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value))
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't check that both are NaN, just that either is. If we guarantee that NaNs must convert successfully, then just saying that as

if (Sleef_iunordq1(in_val->sleef_value, in_val->sleef_value))
    return 1; // NaN input, output guaranteed NaN

would be more explicit

Copy link
Contributor

Choose a reason for hiding this comment

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

If we don't want to guarantee it (and the long double check doesn't) then we should use

if (Sleef_iunordq1(in_val->sleef_value, in_val->sleef_value) && Sleef_iunordq1(roundtrip.sleef_value, roundtrip.sleef_value))

Copy link
Contributor

Choose a reason for hiding this comment

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

also handle this in all cases below

Copy link
Member Author

Choose a reason for hiding this comment

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

Right I forgot this is an OR condition, I'll fix this to assess on based on inputs AND ensuring NAN roundtrip

return 1; // Both NaN
if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value))
return 1; // Equal
if (Sleef_icmpeqq1(in_val->sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have this extra check? If the previous equality check doesn't catch +0.0 == -0.0, then this one would still check (+0.0 == +0.0) && (-0.0 == +0.0) which is no better

Copy link
Contributor

Choose a reason for hiding this comment

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

also handle this in all cases below

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh wait, there's another fault I forgot to test the sign preservation in 0 case, its possible the casting itself will be broken.
I'll update it

QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)context->descriptors[1];
QuadBackendType backend_in = descr_in->backend;
QuadBackendType backend_out = descr_out->backend;
int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG);
Copy link
Contributor

Choose a reason for hiding this comment

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

can this be a bool?

{
long double ld = in_val.longdouble_value;
if (std::isnan(ld)) {
out_val.sleef_value = QUAD_PRECISION_NAN;
Copy link
Contributor

Choose a reason for hiding this comment

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

could we preserve the sign here?

// Compare original and roundtripped values
if (backend == BACKEND_SLEEF) {
// NaN == NaN for same_value purposes
if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value))
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

QuadBackendType backend = descr_in->backend;

npy_intp unicode_size_chars = descrs[1]->elsize / 4;
int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG);
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

quad_value roundtrip = to_quad<T>(*y, backend);
if(backend == BACKEND_SLEEF)
{
if(Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value))
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

Comment on lines +137 to +139
// we could allow, but this will be bad
// Two values that are different in quad precision,
// might appear equal when converted to double.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// we could allow, but this will be bad
// Two values that are different in quad precision,
// might appear equal when converted to double.
// we could allow this, but it would be bad
// since two values that are different in quad precision
// might appear equal when converted to double.

}
else {
return Sleef_cast_from_doubleq1(in_val->longdouble_value);
return Sleef_cast_from_doubleq1((double)(in_val->longdouble_value));
Copy link
Contributor

Choose a reason for hiding this comment

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

we use static_cast elsewhere?

])
@pytest.mark.parametrize("value", [
"0.0", "-0.0", "1.0", "-1.0", "3.14159265358979323846",
"inf", "-inf", "nan", "1e100", "1e-100",
Copy link
Contributor

Choose a reason for hiding this comment

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

please add -nan case as well and check that the sign is preserved

Copy link
Contributor

Choose a reason for hiding this comment

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

also check that the sign is preserved for -0.0 and +0.0

@pytest.mark.parametrize("dtype", [
np.float16, np.float32, np.float64, np.longdouble
])
@pytest.mark.parametrize("val", [0.0, -0.0, float('inf'), float('-inf'), float('nan')])
Copy link
Contributor

Choose a reason for hiding this comment

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

please add -nan and check that the sign is preserved, same for +0.0 and -0.0

values = [
"0.0", "-0.0", "1.0", "-1.0",
"3.14159265358979323846264338327950288", # pi with full quad precision
"inf", "-inf", "nan",
Copy link
Contributor

Choose a reason for hiding this comment

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

what happens for -nan

def test_same_value_cast_strings_enough_width(self, dtype):
"""Test that string types with enough width can represent quad values exactly."""
values = [
"0.0", "-0.0", "1.0", "-1.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

can we also check the sign for +0.0 and -0.0?

def test_same_value_cast_strings_narrow_width(self, dtype):
"""Test that string types with narrow width fail for values that need more precision."""
# Values that can fit in 10 chars should pass
passing_values = ["0.0", "1.0", "-1.0", "inf", "-inf", "nan"]
Copy link
Contributor

Choose a reason for hiding this comment

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

also check -0.0 and -nan

0.0, -0.0, 1.0, -1.0,
0.5, 0.25, 0.125,
2.0, 4.0, 8.0,
"inf", "-inf", "nan",
Copy link
Contributor

Choose a reason for hiding this comment

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

also -nan and sign checks for nan and 0.0

Copy link
Contributor

@juntyr juntyr left a comment

Choose a reason for hiding this comment

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

Thanks @SwayamInSync for your work! My comments are mostly nits and extra tests, I'll review again afterwards

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use same_value casting in astype

2 participants