Make Complex<T> conform to C23 Annex G special values#131132
Make Complex<T> conform to C23 Annex G special values#131132tannergooding wants to merge 16 commits into
Conversation
Handle infinity/NaN and signed-zero branch-cut cases for Sqrt per G.6.4.2 and add the special-value prologue for Exp per G.6.3.1. Non-generic Complex delegates here, so update the two intentionally-changed Sqrt table rows. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Give Sinh/Cosh/Tanh explicit Annex G.6.2 special-value prologues and derive Sin/Cos/Tan from them for non-finite inputs, leaving the finite numeric cores unchanged. Update the delegated non-generic Tan/Tanh table rows that flip. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cover Sqrt/Exp/Log/Sin/Cos/Tan/Sinh/Cosh/Tanh special-value results against the C23 Annex G value tables, shared across Complex<double>, Complex<float>, and Complex<Half> so signed-zero, infinity, and NaN handling is verified to be type-independent. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Give Asin/Acos/Atan explicit Annex G prologues for non-finite inputs, derived from the casinh/cacos/catanh value tables (casin(z) = -i casinh(iz), catan(z) = -i catanh(iz)). The finite numeric cores are untouched. Adds the inverse functions to the shared special-value harness and updates the delegating non-generic Complex Asin/Acos advanced tables to the new Annex G results; ACos round-trip smoke test now skips inputs where Cos overflows to a non-finite intermediate. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Route Sin/Sinh/Cos/Cosh/Tanh/Atan/Log/Log10/Exp through the Complex<double> implementations so the non-generic type inherits the C23 Annex G special-value handling already verified for Complex<T>. Update the affected *_Advanced special -value tables to the conformant results and guard the Pow test oracle where its deferred polar formula diverges from the Exp/Log oracle. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add the Annex G.5.1 infinity recovery to operator * and operator / so an infinite operand yields a directed infinity instead of a spurious NaN, and route the scalar operators and Reciprocal through the complex path so they stay consistent. Pow now defers non-finite or magnitude-overflowing inputs to Exp(power * Log(value)), matching cpow's cexp(w*clog(z)) special values. Divide keeps Smith's formula (Smith 1962) with the recovery layered on top; it is normatively conformant and stays accurate for large-magnitude dividends where the pure Annex G reference would overflow. Add exhaustive Multiply/Divide/Reciprocal special-value tables to the generic harness, generated from an independent literal Annex G reference across double/float/Half, and guard the naive/magnitude oracles in the non-generic arithmetic tests for the recovered cases. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
cabs is conformant via T.Hypot, which returns +inf for an infinite component even when the other part is NaN; add a table pinning the Annex G.6 special rows across double/float/Half. Finite-finite magnitudes are ordinary accuracy (and not exact across the three types), so they stay out of the table. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bring the Annex G recovery in the multiply/sqrt/inverse-trig code and the test reference helpers in line with the repo's always-brace convention, and separate the sequential recovery if-blocks with blank lines for legibility. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Break up the back-to-back if statements in the multiply Annex G recovery, the inverse-trig quadrant fixups, and the test reference helpers so each guard reads on its own instead of running together. 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. |
|
Added When you commit this breaking change:
Tagging @dotnet/compat for awareness of the breaking change. |
There was a problem hiding this comment.
Pull request overview
This PR updates System.Numerics.Complex<T> (and, by delegation, System.Numerics.Complex) to follow C23 Annex G special-value behavior (signed zeros, infinities, NaNs) across arithmetic and elementary functions, and adds/updates tests to validate the new directed results.
Changes:
- Implement Annex G.5.1-style special-value recovery for
Complex<T>multiply/divide and add Annex G prologues for several elementary functions. - Route non-generic
Complexoperators and math functions throughComplex<double>to pick up the updated special-value behavior. - Add a new special-value test harness exercising
Complex<double/float/Half>over a full special-value grid and adjust existing tests/oracles to defer to it.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.Generic.cs | Adds Annex G special-value handling to core operators and selected elementary functions for Complex<T>. |
| src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs | Delegates non-generic Complex operations to Complex<double> to inherit the updated behavior. |
| src/libraries/System.Runtime.Numerics/tests/ComplexTests.SpecialValues.cs | New exhaustive special-value conformance harness (double/float/Half) using an independent reference. |
| src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs | Updates/relaxes legacy test oracles and expectations to align with Annex G special-value behavior. |
| src/libraries/System.Runtime.Numerics/tests/System.Runtime.Numerics.Tests.csproj | Includes the new ComplexTests.SpecialValues.cs test file in the test project. |
Copilot's findings
Comments suppressed due to low confidence (1)
src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.Generic.cs:335
operator /(Complex<T>, T)now always promotes the scalar divisor to a complex value and routes through the full Complex/Complex divide path. That adds extra work to the common finite, non-zero divisor case. A simple fast path for finite inputs can keep the new Annex-G behavior (via the fallback) without regressing typical scalar-division performance.
{
// Promote to (right + 0i) so Annex G special-value behavior stays consistent
// with the Complex/Complex operator.
return left / new Complex<T>(right, T.Zero);
}
- Files reviewed: 5/5 changed files
- Comments generated: 2
Hoist rho = Abs(value) above the non-finite/overflow prologue and reuse it for the overflow check, so the finite common path does one hypot instead of two. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The C23 Annex G.5.1 dual-NaN recovery blocks in operator * and operator / bloated those methods past the JIT inline budget, pessimizing the finite common case. Move the recovery into NoInlining helpers so the naive arithmetic stays small and inlineable; the recovery is a cold call taken only when both result components are already NaN. No behavioral change -- full special-value suite still passes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
In the +-INF + iy (finite y) branch of Tanh, the zero imaginary part takes the sign of sin(2y). Computing 2*y directly overflows to an infinity once |y| passes MaxValue/2, so sin() collapses to a NaN and the recovered zero sign degrades to the platform's NaN sign bit. Compute sin(2y) as 2*sin(y)*cos(y) instead, which keeps the sign for every finite y. Add a regression pinning the Annex G symmetry ctanh(conj(z)) == conj(ctanh(z)) across double/float/Half. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add the non-finite-real Asin/Acos/Atan rows (pi/2, pi/4, pi, 3pi/4 real parts) that the shared oracle previously omitted, verified bit-exact across double/float/Half, plus a Pow test pinning the C23 defer to Exp(power*Log(value)) for non-finite and magnitude-overflowing inputs. Clarify the divide-by-zero guard comment to note NaN results (e.g. 0/0) as well as directed infinities. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/libraries/System.Runtime.Numerics/tests/ComplexTests.SpecialValues.cs:55
- Same as the unary
Verifyoverload: the tuple-form context string uses defaultdoubleformatting, which loses the-0.0sign and makes sign-of-zero failures hard to interpret. It’s worth preserving negative-zero in the printed input tuples for these special-value tests.
private static void Verify<T>(Func<Complex<T>, Complex<T>, Complex<T>> op, string name, double leftReal, double leftImaginary, double rightReal, double rightImaginary, double expectedReal, double expectedImaginary, bool exactZeroSign)
where T : IFloatingPointIeee754<T>, IMinMaxValue<T>
{
Complex<T> left = new Complex<T>(T.CreateTruncating(leftReal), T.CreateTruncating(leftImaginary));
Complex<T> right = new Complex<T>(T.CreateTruncating(rightReal), T.CreateTruncating(rightImaginary));
Complex<T> actual = op(left, right);
string context = $"{name}<{typeof(T).Name}>(({leftReal}, {leftImaginary}), ({rightReal}, {rightImaginary})).";
AssertSame(actual.Real, expectedReal, context + "Real", exactZeroSign);
AssertSame(actual.Imaginary, expectedImaginary, context + "Imaginary", exactZeroSign);
- Files reviewed: 5/5 changed files
- Comments generated: 3
The non-generic Complex.Sin documented that a large imaginary part overflows Cosh/Sinh even when the product would be representable. Delegating to Complex<T> dropped that note; restore it on the shared Sin/Cos finite paths (which Sinh/Cosh route through) so the known limitation stays documented at its source. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.Generic.cs:327
- This comment says the zero-divisor recovery produces a "directed infinity", but
∞ * 0still yieldsNaNfor zero dividend components (and NaN dividend components remain NaN). Consider rewording to reflect that this branch scales the dividend by a directed infinity (producing infinities and/or NaNs depending on the dividend components).
if ((c == T.Zero) && (d == T.Zero) && (!T.IsNaN(a) || !T.IsNaN(b)))
{
// Divisor is zero and the dividend is not fully NaN: directed infinity.
T inf = T.CopySign(T.PositiveInfinity, c);
x = inf * a;
y = inf * b;
- Files reviewed: 5/5 changed files
- Comments generated: 1
|
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. |
eiriktsarpalis
left a comment
There was a problem hiding this comment.
Do we need a feature switch so that users can flip back to the old semantics, or is this going to be too complicated to maintain going forward?
Having taken a closer look, I understand that the divergences specifically concern special value recovery. Given you've factored these into distinct methods, a feature flag seems potentially feasible. |
Given this is a correctness fix, the default preference is to not opportunistically provide anything here, but it is something we could consider if enough feedback is given. Such switches often introduce problems because its typically an all or non global switch for the program, which can fix some and break others, particularly for math oriented APIs like this. So we typically recommend users who are truly dependent on the old behavior to roll their own helper functions for just their code by copying the old (MIT licensed) implementation (which is often a trivial 1-2 lines). |
This makes
Complex<T>(and, by delegation, the non-genericComplex) conform to the C23 Annex G (IEC 60559-compatible complex arithmetic) special-value requirements for signed zeros, infinities, and NaNs. Complex numbers are outside the scope of IEEE 754 itself, so Annex G is the relevant specification of the special-value behavior IEEE 754 otherwise implies for the scalar operations these build on. The non-genericComplexdefers most of its implementation toComplex<double>, so the conformance flows through to the shipped type.The elementary functions (
Sqrt,Exp,Log/Log10, and the trig / hyperbolic / inverse-trig functions) gain Annex G prologues for non-finite and overflowing inputs while leaving the finite numeric cores untouched.operator *gains the Annex G.5.1 infinity recovery so an infinite operand produces a directed infinity instead of a spurious NaN; it is bit-exact to the Annex G reference over the full special-value grid.operator /keeps Smith's formula with the recovery layered on top. It is normatively conformant and stays accurate for large-magnitude dividends where the illustrative Annex G reference algorithm would overflow; it differs from that reference only in the sign of a zero-valued quotient component, which Annex G explicitly leaves unspecified.The scalar
Complex<T> * T/Complex<T> / Toperators andReciprocalroute through the complex path so their special-value behavior stays consistent, andPowdefers non-finite or magnitude-overflowing inputs toExp(power * Log(value)), matchingcpow'scexp(w * clog(z))special values.A new generic special-value harness exercises
Multiply/Divide/Reciprocal/Abs(and the elementary functions) acrossComplex<double>,Complex<float>, andComplex<Half>, with the expected values generated from an independent literal Annex G reference. The legacy non-generic oracles that computed NaN or overflowed on the special rows now defer to that harness.This is a behavioral (breaking) change for special-value inputs to
Complexarithmetic and math functions: many of these previously returnedNaNwhere Annex G requires a directed infinity, a signed zero, or a specific signed result.Breaking-change documentation: dotnet/docs#54845.
Full
System.Runtime.Numericssuite: 8368 passing, 0 failed, 0 skipped.Note
This PR description was drafted with the assistance of GitHub Copilot.