-
-
Notifications
You must be signed in to change notification settings - Fork 15
FEAT: Implementing same_value casting rule in quaddtype
#246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Details for easing the review processOnly Small refactoring
|
|
Tagging everyone involved in the prev PR discussions to have a look |
|
|
||
| if (given_descrs[0]->backend != given_descrs[1]->backend) { | ||
| // Different backends require actual conversion, no view possible | ||
| *view_offset = NPY_MIN_INTP; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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 NaNwould be more explicit
There was a problem hiding this comment.
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))There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
| // we could allow, but this will be bad | ||
| // Two values that are different in quad precision, | ||
| // might appear equal when converted to double. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // 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)); |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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')]) |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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"] |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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
juntyr
left a comment
There was a problem hiding this 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
closes #153
As per the title