-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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
Investigate audit potential/result in double remainder (REM) operation. #62992
Comments
Tagging subscribers to this area: @dotnet/area-system-numerics Issue DetailsDescriptionWhen given two doubles
And the runtime is similar with:
Talking to @tannergooding, we may want to audit this to ensure the impl we have is either following this (and fix if not), or doc better to explain what's going on. For example, if
so
the largest possible integer that is less than or equal to
As such, one interpretation is y = Math.Abs(y);
var result = Math.IEEERemainder(Math.Abs(x), y);
if (double.IsNegative(result))
{
result += y;
}
return Math.CopySign(result, x); Which can also be considered fine. However, if so, the docs should likely call this out so that the discrepancy can be better understood. Reproduction StepsConsole.WriteLine(1.0 % 0.1) Expected behaviorLess clear. Based strictly on the provided docs, either 0 or 0.09999999999999995. This ambiguity isn't great though and we should beef up the docs. Actual behavior0.09999999999999995. If desired, we should doc more precisely. Regression?No response Known WorkaroundsNo response ConfigurationNo response Other informationNo response
|
While its still fresh in my head. We internally implement this as basically over
It additionally calls out that this is functionally similar to: y = Math.Abs(y);
var result = Math.IEEERemainder(Math.Abs(x), y);
if (double.IsNegative(result))
{
result += y;
}
return Math.CopySign(result, x); If you use that algorithm for |
Edit: The below analysis is potentially incorrect and is potentially an error on my part. Namely the C# spec is ambiguous as to whether the following definition implies that
I've added an additional note on this elaborating more further down. This is ultimately a bug in our implementation since we define var n = Math.Truncate(x / y);
return x - n * y; We can't simply do the above algorithm as it may result in issues with regard to rounding since var n = Math.Truncate(x / y);
return Math.FusedMultiplyAdd(-n, y, x); On ARM64 and x86/x64 from ~2013+ this will be effectively three instructions. For x86/x64 this is (noting we should update vdivsd xmm0, xmm1, xmm2
vroundsd xmm0, xmm0, xmm0, 3
vfnmadd213sd xmm0, xmm2, xmm1 For ARM32 we don't currently accelerate For older x86/x64, this would end up being a call to the CRT |
There is a potentially interesting corner case to consider with the If |
Given that this is our behavior, shoudl we then update our docs to call this out? It feels like that if this is what we do, we should just doc that as preserving what we do feels more important than changing things just to satisfy docs that people couldn't have fully depended on anyways. |
.... especially since we - and probably just about every other language out there - are wrapping |
There are problems with wrapping |
@CyrusNajmabadi I've actually gone over this a bit more and there was an issue in what I stated above. Namely the issue is that the C# spec has an ambiguity here. In particular it defines:
The ambiguity comes from how to interpret:
In particular consider Now if you do this using the infinitely precise values represnted then For var n = Math.Truncate(x / y);
return x - n * y The correct answer is Where-as if it is intended that |
We should likely determine what the intent of the C# spec is here and I should finish doing the math to confirm that If the intent is that |
Description
When given two doubles
x
andy
and the C# operationx % y
, C# (roslyn) will push the values to the stack and emit theREM
instruction. C# defines this as:And the runtime is similar with:
Talking to @tannergooding, we may want to audit this to ensure the impl we have is either following this (and fix if not), or doc better to explain what's going on. For example, if
x=1.0
andy=0.1
then the instructions above allow for the following interpretation:so
1.0 % 0.1
is computed as1.0 - n * 0.1
the largest possible integer that is less than or equal to
1.0 / 0.1
is10.0
, which can be validated here:Console.WriteLine(10.0 <= (1.0 / 0.1))
As such, one interpretation is
1.0 - 10.0 * 0.1
which itself is evaluated to0.0
. This does not match current runtime result of0.09999999999999945
. Which likely means the algorithm is more like:Which can also be considered fine. However, if so, the docs should likely call this out so that the discrepancy can be better understood.
Reproduction Steps
Console.WriteLine(1.0 % 0.1)
Expected behavior
Less clear. Based strictly on the provided docs, either 0 or 0.09999999999999995. This ambiguity isn't great though and we should beef up the docs.
Actual behavior
0.09999999999999995. If desired, we should doc more precisely.
Regression?
No response
Known Workarounds
No response
Configuration
No response
Other information
No response
The text was updated successfully, but these errors were encountered: