From 76d78d59a1c2d7095838fd6c53187b81a7b0f82c Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 13:38:10 +0000 Subject: [PATCH] arcsin and arccos of a real number past 1 have their value For about one real argument past 1 in ten, arcsin and arccos evaluated to NaN: arcsin(6 + pi), arccos(6 + pi), arcsin(1 + sqrt(3)). Of arcsin(k + c) for k from 1 to 40 and c in pi, e, sqrt(2), sqrt(3) and ln(3), 20 of the 200 were NaN, on 2.6.0 as on master. The value exists: sign(x) pi/2 - i arccosh|x|. Number.Arcsin and Number.Arccos share ArcSinCosInner. For w = x + iy it takes beta = (|w + 1| - |w - 1|) / 2, then arcsin(beta) or arccos(beta). By the triangle inequality, beta is in [-1, 1], and for a real x past 1 it is exactly 1. The two square roots are rounded, though, and for some x the difference comes out a little past 1, where the decimal arcsine is NaN. beta is now clamped to [-1, 1], which restores a bound the rounding broke and changes no value it did not. ArcsineOfARealPastOneTest takes arcsin and arccos at plus and minus (k + c) for those 200 arguments. It compares them with sign(x) pi/2 - i arccosh|x| and with 0 or pi plus i arccosh|x|, the library's convention, measured at 9.14 and -9.14. All five constants' cases fail on master. Found by work/compilesweep: the compiled arcsin(arcsin(x + pi)) at 6 had a value, and evaluation had none. Fixes #2244. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 13 +++++ Sources/.editorconfig | 3 ++ .../Entity/Continuous/Number/Operators.cs | 10 +++- .../Convenience/ArcsineOfARealPastOneTest.cs | 52 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 Sources/Tests/UnitTests/Convenience/ArcsineOfARealPastOneTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 05b616dc3..eeb7141b5 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -139,6 +139,19 @@ and a number that passes every round is left open, since passing is not a proof | `"(10^30 + 1) in PP".ToEntity().Evaled` | `1000000000000000000000000000001 in PP` | `False` | | `"(10^30 + 57) in PP".ToEntity().Evaled` | `1000000000000000000000000000057 in PP` | `1000000000000000000000000000057 in PP` | +### `arcsin` and `arccos` of a real number past 1 have their value + +**Silent: `arcsin(x)` and `arccos(x)` of a real `x` past 1 are `sign(x) pi/2 - i arccosh|x|` and `pi/2 - arcsin(x)`, +where for about one argument in ten they were `NaN`.** The two share `(|w + 1| - |w - 1|) / 2`, which is in `[-1, 1]` by +the triangle inequality and exactly 1 for such an `x`. Rounded, it came out a little past 1 for some `x`, and the decimal +arcsine of that is `NaN`. Of `arcsin(k + c)` for `k` from 1 to 40 and `c` in `pi`, `e`, `sqrt(2)`, `sqrt(3)` and `ln(3)`, +20 were `NaN` ([#2244](https://github.com/asc-community/AngouriMath/issues/2244)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"arcsin(6 + pi)".ToEntity().Evaled` | `NaN` | `1.5707963267948966 - 2.902976737696685i`, to 15 digits | +| `"arccos(6 + pi)".ToEntity().Evaled` | `NaN` | `2.902976737696685i`, to 15 digits | + ### A system with a product equation is solved branch by branch **Silent: solutions that were missing are answered.** `EquationSystem.Solve` eliminated `x * y = 0` for `y` as `0`, diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 9bcb8732a..bd50d107e 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -926,6 +926,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Convenience/EulerGammaTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Convenience/ArcsineOfARealPastOneTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [AngouriMath/Functions/Compilation/RealProducts.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs index 30c5620be..34eb8e533 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs @@ -947,7 +947,15 @@ public static Complex Cotan(Complex num) var rho = xp1.MultiplyAndAdd(xp1, y.Multiply(y, context), context).SqrtByIntegerRoot(context); var sigma = xm1.MultiplyAndAdd(xm1, y.Multiply(y, context), context).SqrtByIntegerRoot(context); var alpha = rho.Add(sigma, context).Divide(2, context); - return (rho.Subtract(sigma, context).Divide(2, context), + // beta is (|w + 1| - |w - 1|) / 2, in [-1, 1] by the triangle inequality and exactly 1 for a real w past + // 1; the two roots are rounded, and a beta of 1 + ε made arcsin(beta) NaN, as arcsin(6 + pi) was. + // https://github.com/asc-community/AngouriMath/issues/2244 + var beta = rho.Subtract(sigma, context).Divide(2, context); + if (beta.GreaterThan(EDecimal.One)) + beta = EDecimal.One; + else if (beta.LessThan(-EDecimal.One)) + beta = -EDecimal.One; + return (beta, alpha.MultiplyAndSubtract(alpha, EDecimal.One, context).SqrtByIntegerRoot(context).Add(alpha, context).NaturalLogarithm(context).Multiply((y.IsNegative || y.IsZero) ? -1 : 1, context)); } diff --git a/Sources/Tests/UnitTests/Convenience/ArcsineOfARealPastOneTest.cs b/Sources/Tests/UnitTests/Convenience/ArcsineOfARealPastOneTest.cs new file mode 100644 index 000000000..c7049e433 --- /dev/null +++ b/Sources/Tests/UnitTests/Convenience/ArcsineOfARealPastOneTest.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System.Numerics; +using AngouriMath; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Convenience +{ + /// + /// arcsin and arccos of a real number past 1 have their value, sign(x) pi/2 - i arccosh|x| and + /// pi/2 - arcsin(x), where for one argument in ten they were NaN: the rounded (|w + 1| - |w - 1|) / 2 + /// came out a little past 1, whose decimal arcsine is NaN. arcsin(6 + pi) was one. + /// #2244 + /// + [Trait("Area", "Convenience")] + public sealed class ArcsineOfARealPastOneTest + { + private static double Arccosh(double x) => System.Math.Log(x + System.Math.Sqrt(x * x - 1)); + + private static bool Agree(Complex expected, Complex actual) => Complex.Abs(expected - actual) <= 1e-12 * System.Math.Max(1, Complex.Abs(expected)); + + [Theory] + [InlineData("pi")] + [InlineData("e")] + [InlineData("sqrt(2)")] + [InlineData("sqrt(3)")] + [InlineData("ln(3)")] + public void BothHaveTheirValue(string constant) + { + for (var k = 1; k <= 40; k++) + foreach (var sign in new[] { "", "-" }) + { + var argument = $"{sign}({k} + {constant})"; + var x = ((Entity.Number.Complex)argument.ToEntity().Evaled).ToNumerics().Real; + if (System.Math.Abs(x) <= 1) + continue; + var arcsin = ((Entity.Number.Complex)$"arcsin({argument})".ToEntity().Evaled).ToNumerics(); + var arccos = ((Entity.Number.Complex)$"arccos({argument})".ToEntity().Evaled).ToNumerics(); + var expectedArcsin = new Complex(System.Math.Sign(x) * System.Math.PI / 2, -Arccosh(System.Math.Abs(x))); + var expectedArccos = new Complex(x > 0 ? 0 : System.Math.PI, Arccosh(System.Math.Abs(x))); + Assert.True(Agree(expectedArcsin, arcsin), $"arcsin({argument}): {arcsin}, where {expectedArcsin}"); + Assert.True(Agree(expectedArccos, arccos), $"arccos({argument}): {arccos}, where {expectedArccos}"); + } + } + } +}