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
12 changes: 12 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,18 @@ made `0^(-1)` an infinity and `0^0` 1 ([#2286](https://github.com/asc-community/
| `"x^(-1)".Compile<double, double>("x")(0)` | `+Infinity` | `NaN` |
| `"x^x".Compile<double, double>("x")(0)` | `1` | `NaN` |

### A compiled remainder of two integers by zero has no value

**Not silent: a compiled function that threw is `NaN`, as in evaluation.** The remainder of two integers is compiled
over the integers, and the runtime's `%` throws for a divisor of 0. So `2 mod 0`, or `abs(2) mod (2 mod (-1))`, compiled
by the LINQ compilers threw `DivideByZeroException` when the function was called. No issue was filed; a check of compiled
expressions against evaluation found it.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"x + (2 mod 0)".Compile<double, double>("x")(1)` | `DivideByZeroException` | `NaN` |
| `"x + (2 mod 0)".Compile<Complex, Complex>("x")(1)` | `DivideByZeroException` | `NaN` |

### `tanh` and `coth` of a large number are their limits

**Silent: `tanh` and `coth` of a large positive number evaluate and compile to `1`, where they were `NaN`, and at
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ private Expression NumericOperand(Expression expr)
? ConvertType(expr, typeof(double))
: expr;

private static bool IsIntegral(Type type)
=> type == typeof(int) || type == typeof(long) || type == typeof(BigInteger);

private Expression ShouldBeAtLeastDouble(Expression expr)
{
if (typeLevelInHierarchy[expr.Type] < typeLevelInHierarchy[typeof(double)])
Expand Down Expand Up @@ -259,6 +262,13 @@ Divf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(ri
// https://github.com/asc-community/AngouriMath/issues/2195
Modf when left.Type == typeof(System.Numerics.Complex) && right.Type == typeof(System.Numerics.Complex)
=> Expression.Call(complexRemainder, left, right),
// A remainder by zero has no value, as in evaluation. Of two integers, which over
// double or Complex are constants, it is compiled over the integers, where the
// runtime's % throws for a divisor of 0: 2 mod 0 over double threw
// DivideByZeroException when called.
Modf { Divisor: var divisor } when IsIntegral(left.Type) && divisor.Vars.Count == 0
&& divisor.Evaled is Integer { IsZero: true }
=> Expression.Constant(double.NaN),
Modf => Operator(ExpressionType.Modulo,
Operator(ExpressionType.Add, Operator(ExpressionType.Modulo, left, right), right),
right),
Expand Down
28 changes: 28 additions & 0 deletions Sources/Tests/UnitTests/Common/DivisionByZeroOverDoubleTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,34 @@ public void AMatrixOverZeroHasNoEntries()
Assert.True(double.IsNaN(divided[i, j]), $"[{i}, {j}] is {divided[i, j]}");
}

/// <summary>
/// A remainder by zero, the same. Of two integers the remainder is compiled over the integers, and the
/// runtime's % threw DivideByZeroException for a divisor of 0 when the function was called. Found by a check
/// of compiled expressions against evaluation; no issue was filed.
/// </summary>
[Theory]
[InlineData("2 mod 0")]
[InlineData("x + (2 mod 0)")]
[InlineData("abs(2) mod (2 mod (-1))")]
[InlineData("x mod 0")]
[InlineData("x mod (x - x)")]
public void ARemainderByZeroHasNoValue(string expression)
{
var entity = expression.ToEntity();
Assert.True(double.IsNaN(entity.Compile<double, double>("x")(0.7)), $"{expression} over double");
Assert.True(float.IsNaN(entity.Compile<float, float>("x")(0.7f)), $"{expression} over float");
Assert.True(double.IsNaN(entity.Compile<System.Numerics.Complex, System.Numerics.Complex>("x")(0.7).Real), $"{expression} over Complex");
Assert.True(double.IsNaN(entity.Compile("x").Call(0.7).Real), $"{expression} by FastExpression");
Assert.True(entity.Substitute("x", 0.7).Evaled.IsNaN, $"{expression} evaluates to a value");
}

[Theory]
[InlineData("7 mod 3 + x", 0.5, 1.5)]
[InlineData("(-7) mod 3 + x", 0.5, 2.5)]
[InlineData("7 mod (-3) + x", 0.5, -1.5)]
public void ARemainderOfIntegersIsTheFlooredOne(string expression, double at, double value)
=> Assert.Equal(value, expression.ToEntity().Compile<double, double>("x")(at));

[Theory]
[InlineData("1/x", 4, 0.25)]
[InlineData("x^(1/2)", 0, 0)]
Expand Down
Loading