Skip to content

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 - #1364

Merged
Rafael-SOWNet merged 2 commits into
masterfrom
bigint-fixed-point
Sep 15, 2026
Merged

Rafael-SOWNet merged 2 commits into
masterfrom
bigint-fixed-point

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

#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 (#1338, the table), the transcendental rows were still 9–17× 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 vs 1,364 at 500 digits, 75,850 vs 12,036 at 2,000), 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 (EInteger.ToBytes(littleEndian: true) / EInteger.FromBytes), and the way out no longer multiplies by 5^bits and asks EDecimal to round a number of four times the digits: FromFixed scales the fixed-point integer to a power of ten six digits past the precision in BigInteger (the powers cached per context) and leaves only the rounding to EDecimal. That reading alone was 5 of the 25 µs at 100 digits and 65 of 228 at 500. The decimal layer above is untouched; no public API changes.

The hundred-digit values are the ones pinned before, to the last digit (NumericDigits, HighPrecisionFunctionsTest) — no BREAKING-CHANGES entry.

benchmark before after allocation time
EvalTrig 260,728 164,728 −36.8% 153 → 83 µs
EvalTrigPrecise (500 digits) 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%
every other entry within 0.1%

Alone, one probe on both builds (best of seven batches, tiered compilation off, a fresh node per call since an entity caches its Evaled):

100 digits before after mpmath
sin(x) 46.9 µs 15.2 5.6
ln(x) 52.4 18.0 4.5
e^x 32.3 10.0 4.3
arctan(x) 48.8 24.6 3.9

At 500 digits sin 1,273 → 228 µs, ln 3,921 → 777, e^x 2,774 → 548; at 2,000 digits sin 40.7 → 11.7 ms, ln 121 → 26. The remaining factor of 3–6 to mpmath is the term count (eighty artanh terms for a logarithm, twenty-five for a sine), which is the argument-reduction step next.

Baseline from this run; the performance log has the entry. Four suites green (11,345 / 134 / 18 / 41).

Part of #1338.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

Rafael-SOWNet and others added 2 commits September 15, 2026 23:08
…-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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet
Rafael-SOWNet merged commit f57796a into master Sep 15, 2026
31 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the bigint-fixed-point branch September 15, 2026 23:27
Rafael-SOWNet added a commit that referenced this pull request Sep 16, 2026
… before the exact search, and reads a value of up to four thousand digits (#1365)

* The rational pre-check confirms a double's maybe in the double-double before the exact search, and reads a value of up to four thousand digits

EvalTrig is sin 1 + cos 1 + tan 1, and after #1364 fifty of its
eighty-three microseconds were Real.Create deciding that sin 1 is not
a small rational, while cos 1 was decided in a fifth of one. The
cheap decision of #1342 runs the continued fraction in a double first
and answers "may be" where a remainder is within the double's own
error of the next integer; for sin 1 that is the twelfth level, where
the error has grown to a hundredth, and the answer went straight to
the exact hundred-digit search instead of to the double-double, whose
error there is ten to the minus twelve and which refuses in under a
microsecond. A double's yes is confirmed in the double-double now,
always. The reading of a value into the double-double scaled by two
halves of its binary shift, of which half of a thousand-digit value's
is 2^1608, an infinity: every Real.Create past about six hundred and
fifty digits went to the exact search, 5.7 ms each at two thousand.
The scaling is in as many pieces as keep each factor in range, up to
four thousand digits, past which the error the search assumes would
no longer hold. Nothing a value downcasts to changes; the
twelve-thousand-rational agreement test and a two-thousand-digit one
say so.

    EvalTrig                    83 us /   164,728 B  ->   37 us /    82,104 B
    EvalTrigPrecise           1.05 ms /   718,778 B  -> 0.62 ms /   525,289 B
    every other entry         0.0%

Alone, Real.Create of sin 1's hundred digits is 0.5 us where it was
49, and of two thousand digits 0.3 us where it was 5,660. 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

* The gate's baseline is the run of the previous commit

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rafael-SOWNet added a commit that referenced this pull request Sep 16, 2026
…ths, the cosine is the sine's root at the reduced argument, and a square root is the integer one with a sticky digit (#1366)

* The logarithm and the arctangent are reduced by a table of sixty-fourths, the cosine is the sine's root at the reduced argument, and a square root is the integer one with a sticky digit

After #1364 the remaining factor to mpmath was the term count: eighty
artanh terms for a logarithm at a hundred digits, forty-seven for an
arctangent after three square-root halvings, and two series -- the
sine's and the cosine's -- for either. The logarithm's mantissa,
already within [1/sqrt 2, sqrt 2], is divided by the nearest 1 + j/64,
whose logarithm the constant cache holds (built outward from ln 1 = 0
when first asked, one short series a step), so the series runs on a
quotient within 1/128 of one: twenty-five terms. The arctangent's
argument, within [0, 1], is brought within 1/128 of zero by the
nearest j/64 the same way, arctan x = arctan c + arctan((x - c)/(1 +
xc)): thirty terms and no square root. The cosine at the sine's
reduced argument, which is within a twentieth of zero, is
sqrt(1 - sin^2) and cancels nothing; the other way round is what lost
half the digits in #1342. And the square root of a decimal is the
integer square root of its mantissa padded to twice the digits, with a
sticky digit so that the rounding to the context is the correct one
in every mode, as PeterO's is -- Newton from the root of the top half
of the bits, three divisions at the full width where a power of two
above the root took one per bit of the exponent. The hundred-digit
values are the ones pinned before, to the last digit.

    EvalTranscendentalFresh     33 us /    59,136 B  ->   16 us /    34,608 B
    EvalTrig                    37 us /    82,104 B  ->   32 us /    69,792 B
    EvalTrigPrecise            617 us /   525,289 B  ->  406 us /   336,360 B
    SolveEasy                  491 us /   981,996 B  ->  390 us /   783,622 B
    SolveHard                          11,708,080 B  ->          10,053,640 B
    every other entry         within 1.3%

Alone, one probe on both builds: at a hundred digits ln is 8.4 us
where it was 18.0, arctan 7.7 where it was 24.6, sin 13.1 where it
was 15.2; at five hundred ln 241 us where it was 777, sin 141 where
it was 228, and the square root 22 where PeterO's is 41; at two
thousand digits the square root 174 us where PeterO's is 441. 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

* The gate's baseline is the run of the previous commit

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant