From a0e37c57f2bff5b7b489045a829b93094be44980 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 15 Sep 2026 23:08:30 +0000 Subject: [PATCH 1/2] The fixed-point series run on System.Numerics.BigInteger, and a fixed-point value is read back as a decimal by scaling to a power of ten #1346 and #1347 put the logarithm, the exponential, the sine, the cosine and the arctangent into fixed point on PeterO's EInteger. Measured against mpmath on the same machine the transcendental rows were still nine to seventeen times behind, and the reason is the integer type: one multiply of two hundred-digit integers is 1,271 ns in EInteger and 109 ns in System.Numerics.BigInteger (8,087 against 1,364 at five hundred digits, 75,850 against 12,036 at two thousand), and the BCL's is at parity with CPython's int, which is what mpmath's digits live in. The series run on BigInteger now, converted once on the way in and once on the way out as two's-complement bytes, and the way out no longer multiplies by 5^bits and asks EDecimal to round a number of four times the digits: the fixed-point integer is scaled to a power of ten six digits past the precision in BigInteger, and only the rounding is EDecimal's. The hundred-digit values are the ones pinned before, to the last digit. EvalTrig 153 us / 260,728 B -> 83 us / 164,728 B EvalTrigPrecise 2.16 ms / 2,525,707 B -> 1.05 ms / 718,778 B EvalTranscendentalFresh 83 us / 128,664 B -> 33 us / 59,136 B SolveEasy 1.19 ms / 1,675,487 B -> 0.49 ms / 981,996 B SimplifyHard -3.9% B every other entry within 0.1% Alone, one probe on both builds: at a hundred digits sin(x) is 15 us where it was 47, ln(x) 18 where it was 52, e^x 10 where it was 32, arctan(x) 25 where it was 49; at five hundred digits sin(x) 228 us where it was 1,273, ln(x) 777 where it was 3,921; at two thousand sin(x) 11.7 ms where it was 40.7 and ln(x) 26 where it was 121. The gate's baseline is from this run; the performance log has the entry. Four suites green (11,345 / 134 / 18 / 41). Part of #1338. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../WhatsNew/version_performance_control.md | 35 +++ .../Functions/InternalAMExtensions.cs | 219 +++++++++++------- 2 files changed, 176 insertions(+), 78 deletions(-) diff --git a/Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md b/Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md index e1b417ce4..3d63c19c2 100644 --- a/Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md +++ b/Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md @@ -256,6 +256,41 @@ cent — its measured run-to-run spread on mean time is up to 51.8%. A move of 8 outside that band by a wide margin and agrees in sign and rough size with the allocation column beside it. The small rows from the same run are still not worth reading, and are not quoted. +## The 2072nd: the fixed-point series on the BCL's integers + +The 2054th and 2055th put the logarithm, the exponential, the sine, the cosine and the +arctangent into fixed point, on PeterO's `EInteger`. Measured against mpmath on the same +machine, the transcendental rows were still nine to seventeen times behind, and the reason is +the integer type: one multiply of two hundred-digit integers is 1,271 ns in `EInteger` and +109 ns in `System.Numerics.BigInteger` (at five hundred digits 8,087 against 1,364; at two +thousand 75,850 against 12,036), and the BCL's is at parity with CPython's `int`, which is +what mpmath's digits live in. The series run on `BigInteger` now, converted once on the way +in and once on the way out as two's-complement bytes, and the way out no longer multiplies by +`5^bits` and asks `EDecimal` to round a number of four times the digits: the fixed-point +integer is scaled to a power of ten six digits past the precision in `BigInteger`, and only +the rounding is `EDecimal`'s. The hundred-digit values are the ones pinned before, to the +last digit. + +| benchmark | 2055th | 2072nd | allocation | time | +|---|--:|--:|--:|--:| +| `EvalTrig` | 260,728 | **164,728** | **−36.8%** | 153 → **83 µs** | +| `EvalTrigPrecise` | 2,525,707 | **718,778** | **−71.5%** | 2.16 → **1.05 ms** | +| `EvalTranscendentalFresh` | 128,664 | **59,136** | **−54.0%** | 83 → **33 µs** | +| `SolveEasy` | 1,675,487 | **981,996** | **−41.4%** | 1.19 → **0.49 ms** | +| `SimplifyHard` | 189,604,392 | **182,277,112** | **−3.9%** | 111 → 117 ms | +| every other entry | | | within 0.1% | | + +Bytes allocated per call, same machine, both columns by the gate in one session; the gate's +baseline was taken from this run. Alone, by one probe on both builds, each row the best of +seven batches with tiered compilation off: at a hundred digits `sin(x)` is 15.2 µs where it +was 46.9, `ln(x)` 18.0 where it was 52.4, `e^x` 10.0 where it was 32.3, `arctan(x)` 24.6 +where it was 48.8; at five hundred digits `sin(x)` 228 µs where it was 1,273, `ln(x)` 777 +where it was 3,921, `e^x` 548 where it was 2,774; at two thousand digits `sin(x)` 11.7 ms +where it was 40.7 and `ln(x)` 26 where it was 121. mpmath at a hundred digits is 5.6, 4.5, +4.3 and 3.9 µs for the same four, so the remaining factor of three to six is the term count +-- eighty artanh terms for a logarithm, twenty-five for a sine -- which argument reduction +past what is done now would cut. + ## The 2055th: the sine, cosine and arctangent in the same fixed point The 2050th made the trigonometric functions argument reduction and short series, in diff --git a/Sources/AngouriMath/Functions/InternalAMExtensions.cs b/Sources/AngouriMath/Functions/InternalAMExtensions.cs index a2716adbd..d6d5c6877 100644 --- a/Sources/AngouriMath/Functions/InternalAMExtensions.cs +++ b/Sources/AngouriMath/Functions/InternalAMExtensions.cs @@ -202,36 +202,44 @@ public static ConstantCache Lookup(EContext context) // context's digits in bits, and forty-eight bits over for the series' own // truncations and the exponential's ten squarings. FixedBits = (int)(context.Precision.ToInt32Checked() * 3.32192809488736 + 48); - FixedOne = EInteger.One.ShiftLeft(FixedBits); - FiveToFixedBits = EInteger.FromInt32(5).Pow(FixedBits); + FixedOne = BigInteger.One << FixedBits; FixedSqrt2 = ToFixed(EDecimal.FromString("1.41421356237309504880168872420969807856967187537694"), FixedBits); FixedPi = ToFixed(Pi, FixedBits); - FixedTwoPi = FixedPi.ShiftLeft(1); - FixedHalfPi = FixedPi.ShiftRight(1); + FixedTwoPi = FixedPi << 1; + FixedHalfPi = FixedPi >> 1; // ln 2 = 2 artanh(1/3), and ln 10 = ln 8 + ln 5/4 = 3 ln 2 + 2 artanh(1/9). - FixedLn2 = TwiceArtanh(FixedOne.Divide(3), FixedBits); - FixedLn10 = FixedLn2.Multiply(3).Add(TwiceArtanh(FixedOne.Divide(9), FixedBits)); + FixedLn2 = TwiceArtanh(FixedOne / 3, FixedBits); + FixedLn10 = FixedLn2 * 3 + TwiceArtanh(FixedOne / 9, FixedBits); } /// Represents public EDecimal Pi { get; } /// The bits after the point of the fixed-point numbers below public int FixedBits { get; } /// One, in fixed point: 2 to the - public EInteger FixedOne { get; } - /// 5 to the , by which a fixed-point number is a decimal exactly - public EInteger FiveToFixedBits { get; } + public BigInteger FixedOne { get; } + /// + /// 10 to , kept once each: a fixed-point number is read + /// back as a decimal by one of a few powers near the context's digits. + /// + public BigInteger TenTo(int power) + { + if (!powersOfTen.TryGetValue(power, out var result)) + powersOfTen[power] = result = BigInteger.Pow(BigTen, power); + return result; + } + private readonly Dictionary powersOfTen = new(); /// The square root of 2, in fixed point, to fifty digits - public EInteger FixedSqrt2 { get; } + public BigInteger FixedSqrt2 { get; } /// Pi in fixed point, to the context's digits - public EInteger FixedPi { get; } + public BigInteger FixedPi { get; } /// 2 pi in fixed point - public EInteger FixedTwoPi { get; } + public BigInteger FixedTwoPi { get; } /// Pi / 2 in fixed point - public EInteger FixedHalfPi { get; } + public BigInteger FixedHalfPi { get; } /// The natural logarithm of 2, in fixed point - public EInteger FixedLn2 { get; } + public BigInteger FixedLn2 { get; } /// The natural logarithm of 10, in fixed point - public EInteger FixedLn10 { get; } + public BigInteger FixedLn10 { get; } /// Represents 2 * public EDecimal TwoPi { get; } /// Represents / 2 @@ -422,33 +430,33 @@ internal static (EDecimal Sin, EDecimal Cos) SinAndCos(EDecimal x, EContext cont // and a halving is a shift. var fixedX = ToFixed(x, bits); // Into [-pi, pi]: the nearest multiple of 2 pi off. - var shifted = fixedX.Add(consts.FixedPi); - var turns = shifted.Divide(consts.FixedTwoPi); - if (shifted.Sign < 0 && !shifted.Remainder(consts.FixedTwoPi).IsZero) - turns = turns.Subtract(1); // the division truncates, and this floors + var shifted = fixedX + consts.FixedPi; + var turns = BigInteger.DivRem(shifted, consts.FixedTwoPi, out var rest); + if (shifted.Sign < 0 && !rest.IsZero) + turns -= 1; // the division truncates, and this floors if (!turns.IsZero) - fixedX = fixedX.Subtract(consts.FixedTwoPi.Multiply(turns)); + fixedX -= consts.FixedTwoPi * turns; // Into [-pi/2, pi/2], with the sign the fold costs the cosine. var negateCos = false; - if (fixedX.CompareTo(consts.FixedHalfPi) > 0) + if (fixedX > consts.FixedHalfPi) { // sin(x) = sin(pi - x), cos(x) = -cos(pi - x) - fixedX = consts.FixedPi.Subtract(fixedX); + fixedX = consts.FixedPi - fixedX; negateCos = true; } - else if (fixedX.CompareTo(consts.FixedHalfPi.Negate()) < 0) + else if (fixedX < -consts.FixedHalfPi) { // sin(x) = sin(-pi - x), cos(x) = -cos(-pi - x) - fixedX = consts.FixedPi.Negate().Subtract(fixedX); + fixedX = -consts.FixedPi - fixedX; negateCos = true; } // Halve until |x| < 1/20. var halvings = 0; - var twentieth = one.Divide(20); - while (fixedX.Abs().CompareTo(twentieth) > 0) + var twentieth = one / 20; + while (BigInteger.Abs(fixedX) > twentieth) { - fixedX = fixedX.Sign < 0 ? fixedX.Negate().ShiftRight(1).Negate() : fixedX.ShiftRight(1); + fixedX = HalveFixed(fixedX); halvings++; } @@ -460,22 +468,22 @@ internal static (EDecimal Sin, EDecimal Cos) SinAndCos(EDecimal x, EContext cont var cos = one; for (var i = 1; i < 100000; i++) { - cosTerm = MultiplyFixed(cosTerm, square, bits).Negate().Divide((2 * i - 1) * (2 * i)); - sinTerm = MultiplyFixed(sinTerm, square, bits).Negate().Divide((2 * i) * (2 * i + 1)); + cosTerm = -MultiplyFixed(cosTerm, square, bits) / ((2 * i - 1) * (2 * i)); + sinTerm = -MultiplyFixed(sinTerm, square, bits) / ((2 * i) * (2 * i + 1)); if (cosTerm.IsZero && sinTerm.IsZero) break; - cos = cos.Add(cosTerm); - sin = sin.Add(sinTerm); + cos += cosTerm; + sin += sinTerm; } for (var i = 0; i < halvings; i++) { // sin(2y) = 2 sin(y) cos(y), cos(2y) = 2 cos(y)^2 - 1 - var doubledSin = MultiplyFixed(sin, cos, bits).ShiftLeft(1); - cos = MultiplyFixed(cos, cos, bits).ShiftLeft(1).Subtract(one); + var doubledSin = MultiplyFixed(sin, cos, bits) << 1; + cos = (MultiplyFixed(cos, cos, bits) << 1) - one; sin = doubledSin; } if (negateCos) - cos = cos.Negate(); + cos = -cos; return (FromFixed(sin, consts, working).RoundToPrecision(context), FromFixed(cos, consts, working).RoundToPrecision(context)); } @@ -599,11 +607,11 @@ public static EDecimal Arctan(this EDecimal x, EContext context) var one = workingConsts.FixedOne; var fixedX = ToFixed(x, bits); var halvings = 0; - var twentieth = one.Divide(20); - while (fixedX.CompareTo(twentieth) > 0) + var twentieth = one / 20; + while (fixedX > twentieth) { - var root = one.Add(MultiplyFixed(fixedX, fixedX, bits)).ShiftLeft(bits).Sqrt(); - fixedX = fixedX.ShiftLeft(bits).Divide(one.Add(root)); + var root = IntegerSquareRoot((one + MultiplyFixed(fixedX, fixedX, bits)) << bits); + fixedX = (fixedX << bits) / (one + root); halvings++; } var square = MultiplyFixed(fixedX, fixedX, bits); @@ -611,13 +619,13 @@ public static EDecimal Arctan(this EDecimal x, EContext context) var sum = fixedX; for (var i = 1; i < 100000; i++) { - power = MultiplyFixed(power, square, bits).Negate(); - var term = power.Divide(2 * i + 1); + power = -MultiplyFixed(power, square, bits); + var term = power / (2 * i + 1); if (term.IsZero) break; - sum = sum.Add(term); + sum += term; } - return FromFixed(sum.ShiftLeft(halvings), workingConsts, working).RoundToPrecision(context); + return FromFixed(sum << halvings, workingConsts, working).RoundToPrecision(context); } /// Analogy of public static EDecimal Acos(this EDecimal x, EContext context) @@ -674,34 +682,90 @@ public static EDecimal Arctan2(this EDecimal y, EDecimal x, EContext context) // each term an alignment of exponents, an exact sum and a rounding -- two to three // microseconds a term at a hundred digits, which is why PeterO's Log and Exp are // three hundred to seven hundred microseconds. The series here run in fixed point: - // an EInteger holding the value times 2^FixedBits, where a product is a big-integer + // an integer holding the value times 2^FixedBits, where a product is a big-integer // multiply and a shift, a division by a term's index an integer division, and a sum - // an addition -- a third of a microsecond a term -- and the value is a decimal - // again exactly, by multiplying with 5^FixedBits and moving the point. + // an addition -- a tenth of a microsecond a term -- and the value is a decimal again + // by scaling to a power of ten a few digits past the precision and rounding. // https://github.com/asc-community/AngouriMath/issues/1338 + // The fixed-point integers are System.Numerics.BigInteger, not EInteger: a multiply + // of two hundred-digit integers is 109 ns there and 1,271 ns in PeterO's, a division + // 193 against 953, and the series are made of those -- sin(0.37) at a hundred digits + // was 44 µs on EInteger where mpmath, on CPython's integers, which are as fast as the + // BCL's, is 5.7. The decimal is converted once on the way in and once on the way out, + // as two's-complement bytes, which both types read and write. + // https://github.com/asc-community/AngouriMath/issues/1338 + + /// The integer as the BCL's, by its two's-complement bytes. + private static BigInteger ToBig(EInteger n) => new(n.ToBytes(littleEndian: true)); + + /// The integer as PeterO's, by its two's-complement bytes. + private static EInteger ToEInteger(BigInteger n) => EInteger.FromBytes(n.ToByteArray(), littleEndian: true); + + [ConstantField] private static readonly BigInteger BigTen = new(10); + /// times 2^, truncated to an integer. - private static EInteger ToFixed(EDecimal x, int bits) + private static BigInteger ToFixed(EDecimal x, int bits) { var exponent = x.Exponent.ToInt32Checked(); - var mantissa = x.Mantissa; + var mantissa = ToBig(x.Mantissa); return exponent >= 0 - ? mantissa.Multiply(EInteger.FromInt32(10).Pow(exponent)).ShiftLeft(bits) - : mantissa.ShiftLeft(bits).Divide(EInteger.FromInt32(10).Pow(-exponent)); + ? (mantissa * BigInteger.Pow(BigTen, exponent)) << bits + : (mantissa << bits) / BigInteger.Pow(BigTen, -exponent); } - /// A fixed-point as a decimal, exactly, rounded to . - private static EDecimal FromFixed(EInteger n, ConstantCache consts, EContext working) - => EDecimal.Create(n.Multiply(consts.FiveToFixedBits), -consts.FixedBits).RoundToPrecision(working); + /// + /// A fixed-point as a decimal to 's + /// digits. The scaling to a power of ten is done in , with + /// six digits over the precision, and only the rounding is left to + /// : the exact reading, n 5^bits over + /// 10^bits, costs a multiply and a rounding of four times the digits. + /// https://github.com/asc-community/AngouriMath/issues/1338 + /// + private static EDecimal FromFixed(BigInteger n, ConstantCache consts, EContext working) + { + var bits = consts.FixedBits; + // The value is n / 2^bits, so its leading digit is near 10^(log10(2) (bitlen n - bits)). + // The byte length overestimates the bit length by up to seven, which only asks for + // up to two digits more than the six. + var magnitude = (int)Math.Floor((n.ToByteArray().Length * 8 - bits) * 0.30102999566398120); + var digits = working.Precision.ToInt32Checked() + 6 - magnitude; + var mantissa = digits >= 0 + ? (n * consts.TenTo(digits)) >> bits + : (n >> bits) / consts.TenTo(-digits); + return EDecimal.Create(ToEInteger(mantissa), -digits).RoundToPrecision(working); + } /// /// The product of two fixed-point numbers, truncated towards zero -- a shift alone /// floors, and a negative term of a series floored never reaches zero. /// - private static EInteger MultiplyFixed(EInteger a, EInteger b, int bits) + private static BigInteger MultiplyFixed(BigInteger a, BigInteger b, int bits) { - var product = a.Multiply(b); - return product.Sign < 0 ? product.Negate().ShiftRight(bits).Negate() : product.ShiftRight(bits); + var product = a * b; + return product.Sign < 0 ? -((-product) >> bits) : product >> bits; + } + + /// Half of a fixed-point number, truncated towards zero. + private static BigInteger HalveFixed(BigInteger a) + => a.Sign < 0 ? -((-a) >> 1) : a >> 1; + + /// The integer square root, floored: Newton's iteration from a power of two above it. + private static BigInteger IntegerSquareRoot(BigInteger n) + { + if (n.Sign <= 0) + return BigInteger.Zero; + // From a power of two at or above the root, so that the iteration descends: + // the byte count is a bound on the bit length that netstandard2.0's BigInteger + // can give without a logarithm. + var root = BigInteger.One << (n.ToByteArray().Length * 4 + 1); + while (true) + { + var next = (root + n / root) >> 1; + if (next >= root) + return root; + root = next; + } } /// @@ -709,7 +773,7 @@ private static EInteger MultiplyFixed(EInteger a, EInteger b, int bits) /// ln((1 + y)/(1 - y)), in fixed point; for |y| below 0.18, /// seventy terms at a hundred digits. /// - private static EInteger TwiceArtanh(EInteger y, int bits) + private static BigInteger TwiceArtanh(BigInteger y, int bits) { var square = MultiplyFixed(y, y, bits); var term = y; @@ -719,9 +783,9 @@ private static EInteger TwiceArtanh(EInteger y, int bits) term = MultiplyFixed(term, square, bits); if (term.IsZero) break; - sum = sum.Add(term.Divide(2 * k + 1)); + sum += term / (2 * k + 1); } - return sum.ShiftLeft(1); + return sum << 1; } [ConstantField] private static readonly EDecimal sqrt2 = EDecimal.FromString("1.4142135623730950488"); @@ -746,7 +810,7 @@ public static EDecimal NaturalLogarithm(this EDecimal x, EContext context) var working = WithGuardDigits(context, 8); var consts = ConstantCache.Lookup(working); var bits = consts.FixedBits; - EInteger mantissa; + BigInteger mantissa; var tens = 0; var twos = 0; if (x.CompareTo(sqrt2) <= 0 && x.CompareTo(halfSqrt2) >= 0) @@ -757,18 +821,18 @@ public static EDecimal NaturalLogarithm(this EDecimal x, EContext context) // then halved, exactly, until it is at most sqrt(2). tens = x.Exponent.Add(x.Precision()).Subtract(1).ToInt32Checked(); mantissa = ToFixed(x.MovePointLeft(tens), bits); - while (mantissa.CompareTo(consts.FixedSqrt2) > 0) + while (mantissa > consts.FixedSqrt2) { - mantissa = mantissa.ShiftRight(1); + mantissa >>= 1; twos++; } } - var y = mantissa.Subtract(consts.FixedOne).ShiftLeft(bits).Divide(mantissa.Add(consts.FixedOne)); + var y = ((mantissa - consts.FixedOne) << bits) / (mantissa + consts.FixedOne); var log = TwiceArtanh(y, bits); if (twos != 0) - log = log.Add(consts.FixedLn2.Multiply(twos)); + log += consts.FixedLn2 * twos; if (tens != 0) - log = log.Add(consts.FixedLn10.Multiply(tens)); + log += consts.FixedLn10 * tens; return FromFixed(log, consts, working).RoundToPrecision(context); } @@ -794,35 +858,34 @@ public static EDecimal Exponential(this EDecimal x, EContext context) var consts = ConstantCache.Lookup(working); var bits = consts.FixedBits; var fixedX = ToFixed(x, bits); - var k = fixedX.Divide(consts.FixedLn2); - var r = fixedX.Subtract(consts.FixedLn2.Multiply(k)); - var halfLn2 = consts.FixedLn2.ShiftRight(1); - if (r.CompareTo(halfLn2) > 0) + var k = BigInteger.DivRem(fixedX, consts.FixedLn2, out var r); + var halfLn2 = consts.FixedLn2 >> 1; + if (r > halfLn2) { - k = k.Add(1); - r = r.Subtract(consts.FixedLn2); + k += 1; + r -= consts.FixedLn2; } - else if (r.CompareTo(halfLn2.Negate()) < 0) + else if (r < -halfLn2) { - k = k.Subtract(1); - r = r.Add(consts.FixedLn2); + k -= 1; + r += consts.FixedLn2; } const int halvings = 10; - r = r.Sign < 0 ? r.Negate().ShiftRight(halvings).Negate() : r.ShiftRight(halvings); + r = r.Sign < 0 ? -((-r) >> halvings) : r >> halvings; var term = consts.FixedOne; var sum = consts.FixedOne; for (var n = 1; n < 100000; n++) { - term = MultiplyFixed(term, r, bits).Divide(n); + term = MultiplyFixed(term, r, bits) / n; if (term.IsZero) break; - sum = sum.Add(term); + sum += term; } for (var i = 0; i < halvings; i++) sum = MultiplyFixed(sum, sum, bits); - var power = k.ToInt32Checked(); + var power = (int)k; if (power >= 0) - return FromFixed(sum.ShiftLeft(power), consts, working).RoundToPrecision(context); + return FromFixed(sum << power, consts, working).RoundToPrecision(context); return FromFixed(sum, consts, working) .Divide(EDecimal.FromEInteger(EInteger.One.ShiftLeft(-power)), working) .RoundToPrecision(context); From db051d9589291a1ae263d2f989c03e2d4b3ac122 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 15 Sep 2026 23:08:38 +0000 Subject: [PATCH 2/2] The gate's baseline is the run of the previous commit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../DotnetBenchmark/performance-baseline.json | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Sources/Tests/DotnetBenchmark/performance-baseline.json b/Sources/Tests/DotnetBenchmark/performance-baseline.json index 978b86796..493b110be 100644 --- a/Sources/Tests/DotnetBenchmark/performance-baseline.json +++ b/Sources/Tests/DotnetBenchmark/performance-baseline.json @@ -1,94 +1,94 @@ { "comment": "Allocated bytes and mean nanoseconds per operation for the popular use cases of https://github.com/asc-community/AngouriMath/issues/746. Checked by PerformanceGate.cs; see Sources/AngouriMath/Docs/WhatsNew/version_performance_control.md for when updating it is legitimate.", "benchmark": "CommonFunctionsInterVersion", - "commit": "95082e2f814874b99f152fbb1072fc977735337f", + "commit": "a0e37c57f2bff5b7b489045a829b93094be44980", "measuredOn": "2026-09-15", "runtime": ".NET 10.0.10", "machine": "Ubuntu 26.04 LTS, X64, 8 logical cores", "cases": { "CompileEasy": { "allocatedBytes": 11031, - "meanNanoseconds": 184131.37203543526 + "meanNanoseconds": 187188.48854166668 }, "CompileHard": { "allocatedBytes": 20434, - "meanNanoseconds": 310381.04733072914 + "meanNanoseconds": 313336.8848632813 }, "Derivate": { "allocatedBytes": 53111, - "meanNanoseconds": 14270.049594116212 + "meanNanoseconds": 14254.605079142253 }, "EvalEasy": { "allocatedBytes": 0, - "meanNanoseconds": 1.883551713079214 + "meanNanoseconds": 1.8936276791187434 }, "EvalPolynomialFresh": { "allocatedBytes": 12328, - "meanNanoseconds": 4164.772733835073 + "meanNanoseconds": 4188.053805033366 }, "EvalPolynomialFresh15Digits": { "allocatedBytes": 11656, - "meanNanoseconds": 4157.980944497244 + "meanNanoseconds": 4163.698484693255 }, "EvalTranscendentalFresh": { - "allocatedBytes": 128664, - "meanNanoseconds": 83463.06102643695 + "allocatedBytes": 59136, + "meanNanoseconds": 32775.32282802037 }, "EvalTrig": { - "allocatedBytes": 260728, - "meanNanoseconds": 153426.1548339844 + "allocatedBytes": 164728, + "meanNanoseconds": 82560.51665387835 }, "EvalTrigPrecise": { - "allocatedBytes": 2525707, - "meanNanoseconds": 2164306.404296875 + "allocatedBytes": 718778, + "meanNanoseconds": 1049946.529575893 }, "ParseEasy": { - "allocatedBytes": 18104, - "meanNanoseconds": 5932.770784231333 + "allocatedBytes": 18125, + "meanNanoseconds": 6187.999439038728 }, "ParseHard": { - "allocatedBytes": 3555057, - "meanNanoseconds": 1392708.7341145833 + "allocatedBytes": 3555058, + "meanNanoseconds": 1389899.094029018 }, "RunEasy": { "allocatedBytes": 0, - "meanNanoseconds": 20.095689982175827 + "meanNanoseconds": 20.08104038039843 }, "RunHard": { "allocatedBytes": 0, - "meanNanoseconds": 295.40826177597046 + "meanNanoseconds": 296.0797004699707 }, "RunMedium": { "allocatedBytes": 0, - "meanNanoseconds": 162.8344373519604 + "meanNanoseconds": 163.28495715214655 }, "SimplifyEasy": { "allocatedBytes": 78578, - "meanNanoseconds": 44253.979728190105 + "meanNanoseconds": 45234.654259314906 }, "SimplifyHard": { - "allocatedBytes": 189604392, - "meanNanoseconds": 111491250.2413793 + "allocatedBytes": 182277112, + "meanNanoseconds": 117284414.35714285 }, "SolveEasy": { - "allocatedBytes": 1675487, - "meanNanoseconds": 1185599.8670479911 + "allocatedBytes": 981996, + "meanNanoseconds": 489748.97991071426 }, "SolveEasyMedium": { "allocatedBytes": 65184, - "meanNanoseconds": 18840.902629307337 + "meanNanoseconds": 18883.072003173827 }, "SolveHard": { - "allocatedBytes": 11706656, - "meanNanoseconds": 63240162.666666664 + "allocatedBytes": 11706992, + "meanNanoseconds": 64872788.86363637 }, "SolveMedium": { - "allocatedBytes": 446414, - "meanNanoseconds": 259625.15890066963 + "allocatedBytes": 446445, + "meanNanoseconds": 265146.4944786659 }, "SolveMediumHard": { - "allocatedBytes": 1367798, - "meanNanoseconds": 8776522.877083333 + "allocatedBytes": 1367804, + "meanNanoseconds": 8406241.805208333 } }, "ungated": {