Make DegreesToRadians and RadiansToDegrees correctly rounded - #132096
Make DegreesToRadians and RadiansToDegrees correctly rounded#132096tannergooding wants to merge 4 commits into
Conversation
The conversion factor is not representable, so multiplying by a rounded constant rounds twice and leaves up to a full ulp of error. Carry the factor as a multi-limb value instead and fold the limbs back with a single rounding, over the whole domain including the subnormals. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @dotnet/area-system-numerics |
There was a problem hiding this comment.
Pull request overview
Improves the correctness of the DegreesToRadians / RadiansToDegrees conversions across scalar and SIMD APIs by avoiding double-rounding and using correctly-rounded wide-constant multiplication (including a rare slow path and subnormal scaling for double), plus adding targeted edge/near-tie tests.
Changes:
- Reworks
doubleconversions to use ahead+mid+tailmulti-limb constant with decidable 2-limb fast path, 3-limb fallback, and a scaled path for very small inputs. - Reworks
float(andHalf/BFloat16) conversions to avoid overflow/inaccuracy by multiplying by thedoublehead constant and rounding once. - Adds/updates SIMD and decimal IEEE-754 decimal helpers + extensive edge/near-tie tests for all affected types.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/SingleTests.cs | Adds bitwise edge-case tests for float degrees/radians conversions (zero/subnormals/overflow/sign). |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Numerics/BFloat16Tests.cs | Adds bitwise edge-case tests for BFloat16 conversions. |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/HalfTests.cs | Adds bitwise edge-case tests for Half conversions. |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DoubleTests.cs | Adds bitwise edge/near-tie tests for double conversions (min/max cutoffs + closest-midpoint cases). |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs | Adds NaN/Inf/zero/extrema/subnormal + near-tie bit-pattern tests for Decimal64 conversions. |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs | Adds NaN/Inf/zero/extrema/subnormal + near-tie bit-pattern tests for Decimal32 conversions. |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs | Adds NaN/Inf/zero/extrema/subnormal + near-tie bit-pattern tests for Decimal128 conversions. |
| src/libraries/System.Runtime/ref/System.Runtime.cs | Adds new Decimal32/64/128.DegreesToRadians and RadiansToDegrees members to the public ref surface. |
| src/libraries/System.Private.CoreLib/src/System/Single.cs | Changes scalar float conversions to multiply by double head constants (single rounding, avoids intermediate overflow). |
| src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs | Introduces wide-constant SIMD machinery for double and widened float conversions, including FMA-based roundoff recovery. |
| src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs | Routes SIMD degrees/radians conversions to the new VectorMath paths, with widening selection for float. |
| src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs | Routes SIMD degrees/radians conversions to the new VectorMath paths, with widening selection for float. |
| src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs | Routes SIMD degrees/radians conversions to the new VectorMath paths, with widening selection for float. |
| src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs | Routes SIMD degrees/radians conversions to the new VectorMath paths. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs | Routes Vector<T> degrees/radians conversions to the new VectorMath paths. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/ITrigonometricFunctions.cs | Updates DIM commentary to clarify why interface-default implementations can’t generally be correctly rounded. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs | Adds Decimal32 conversion APIs implemented via wide-constant decimal helper. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs | Adds Decimal64 conversion APIs implemented via wide-constant decimal helper. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs | Adds Decimal128 conversion APIs implemented via wide-constant decimal helper and documents the constant layout rationale. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/BFloat16.cs | Simplifies conversions to delegate directly to the updated float implementations. |
| src/libraries/System.Private.CoreLib/src/System/Half.cs | Simplifies conversions to delegate directly to the updated float implementations. |
| src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs | Adds a shared helper to multiply decimal IEEE-754 values by a split wide constant (single rounding) + optimizes digit dropping with chunked division. |
| src/libraries/System.Private.CoreLib/src/System/Double.cs | Implements correctly-rounded double conversions using multi-limb constants, decidable fast path, 3-limb fallback, and subnormal scaling. |
Review details
- Files reviewed: 23/23 changed files
- Comments generated: 1
- Review effort level: Lite
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/libraries/System.Private.CoreLib/src/System/Double.cs:2189
- The trailing comment is incomplete (ends mid-sentence), which makes this block harder to understand/maintain.
// `sum` sat exactly between `result` and `next`, so the true value did not
- Files reviewed: 23/23 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
I took a look at the binary scalar portion of this, and am glad to see interest in correctly rounded math. For
The following appears to be correctly rounded for
For tests for For I'm also curious how the performance is without hardware FMA, and how the proposed approach compares to just calling FusedMultiplyAdd anyway. If you're going to go with correctly rounded implementations here, it would be nice if the documentation promised that. Much of the benefit of correct rounding is unavailable if it's not a documented promise. I suppose you might not want to commit to that, in which case it would still be good to promise some limit on the inaccuracy, such as that the maximum ULP error is less than 1. |
|
Reiteration of exactly what the PR is doing and/or what the original post describes is not beneficial. Checking in a 65k entry dataset is likewise not beneficial, its the type of thing that is more than sufficient to cover via simple mathematics and an exhaustiveness test prior to check-in. If the algorithm changes in the future, devs should do the due diligence in testing additional scenarios. The tests given are generally sufficient, covering some basic values, the well known special values, and various edge case scenarios that are common to showcasing floating-point 1-2 ULP errors. The mid-point cases can be derived mathematically based on the known error from the head/tail and are covered in the code comments around those constants. Using a Veltkamp/Dekker split is a well known scenario for when no-FMA exists because it explicitly does less work. This is important for platforms, like WASM, without FMA. It also impacts the default case for x64 NAOT since the baseline is still x86-64-v2 (SSE4.2+POPCNT) We typically do not document exact precision guarantees of functions. They are simply documented when they defer to some other implementation. |
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/libraries/System.Runtime/ref/System.Runtime.cs:11484
- This PR adds new public API in the ref assembly, but the only linked issue in the PR description (#131930) does not have the
api-approvedlabel. If these APIs were approved via DIM/alternative process, please link the actual approval issue (withapi-approved) or otherwise provide the standard approval signal so the API approval verification procedure can be satisfied.
- Files reviewed: 23/23 changed files
- Comments generated: 0 new
- Review effort level: Lite
I left out the implicit question for
The point of the hash suggestion was that you could check in a single hash value that covers any number of test cases. I am not in a position to say whether it makes sense in this context, but figured I'd mention the idea anyway.
I'm aware. But if you can easily give a useful upper bound (like <1 ULP), why not? |
x * (pi / 180)rounds twice -- once forming the constant, once forming the product -- leaving up to ~1 ulp of error. Over all2^31finite positivefloatinputs the current shape misrounds 37.9% of them, and 0.042% are off by 2 ulp.Carry the factor as a multi-limb value and fold the limbs back with a single rounding. Two limbs suffice for all but a rare input, and whether they did is itself decidable, so the third limb is only paid for when needed; a scaled path covers the subnormals.
Covers
float,double,Half,BFloat16,Vector64/128/256/512,Vector<T>, andDecimal32/64/128.Fixes #131930
Validated against exact rational arithmetic: exhaustively for
Half,BFloat16,float(all2^31) andDecimal32(60M); fordoubleand the larger decimals against an enumerated near-tie set (372k vectors) plus replay of the whole subnormal band. 0 misrounded in every case.Cost in ns per operation, over a 4096 element
doublearray storing to a destination, so aVector256entry is one call handling four elements. Ad hoc harness on a 7950X, and both directions measure the same within noise.Vector128Vector256Vector512The old code was bound by
vdivsd/vdivpdthroughput rather than by the multiply, which is why it cost about the same at every width. The new code has no divide and is FP issue bound instead, so it scales with the work rather than with the divider.Vector512is double pumped on this part, hence roughly twiceVector256in both columns.Inputs below the point where the two limb form stops working take a scaled path, at roughly 13 ns.
Vector*.FusedMultiplyAddwhere the hardware has it and a Veltkamp split otherwise, since recovering the roundoff of a product requires a real fused multiply-add.Decimal32.DegreesToRadians(0E0)is0E-17, was0E-8.Decimal*members mirror the existingdouble/floatITrigonometricFunctionssurface.Note
Portions of this pull request were drafted with Copilot.