Skip to content

Round: round exact and arbitrary-precision numbers exactly - #2034

Merged
rocky merged 4 commits into
Mathics3:masterfrom
tombouctou:audit-round
Oct 21, 2026
Merged

rocky merged 4 commits into
Mathics3:masterfrom
tombouctou:audit-round

Conversation

@tombouctou

Copy link
Copy Markdown
Collaborator

Round converted its argument to a machine float before rounding, so any
number beyond 2^53 lost its last digits:

Round[10^20 + 1]         gave 100000000000000000000
Round[Pi 10^20]          gave 314159265358979334144
Round[1.5`30 10^25]      gave 15000000000000000285212672
Round[1/2 + 10^-40]      gave 0

Floor and Ceiling go through SymPy and were right. Round now rounds a
rational or a float (machine or arbitrary precision) exactly, ties to
even as before, and an exact irrational number by SymPy's floor of
x + 1/2. Complex arguments and anything not known to be real take the
old path.

To reproduce on master:

Round[10^20 + 1]   (* 100000000000000000000 *)
Round[Pi 10^20]    (* 314159265358979334144; Floor[Pi 10^20] is 314159265358979323846 *)

Tests: new cases in test/builtin/test_numeric.py (test_numeric). They fail on current master and pass with this change.

The full pytest suite and the doctests pass with this change (run together with the other fixes from the same audit; the only pytest failures, in test_main.py and test_returncode.py, are the same as on master and come from the mathics script not being on PATH in our environment).

Found in a correctness audit of Mathics3 (differential checks against SymPy/mpmath/exact computation, and calls of every builtin with edge-case arguments).

🤖 Generated with Claude Code

tombouctou and others added 2 commits October 18, 2026 13:29
Round converted its argument to a machine float before rounding, so any
number beyond 2^53 lost its last digits:

    Round[10^20 + 1]         gave 100000000000000000000
    Round[Pi 10^20]          gave 314159265358979334144
    Round[1.5`30 10^25]      gave 15000000000000000285212672
    Round[1/2 + 10^-40]      gave 0

Floor and Ceiling go through SymPy and were right. Round now rounds a
rational or a float (machine or arbitrary precision) exactly, ties to
even as before, and an exact irrational number by SymPy's floor of
x + 1/2. Complex arguments and anything not known to be real take the
old path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread mathics/builtin/numeric.py Outdated
return Expression(SymbolTimes, Integer(n), k)


def round_real_exactly(x) -> Optional[int]:

@rocky rocky Oct 18, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of thing should be in mathics.eval.numeric.

What goes into mathics.buiitin are the kinds of properties associated with the Builtin, which in Mathics3, is are its class parameters (attributes, rules, custom error messages).

Things associated with evaluation that do not need "self" the class object should be under mathics.eval.

This way, evaluation routines can have access to those "helper" functions should they need it. It also makes it feasible to write a compiler to some sort of custom bytecode, if we ever get there.

@rocky rocky Oct 18, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in the future, please put in PRs in branches in this repository. Thanks.

I see this is being done on future PRs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done in 5c9339c: the rounding logic and the round_real_exactly helper are now eval_Round in mathics.eval.numeric, and Round.eval_two_args only calls it.

tombouctou and others added 2 commits October 21, 2026 23:18
The rounding logic and its round_real_exactly helper do not need the
Builtin instance, so they now live in mathics.eval.numeric as eval_Round;
Round.eval_two_args only calls it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rocky

rocky commented Oct 21, 2026

Copy link
Copy Markdown
Member

LGTM

@rocky
rocky merged commit 20cdd51 into Mathics3:master Oct 21, 2026
15 checks passed
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.

2 participants