Repository navigation
PrecisionReal: keep 64 bits of a low-precision value, as WMA does - #2060
Conversation
f87005c to
2ce29b5
Compare
e045750 to
56f9104
Compare
2ce29b5 to
0fa94b6
Compare
56f9104 to
bff5fef
Compare
0fa94b6 to
a375705
Compare
bff5fef to
66e8ff7
Compare
a375705 to
537a396
Compare
66e8ff7 to
80be2d0
Compare
Part of step 6 of #2047 (MATH-227), on top of the SetPrecision PR. 1.4`2 was stored with the 10 bits its precision needs, as 1.400390625, so it cancelled wrongly: Precision[1.5`20 - 1.4`2] was 0.852, 0.854 in WMA. WMA keeps at least about 64 bits of the value. - A PrecisionReal whose precision needs fewer than MIN_STORED_BITS (64) bits keeps its value with 64 bits in a new slot, _extended. value, precision, sameQ, the output and the code that still uses the minimum precision are unchanged: they see the value rounded to the precision, so nothing can mistake the extra bits for precision. - PrecisionReal.from_value builds such a number. The parser, N of exact numbers and of constants, Plus, Times, the functions with a condition number, SetPrecision and N's bounds use it, and read extended_value. - Accuracy and Plus take Log10 of the extended value: Accuracy[3.1416`2] is 1.50285, as in WMA (the doctest had 1.50298, the Log10 of 3.140625). - The object cache key includes the extended value: 1.4`2 and 1.4001`2 round to the same 10 bits but are different numbers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As other module constants are (review on #2057). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80be2d0 to
e2a1418
Compare
One reason I personally am not worried about this is that we don't really know how often Precision is used, let alone Low precision. And I am guessing that it is not much. |
|
In this branch I am seeing: In[1]:= SetPrecision[1.5 + 2.5 I, 20] === SetPrecision[1.5 + 2.5 I, 20.3]
Out[1]= TrueIs this expected? Should these be different objects in the cache? Suggested test for adaption: @pytest.mark.parametrize(
("str_expr", "str_expected", "msg"),
[
(
"SetPrecision[1.5 + 2.5 I, 20] === SetPrecision[1.5 + 2.5 I, 20.3]",
"False",
"different low-precision complex values must not collide in the object cache",
),
(
"Precision[SetAccuracy[1.5 + 2.5 I, 20]]",
"20.",
"complex values should preserve the same accuracy semantics after SetAccuracy",
),
(
"Precision /@ {Re[#], Im[#]} &[SetPrecision[1.5 + 2.5 I, 20.3]]",
"{20.1619, 20.3837}",
"both parts should share the same accuracy target",
),
],
)
def test_low_precision_complex_cache_regression(str_expr, str_expected, msg):
"""Guard against recursive cache collisions in complex numbers."""
check_evaluation(str_expr, str_expected, failure_message=msg)
`` |
|
Thanks for checking. Yes, In[1]:= SetPrecision[1.5 + 2.5 I, 20] === SetPrecision[1.5 + 2.5 I, 20.3]
Out[1]= True
In[2]:= SameQ[1.5`20, 1.5`20.3]
Out[2]= True
I added your tests in
About the benchmark: agreed. The slowdown is only for numbers with fewer than about 19 digits, and only in the code that tracks the error. |
Suggested in review, with the WMA 15 values. SetPrecision[1.5 + 2.5 I, 20] and SetPrecision[1.5 + 2.5 I, 20.3] are SameQ, as in WMA (SameQ holds for numbers that agree to their precision), but they are two numbers with precisions 20. and 20.3, not one cached object. Precision[SetAccuracy[1.5 + 2.5 I, 20]] is 20.4647 in WMA: each part gets accuracy 20 + Log10[2]/2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the explanation and improvements. LGTM. Merge when you are satisified. |
…WMA (#2064) Since #2060 a PrecisionReal with fewer than 64 bits of precision keeps its value with 64 bits (``_extended``), but InputForm and FullForm printed only the digits that the precision gives, so the printed form did not read back as the same number. As in WMA, InputForm and FullForm now print the shortest decimal string that reads back as the kept value; OutputForm and StandardForm still print the digits of the precision. WMA 15: InputForm[3.1415926535897932385`3] -> 3.1415926535897932385`3. InputForm[-4.326563712`2] -> -4.326563712`2. InputForm[-4.32`4] -> -4.32`4. (the last two are in test/format/format_tests-WMA.yaml; the Mathics expectation for -4.326563712`2 in format_tests.yaml is updated to match). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of step 6 of the outline in #2047. On top of #2057 (and the PRs below it): this diff is against its branch.
A low-precision number was stored with only the bits its precision needs:
1.4`2was1.400390625. WMA keeps at least about 64 bits of the value, and the difference shows when numbers cancel:Precision[1.5`20 - 1.4`2]0.8520510.8538720.853872Accuracy[3.1416`2]1.502981.502851.50285SetPrecision[1.4`2, 20]1.40039062500000000001.40000000000000000001.4000000000000000000SetPrecision[N[Pi, 3], 18]3.141601562500000003.141592653589793243.14159265358979324SetPrecision[Sin[1.4`2], 18]0.9854736328125000000.9854497299884601810.985449729988460181Design
The extra bits must not be mistaken for precision. Many builtins rebuild a number from
x.valueor readx.precision(bits), and the code that still uses the minimum precision (complex numbers, special functions,Equal) works from those. Sovaluestays the value rounded to the precision, and the 64-bit value is kept beside it:PrecisionReal._extended, a new slot: the value withMIN_STORED_BITS(64) bits, kept only when the precision needs fewer.value,precision,sameQ, the output and every other reader are unchanged.PrecisionReal.from_value(value, precision10)rounds to the precision and keeps the 64-bit value. The parser,Nof exact numbers and constants,Plus,Times, the functions with a condition number (Power, Sqrt: precision from the condition number, as in WMA #2052),SetPrecisionandN's bounds use it.extended_valueis read by the arithmetic that tracks the error (Plus,Times, the condition-number helper,SetPrecision), and for theLog10inAccuracyand inPlus's accuracy. These now work at no fewer than 64 bits.1.4`2and1.4001`2round to the same 10 bits but are different numbers. (They are stillSameQandEqual, as in WMA.)N[Pi, d]withdup to machine precision came from numpy, with 53 bits; the 64-bit value is taken from sympy or mpmath.Numbers with 19 or more digits need 64 bits or more anyway, so nothing changes for them.
Not changed: WMA's
InputFormprints all the stored digits (3.1415926535897932385`3.), while Mathics3 prints the digits the precision gives (3.142`3.). That is a question of output, not of this PR.Tests
test_low_precision_storageintest/builtin/atomic/test_numbers.py, with WMA 15 values.AccuracydoctestAccuracy[3.1416`2]expected1.50298, which is2 - Log10[3.140625]: the rounded value. It now expects WMA's1.50285.Full run on kimmy, next to #2057 at the same time: doctests all pass. The pytest failures are the order-dependent ones (17 here, 30 on #2057).
Benchmark
Total[Table[N[1/k, 50], {k, 2000}]]Times @@ Table[N[1 + 1/k, 50], {k, 2000}]Do[Sqrt[N[k, 50]], {k, 2000}]Total[Table[1./k, {k, 2000}]](machine reals)Total[Table[N[1/k, 5], {k, 2000}]]Do[x = Sin[N[k, 5]] + 1.5`3, {k, 2000}]At 50 digits nothing changes. Low-precision arithmetic is 7–10% slower, since it now works with 64 bits and keeps the extra value.
🤖 Generated with Claude Code