Skip to content

Make Complex<T> conform to C23 Annex G special values#131132

Open
tannergooding wants to merge 16 commits into
dotnet:mainfrom
tannergooding:tannergooding-complex-t-api-parity
Open

Make Complex<T> conform to C23 Annex G special values#131132
tannergooding wants to merge 16 commits into
dotnet:mainfrom
tannergooding:tannergooding-complex-t-api-parity

Conversation

@tannergooding

@tannergooding tannergooding commented Jul 21, 2026

Copy link
Copy Markdown
Member

This makes Complex<T> (and, by delegation, the non-generic Complex) 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-generic Complex defers most of its implementation to Complex<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> / T operators and Reciprocal route through the complex path so their special-value behavior stays consistent, and Pow defers non-finite or magnitude-overflowing inputs to Exp(power * Log(value)), matching cpow's cexp(w * clog(z)) special values.


A new generic special-value harness exercises Multiply / Divide / Reciprocal / Abs (and the elementary functions) across Complex<double>, Complex<float>, and Complex<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 Complex arithmetic and math functions: many of these previously returned NaN where Annex G requires a directed infinity, a signed zero, or a specific signed result.

Breaking-change documentation: dotnet/docs#54845.

Full System.Runtime.Numerics suite: 8368 passing, 0 failed, 0 skipped.

Note

This PR description was drafted with the assistance of GitHub Copilot.

tannergooding and others added 9 commits July 20, 2026 16:03
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

Copy link
Copy Markdown
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.

@tannergooding tannergooding added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Jul 21, 2026
@dotnet-policy-service dotnet-policy-service Bot added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label Jul 21, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET Breaking Change Notification DL.

Tagging @dotnet/compat for awareness of the breaking change.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 Complex operators and math functions through Complex<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

tannergooding and others added 3 commits July 20, 2026 20:30
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>
Copilot AI review requested due to automatic review settings July 21, 2026 03:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 5

Comment thread src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs
Comment thread src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.Generic.cs Outdated
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>
Copilot AI review requested due to automatic review settings July 21, 2026 04:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

Comments suppressed due to low confidence (1)

src/libraries/System.Runtime.Numerics/tests/ComplexTests.SpecialValues.cs:55

  • Same as the unary Verify overload: the tuple-form context string uses default double formatting, which loses the -0.0 sign 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>
Copilot AI review requested due to automatic review settings July 21, 2026 04:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 1

Comment thread src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 21, 2026 05:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 1

Comment thread src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.Generic.cs Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 21, 2026 05:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 ∞ * 0 still yields NaN for 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

Copy link
Copy Markdown
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 eiriktsarpalis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

@eiriktsarpalis

Copy link
Copy Markdown
Member

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.

@tannergooding tannergooding removed the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label Jul 21, 2026
@tannergooding

Copy link
Copy Markdown
Member Author

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).

@tannergooding
tannergooding enabled auto-merge (squash) July 21, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Numerics breaking-change Issue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants