Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,20 @@ one where both sides are real, and `NaN` where either is not
|---|---|---|
| `"x mod 3".ToEntity().Compile<Complex, Complex>("x")(7)` | `UncompilableNodeException` | `(1, 0)` |

### A compiled product of real numbers is real at an infinity

**Silent: a compiled product or quotient with a real infinity is that infinity, where it had a `NaN` imaginary part.**
Over `System.Numerics.Complex`, in FastExpression and in the LINQ compiler, the product's imaginary part was
`∞ · 0`, which is `NaN`, so `-x` at `+oo` was `(-∞, NaN)`: a value with no imaginary part at all, which a comparison
then had no truth value for. A product or a quotient of two real numbers is now taken as one of reals. A division by
`0` is unchanged, and has no value, as evaluation has none
([#2222](https://github.com/asc-community/AngouriMath/issues/2222)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"-x".ToEntity().Compile("x").Call(double.PositiveInfinity)` | `(-Infinity, NaN)` | `(-Infinity, 0)` |
| `"x / 2".ToEntity().Compile<Complex, Complex>("x")(double.PositiveInfinity)` | `(Infinity, NaN)` | `(Infinity, 0)` |

### A compiled statement with no truth value has none

**Silent in part: a compiled comparison has the truth value evaluation gives it.** A comparison over `Complex` was refused
Expand Down
6 changes: 6 additions & 0 deletions Sources/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -904,3 +904,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

[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

[Tests/UnitTests/Common/RealProductCompilationTest.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
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,13 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value
case InstructionType.CALL_MINUS:
stack.Push(stack.Pop() - stack.Pop());
break;
// Real where both sides are, so that an infinity keeps an imaginary part of 0:
// https://github.com/asc-community/AngouriMath/issues/2222
case InstructionType.CALL_MUL:
stack.Push(stack.Pop() * stack.Pop());
stack.Push(AngouriMath.Core.Compilation.RealProducts.Product(stack.Pop(), stack.Pop()));
break;
case InstructionType.CALL_DIV:
stack.Push(stack.Pop() / stack.Pop());
stack.Push(AngouriMath.Core.Compilation.RealProducts.Quotient(stack.Pop(), stack.Pop()));
break;
case InstructionType.CALL_MOD:
stack.Push(Core.Compilation.RealOnly.Mod(stack.Pop(), stack.Pop()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,10 @@ private static MethodInfo Conv<TFrom, TTo>(Expression<Func<TFrom, TTo>> e)
{
{ (ExpressionType.Add, typeof(System.Numerics.Complex)), Op<System.Numerics.Complex>((a, b) => a + b) },
{ (ExpressionType.Subtract, typeof(System.Numerics.Complex)), Op<System.Numerics.Complex>((a, b) => a - b) },
{ (ExpressionType.Multiply, typeof(System.Numerics.Complex)), Op<System.Numerics.Complex>((a, b) => a * b) },
{ (ExpressionType.Divide, typeof(System.Numerics.Complex)), Op<System.Numerics.Complex>((a, b) => a / b) },
// Real where both sides are, so that an infinity keeps an imaginary part of 0:
// https://github.com/asc-community/AngouriMath/issues/2222
{ (ExpressionType.Multiply, typeof(System.Numerics.Complex)), Call<System.Numerics.Complex>((a, b) => RealProducts.Product(a, b)) },
{ (ExpressionType.Divide, typeof(System.Numerics.Complex)), Call<System.Numerics.Complex>((a, b) => RealProducts.Quotient(a, b)) },
{ (ExpressionType.Equal, typeof(System.Numerics.Complex)), Cmp<System.Numerics.Complex>((a, b) => a == b) },

{ (ExpressionType.Add, typeof(BigInteger)), Op<BigInteger>((a, b) => a + b) },
Expand Down Expand Up @@ -515,6 +517,8 @@ internal Expression ConvertMembership(Expression element, AngouriMath.Numerics.N
[ConstantField] private static readonly MethodInfo complexRemainder =
((MethodCallExpression)((Expression<Func<System.Numerics.Complex, System.Numerics.Complex, System.Numerics.Complex>>)((a, b) => RealOnly.Mod(a, b))).Body).Method;

private static MethodInfo Call<T>(Expression<Func<T, T, T>> e) => ((MethodCallExpression)e.Body).Method;

private static MethodInfo Op<T>(Expression<Func<T, T, T>> e)
=> ((BinaryExpression)e.Body).Method
?? throw new AngouriBugException($"{typeof(T)} defines this operator as an instruction, not a method");
Expand Down
31 changes: 31 additions & 0 deletions Sources/AngouriMath/Functions/Compilation/RealProducts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// 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 NumericsComplex = System.Numerics.Complex;

namespace AngouriMath.Core.Compilation
{
/// <summary>
/// The product and the quotient of two numbers carried as <see cref="System.Numerics.Complex"/>, real where both
/// are real. <see cref="System.Numerics.Complex"/> forms the product's imaginary part as <c>a.Re b.Im + a.Im b.Re</c>,
/// which is <c>∞ · 0</c>, <c>NaN</c>, where a real factor is infinite, so a compiled <c>-x</c> at <c>+oo</c> was
/// <c>(-∞, NaN)</c>, a value with no imaginary part at all, where evaluation gives <c>-oo</c>.
/// https://github.com/asc-community/AngouriMath/issues/2222
/// </summary>
internal static class RealProducts
{
public static NumericsComplex Product(NumericsComplex a, NumericsComplex b)
=> a.Imaginary == 0 && b.Imaginary == 0 ? new NumericsComplex(a.Real * b.Real, 0) : a * b;

/// <summary>
/// The quotient, real where both are real and the divisor is not 0. A division by 0 is left to
/// <see cref="System.Numerics.Complex"/>, whose <c>NaN</c> is evaluation's: <c>1/0</c> has no value.
/// </summary>
public static NumericsComplex Quotient(NumericsComplex a, NumericsComplex b)
=> a.Imaginary == 0 && b.Imaginary == 0 && b.Real != 0 ? new NumericsComplex(a.Real / b.Real, 0) : a / b;
}
}
5 changes: 1 addition & 4 deletions Sources/Tests/UnitTests/Common/MembershipCompilationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// Website: https://am.angouri.org.
//

using System.Linq;
using System.Numerics;
using AngouriMath;
using AngouriMath.Core.Exceptions;
Expand Down Expand Up @@ -99,9 +98,7 @@ public void ADivisibilityIsWhatEvaluationSays()
public void AMembershipInAnExpressionIsWhatEvaluationSays(string expression)
{
var fast = expression.ToEntity().Compile("x", "y");
// Not at an infinity, where a compiled -x has a NaN imaginary part:
// https://github.com/asc-community/AngouriMath/issues/2222
foreach (var a in points.Where(point => !point.Contains("oo")))
foreach (var a in points)
{
var evaluated = expression.ToEntity().Substitute("x", a).Substitute("y", 0).Evaled;
var value = fast.Call(Numeric(a), 0);
Expand Down
59 changes: 59 additions & 0 deletions Sources/Tests/UnitTests/Common/RealProductCompilationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// 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.Common
{
/// <summary>
/// A compiled product or quotient of real numbers is real, so an infinity keeps an imaginary part of 0, where
/// <see cref="Complex"/>'s own product made it <c>∞ · 0</c>, <c>NaN</c>: <c>-x</c> at <c>+oo</c> was
/// <c>(-∞, NaN)</c>. <a href="https://github.com/asc-community/AngouriMath/issues/2222">#2222</a>
/// </summary>
[Trait("Area", "Common")]
public sealed class RealProductCompilationTest
{
[Theory]
[InlineData("-x", "+oo")]
[InlineData("-x", "-oo")]
[InlineData("2 * x", "+oo")]
[InlineData("x / 2", "+oo")]
[InlineData("x / (-3)", "-oo")]
[InlineData("x * x", "-oo")]
[InlineData("-x + 1", "+oo")]
[InlineData("3 * x - 1", "-oo")]
public void AtAnInfinityTheValueIsEvaluations(string expression, string at)
{
var expected = ((Entity.Number.Complex)expression.ToEntity().Substitute("x", at).Evaled).ToNumerics();
var x = ((Entity.Number.Complex)at.ToEntity().Evaled).ToNumerics();
Assert.True(double.IsInfinity(expected.Real) && expected.Imaginary == 0, $"{expression} at {at} evaluates to {expected}");
Assert.Equal(expected, expression.ToEntity().Compile("x").Call(x));
Assert.Equal(expected, expression.ToEntity().Compile<Complex, Complex>("x")(x));
}

/// <summary>A division by 0 still has no value, as evaluation has none, and a complex product is a complex product.</summary>
[Theory]
[InlineData("1 / x", "0", double.NaN, double.NaN)]
[InlineData("x / x", "0", double.NaN, double.NaN)]
[InlineData("(1 + i) * x", "1 - i", 2, 0)]
[InlineData("x * x", "i", -1, 0)]
[InlineData("x / (1 + i)", "2", 1, -1)]
[InlineData("x * 3", "-2", -6, 0)]
public void ElsewhereTheValueIsWhatItWas(string expression, string at, double real, double imaginary)
{
var x = ((Entity.Number.Complex)at.ToEntity().Evaled).ToNumerics();
foreach (var value in new[] { expression.ToEntity().Compile("x").Call(x), expression.ToEntity().Compile<Complex, Complex>("x")(x) })
if (double.IsNaN(real))
Assert.True(double.IsNaN(value.Real) || double.IsNaN(value.Imaginary), $"{expression} at {at}: {value}");
else
Assert.True(Complex.Abs(value - new Complex(real, imaginary)) < 1e-15, $"{expression} at {at}: {value}");
}
}
}
Loading