Repository navigation
PrecisionReal: keep a real-valued precision, as WMA does (#2047) - #2050
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real.precision is Optional[int] (inherited from Number); the helper now takes MachineReal or PrecisionReal, whose precision is always an int. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
|
Personally, I think this is really excellent. Very well thought out and executed. It is exacting, and this level of detail distinguishes us from virtually all of the other Mathematica lookalikes. And it also removes some code duplication. This is a great improvement over the kind of code I experienced when I first encountered this project. Thanks @tombouctou! However, @mmatera, you had concerns. So I'm waiting to hear your thoughts. |
Member
|
LGTM |
tombouctou
added a commit
that referenced
this pull request
Oct 19, 2026
Part of step 6 of #2047 (MATH-227). - N[expr, p] gives each arbitrary-precision number in the result precision p, as WMA does, also for a non-integer p: Precision[N[Pi, 20.3]] is 20.3 (was 20.). - When the computation loses digits, it is done again with more digits, up to $MaxExtraPrecision more: Precision[N[Pi E, 20]] is 20. again (it became 19.699 with #2050), and Precision[N[Pi - 314159/100000, 20]] is 20. (was 13.6). A number whose input has fewer digits keeps them. - New $MaxExtraPrecision (50. by default) and N::meprec, as in WMA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
tombouctou
added a commit
that referenced
this pull request
Oct 19, 2026
Part of step 6 of #2047 (MATH-227), on top of the hyperbolic PR. - SetPrecision[expr, p] sets every number in expr to precision p, and evaluates numeric constants such as Pi to it. An inexact number keeps its binary value: SetPrecision[0.1, 20] is 0.10000000000000000555. As in WMA, an exact exponent stays exact (SetPrecision[Sqrt[2], 20] has precision 20.301), and a machine or arbitrary-precision zero becomes 0. p can be MachinePrecision, or Infinity for the exact binary values. - SetAccuracy[expr, a] does the same with accuracy a; a number with no digit left becomes a zero of accuracy a. - Complex numbers: both parts get the same accuracy, so that the number has precision (or accuracy) p, as in WMA. - Messages SetPrecision::precbd, ::precsm, ::preclg and SetAccuracy::accbd, as in WMA. - Complex: the object cache was keyed on the parts' values and whole-bit precision, so two numbers differing only in a real-valued precision (#2050) were the same object. The key now has the parts' precisions. - number._get_float_inf becomes get_float_inf, for SetPrecision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rocky
pushed a commit
that referenced
this pull request
Oct 19, 2026
…MA (#2051) Part of step 6 of the outline in #2047, after #2050: `N[expr, p]` gives precision `p`, as WMA does. | Input | master | this PR | WMA 15 | |---|---|---|---| | `Precision[N[Pi, 20.3]]` | `20.` | `20.3` | `20.3` | | `Precision[N[Pi, 41/2]]` | `20.` | `20.5` | `20.5` | | `Precision[N[Pi E, 20]]` | `19.699` | `20.` | `20.` | | `Precision[N[Pi - 314159/100000, 20]]` | `13.6257` | `20.` | `20.` | | ``Precision[N[1.5`10, 20.3]]`` | `10.` | `10.` | `10.` | | ``Precision /@ N[{Pi, 1.5`10, 2.5`30.2}, 20.3]`` | `{20., 10., 20.}` | `{20.3, 10., 20.3}` | `{20.3, 10., 20.3}` | | `$MaxExtraPrecision` | `$MaxExtraPrecision` | `50.` | `50.` | `N[Pi E, 20]` giving `19.699` came in with #2050: `N` makes `Pi` and `E` 20-digit numbers, and `Times` now adds their errors. ## Changes - **`N[expr, p]`** (`eval_NValues_to_precision` in `mathics/eval/nevaluator.py`) first evaluates at `p`, as before. If a nonzero arbitrary-precision number in the result has fewer than `p` digits, it evaluates again with the digits lost added, up to `$MaxExtraPrecision` more than `p`. It goes on while some number short of `p` digits gains digits; one that gains nothing has an input with fewer than `p` digits, and keeps them. Then every number above `p` is set to precision `p`. - The N rules see the working precision. So a retry is used only if it has the same form as the first result, up to the values of numbers: with `N[a, 20] = 11`, `N[a Pi, 20]` must not turn back into `a 3.14…` at 21 digits. The first pass is still at exactly `p`, so a rule given for `p` applies as before. - **`$MaxExtraPrecision`**, new, `50.` by default, no attributes. Assigning it anything other than a non-negative number or `Infinity` gives `$MaxExtraPrecision::precset`, as in WMA. - **`N::meprec`** when the extra digits are not enough. As in WMA, there is no message when `$MaxExtraPrecision` is `0`. `N[expr, {p, a}]` and the internal `eval_N` are unchanged. ## Tests - `test_n_real_valued_precision` and `test_max_extra_precision` in `test/builtin/atomic/test_numbers.py`, with WMA 15 values. - Doctests for `$MaxExtraPrecision`. - The `RootSum` doctest `N[Integrate[1/(x^5 + 11 x + 1), {x, 1, 3}], 50]` expected `…857550894`. On master that number has precision 49.66, so its last digits were off. Now it is `…857553407`, as in WMA (`…8575534094…`; `NIntegrate` at 70 digits agrees). Full run on kimmy (Python 3, `pytest -n 4` and `docpipeline.py`), next to master 9f166b1: - Doctests: 4611 passed, 0 failed (master: 4604; the 7 new ones are for `$MaxExtraPrecision`). - pytest: 34 failed on this branch, 58 on master. As noted in #2050, these depend on the order the tests run in. The 12 that fail only on this branch (`test_tensor.py`, `test_testing_expressions.py`) pass when their files run on their own. ## Benchmark kimmy, best of 7, both runs at the same time: | | master | this PR | |---|---|---| | `Total[Table[N[1/k, 50], {k, 2000}]]` | 2636 ms | 2586 ms | | `Times @@ Table[N[1 + 1/k, 50], {k, 2000}]` | 3113 ms | 3104 ms | | `Do[x = N[k, 50] + Pi, {k, 2000}]` | 5512 ms | 5277 ms | | `Total[Table[1./k, {k, 2000}]]` (machine reals, control) | 1921 ms | 1850 ms | | `Do[N[Pi E + Sqrt[2], 30], {k, 300}]` | 1118 ms | 1643 ms | | `Do[N[Sin[k] + Cos[k], 30], {k, 300}]` | 787 ms | 935 ms | The benchmark from #2050 doesn't change. An `N[expr, p]` whose first pass loses digits now costs about one more evaluation (the last two rows): that is the second pass. ## Still to do from step 6 - Functions other than `Plus` and `Times` (`Sqrt`, `Power`, `Exp`, …): precision from the condition number. - `SetPrecision`, `SetAccuracy`. - Low-precision inputs stored with few bits (``Precision[1.5`20 - 1.4`2]``). - `Precision` of a list with a machine number. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
tombouctou
added a commit
that referenced
this pull request
Oct 19, 2026
Part of step 6 of #2047 (MATH-227), on top of the hyperbolic PR. - SetPrecision[expr, p] sets every number in expr to precision p, and evaluates numeric constants such as Pi to it. An inexact number keeps its binary value: SetPrecision[0.1, 20] is 0.10000000000000000555. As in WMA, an exact exponent stays exact (SetPrecision[Sqrt[2], 20] has precision 20.301), and a machine or arbitrary-precision zero becomes 0. p can be MachinePrecision, or Infinity for the exact binary values. - SetAccuracy[expr, a] does the same with accuracy a; a number with no digit left becomes a zero of accuracy a. - Complex numbers: both parts get the same accuracy, so that the number has precision (or accuracy) p, as in WMA. - Messages SetPrecision::precbd, ::precsm, ::preclg and SetAccuracy::accbd, as in WMA. - Complex: the object cache was keyed on the parts' values and whole-bit precision, so two numbers differing only in a real-valued precision (#2050) were the same object. The key now has the parts' precisions. - number._get_float_inf becomes get_float_inf, for SetPrecision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tombouctou
added a commit
that referenced
this pull request
Oct 19, 2026
Part of step 6 of #2047 (MATH-227), on top of the hyperbolic PR. - SetPrecision[expr, p] sets every number in expr to precision p, and evaluates numeric constants such as Pi to it. An inexact number keeps its binary value: SetPrecision[0.1, 20] is 0.10000000000000000555. As in WMA, an exact exponent stays exact (SetPrecision[Sqrt[2], 20] has precision 20.301), and a machine or arbitrary-precision zero becomes 0. p can be MachinePrecision, or Infinity for the exact binary values. - SetAccuracy[expr, a] does the same with accuracy a; a number with no digit left becomes a zero of accuracy a. - Complex numbers: both parts get the same accuracy, so that the number has precision (or accuracy) p, as in WMA. - Messages SetPrecision::precbd, ::precsm, ::preclg and SetAccuracy::accbd, as in WMA. - Complex: the object cache was keyed on the parts' values and whole-bit precision, so two numbers differing only in a real-valued precision (#2050) were the same object. The key now has the parts' precisions. - number._get_float_inf becomes get_float_inf, for SetPrecision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tombouctou
added a commit
that referenced
this pull request
Oct 19, 2026
A follow-up to #2050 (MATH-227). - Plus: a PrecisionReal zero adds its absolute error, 10^-accuracy, as any other term does. Precision[1.5`20 + 0``10] is 10.1761 (was 10.), and a sum of zeros is a zero with the combined accuracy. - Times: a product with PrecisionReal zeros is a zero whose accuracy is the sum of theirs, less Log10 of the other factors: Accuracy[1.5`20 * 0``10] is 9.82391 (was 15.). - Times: an exact 0 times arbitrary-precision numbers is an exact 0, as in WMA (it gave 0.`15.*^-15). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tombouctou
added a commit
that referenced
this pull request
Oct 19, 2026
Part of step 6 of #2047 (MATH-227), on top of the hyperbolic PR. - SetPrecision[expr, p] sets every number in expr to precision p, and evaluates numeric constants such as Pi to it. An inexact number keeps its binary value: SetPrecision[0.1, 20] is 0.10000000000000000555. As in WMA, an exact exponent stays exact (SetPrecision[Sqrt[2], 20] has precision 20.301), and a machine or arbitrary-precision zero becomes 0. p can be MachinePrecision, or Infinity for the exact binary values. - SetAccuracy[expr, a] does the same with accuracy a; a number with no digit left becomes a zero of accuracy a. - Complex numbers: both parts get the same accuracy, so that the number has precision (or accuracy) p, as in WMA. - Messages SetPrecision::precbd, ::precsm, ::preclg and SetAccuracy::accbd, as in WMA. - Complex: the object cache was keyed on the parts' values and whole-bit precision, so two numbers differing only in a real-valued precision (#2050) were the same object. The key now has the parts' precisions. - number._get_float_inf becomes get_float_inf, for SetPrecision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tombouctou
added a commit
that referenced
this pull request
Oct 19, 2026
Part of step 6 of #2047 (MATH-227), on top of the hyperbolic PR. - SetPrecision[expr, p] sets every number in expr to precision p, and evaluates numeric constants such as Pi to it. An inexact number keeps its binary value: SetPrecision[0.1, 20] is 0.10000000000000000555. As in WMA, an exact exponent stays exact (SetPrecision[Sqrt[2], 20] has precision 20.301), and a machine or arbitrary-precision zero becomes 0. p can be MachinePrecision, or Infinity for the exact binary values. - SetAccuracy[expr, a] does the same with accuracy a; a number with no digit left becomes a zero of accuracy a. - Complex numbers: both parts get the same accuracy, so that the number has precision (or accuracy) p, as in WMA. - Messages SetPrecision::precbd, ::precsm, ::preclg and SetAccuracy::accbd, as in WMA. - Complex: the object cache was keyed on the parts' values and whole-bit precision, so two numbers differing only in a real-valued precision (#2050) were the same object. The key now has the parts' precisions. - number._get_float_inf becomes get_float_inf, for SetPrecision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Steps 1–5 of the outline in #2047:
PrecisionRealkeeps a real-valued precision, as WMA does, andPlusandTimescarry it through.Precision[1.5`20.3]20.20.320.3Precision[1.5`5.497 + 1.5`5.497]5.5.4975.497Precision[1.5`5.497 * 1.5`5.497]5.5.195975.19597Precision[1.5`20.3 + 3.2`30]20.20.79620.796Precision[2 + 1.5`20.3]20.20.66820.668Precision[N[Pi, 30] N[E, 30]]30.29.69929.699Precision[N[Pi, 30] E]30.30.30.Precision[N[Pi, {20, 5}]]5.5.497155.49715Accuracy[1.5`5 - 1.5`5]15.4.522884.52288ToString[1.5`20.3, InputForm]1.5`20.1.5`20.31.5`20.33.14159265358979323846264`20.6Changes
PrecisionReal(mathics/core/atoms/numerics.py) has a new slot,_precision10. It isNoneunless the precision differs from what the binary precision of thesympy.Floatalready gives, so numbers without a fractional precision look the same as before.precision10returns the decimal precision; for a zero, as before, it is the accuracy. The object cache is keyed by(sympy.Float, precision10).__neg__,do_copyand pickling keep the value.precisionstill returns binary digits.x`pandxa`` keep the real-valued precision the parser already computed. The binary precision is rounded up from it.Precision,Accuracyreadprecision10.Plus,Times: when the numbers are exact or nonzeroPrecisionReals, the errors add up, as in WMA. ForPlusthe absolute errors add: the accuracy is-Log10[Total[10^-acc_i]]. ForTimesthe relative errors add: the precision is-Log10[Total[10^-prec_i]]. A sum that cancels down to no digits becomes a zero with that accuracy, as in WMA. The computation itself still runs in mpmath at a whole number of bits. Machine numbers, complex numbers and zero factors take the code path they took before.numerify(used byPlus,Times,Power) evaluates exact numeric elements likeEorSqrt[2]with 16 more digits than the inexact ones. Their error then vanishes next to the error of the inexact ones, soN[Pi, 30] Ekeeps precision30., as in WMA. Without this,Ewould count as one more 30-digit number and give29.699. Nothing changes when a machine number is involved.N[expr, {prec, acc}](N: evaluate with {precision, accuracy}; give a numeric precision to N rules as a machine real #2028) stores the precision it computes instead of rounding it down to whole bits.Equal: the tolerance ("the last 7 binary digits") is taken from the decimal precision, not from the binary digits it was rounded up to. Otherwise13.14164 == 13.14134(a doctest,Truein WMA) would turnFalse. For numbers with a whole-digit precision the tolerance is the same as before.InputFormshows the stored precision.eval_add_numbersandeval_multiply_numbershad three identical copies:mathics/eval/numeric.py,mathics/eval/arithmetic.pyandmathics/eval/arithfns/basic.py.PlusandTimesuse the one inbasic.py, whileAbsandSignuse the one innumeric.py. Now there is one, innumeric.py, which the other two modules import, so the new rule applies everywhere.Tests
test_real_valued_precisionintest/builtin/atomic/test_numbers.py, with the WMA 15 values above.test_precision,Precision[{{a, 2, 3.2`}, {2.13, 3.25, "a"}}]expected3.. That was only 2.13 rounded down: its precision is `3.32222`. The case now compares withPrecision[2.13]. WMA itself answersMachinePrecisionhere, because the list holds a machine number; the case above it has the same difference, which this PR doesn't change.test_n_precision_and_accuracy(N: evaluate with {precision, accuracy}; give a numeric precision to N rules as a machine real #2028) andtest_gammaexpected whole-digit precisions. They now expect the WMA 15 values, e.g.Precision[N[Pi, {20, 5}]]is5.49715and theGammaquotient from issue 203 has precision99.699.Full run (Python 3.14,
pytest -n 4anddocpipeline.py), this branch next to master 0e0aba0 on the same runner:Benchmark
Local micro-benchmark (best of 5, Python 3.14, macOS):
Total[Table[N[1/k, 50], {k, 2000}]]Times @@ Table[N[1 + 1/k, 50], {k, 2000}]Do[x = N[k, 50] + Pi, {k, 2000}]Total[Table[1./k, {k, 2000}]](machine reals, control)So 1–7% on arbitrary-precision sums and products, against 3% noise on the machine-number control. The memory cost is one slot per
PrecisionReal.Not in this PR (step 6 of the outline)
N[Pi, 20.3]still gives precision20.:Nwith a non-integer precision.PlusandTimes, such asSqrtandPower, keep the minimum precision of their arguments instead of WMA's condition-number adjustment.SetPrecision,SetAccuracy.Precision[1.5`20 - 1.4`2]gives0.852here and0.854in WMA.🤖 Generated with Claude Code