A calculator that doesn't round

dimview1 pts0 comments

A calculator that doesn’t round | Constructive Calculator

&larr; Constructive Calculator<br>No Rounding

Constructive Calculator is an iPhone calculator that doesn&rsquo;t round at all.<br>It computes with constructive real numbers: every result is exact, and you can scroll<br>any answer for as many correct digits as you want. What that gives you:

exp(&pi;&radic;163), Ramanujan&rsquo;s constant, looks like the<br>integer 262,537,412,640,768,744. Scroll past the decimal point and you find<br>twelve 9s before it finally diverges.

exp(100) + 42 &minus; exp(100) returns exactly 42 . In IEEE 754 double,<br>exp(100) &asymp; 2.7&times;1043, and adding 42 changes nothing (42<br>falls off the bottom of the representation), so the subtraction gives 0 . The constructive<br>engine evaluates exp(100) to as many digits as is needed, twice, and the subtraction<br>leaves only 42. The calculator does not simplify the expression first; it does indeed<br>perform both operations as instructed: addition and then subtraction.

It&rsquo;s made possible by constructive (or computable) real arithmetic: instead of<br>storing a number as a fixed-width approximation, store a function that produces an approximation<br>to any requested precision, and evaluate each subexpression to whatever precision the final<br>answer needs. Hans Boehm built a Java library for this in the 1980s and 90s, and it has been<br>the engine behind Android&rsquo;s built-in Calculator, a fact that periodically<br>delights Hacker<br>News.

But there was no equivalent on iPhone that I could find, so I built one, by porting<br>Boehm&rsquo;s engine.

It&rsquo;s 2026, so I didn&rsquo;t hand-write the port. I directed Opus 4.8 to translate the source<br>line by line into Swift, and steered the process (architecture, UI, on-device testing, the<br>App Store machinery) over a long back-and-forth.

What got ported:

com.hp.creals, Boehm&rsquo;s constructive-reals library (CR,<br>the transcendental functions, the Gauss-Legendre AGM for &pi;), into a Swift package.

UnifiedReal / BoundedRational from AOSP&rsquo;s<br>ExactCalculator, the layer that keeps results symbolically exact when it can (so<br>cos(&pi;/3) comes back as exactly &frac12;, not a constructive<br>approximation of it) and makes comparisons decidable for the rational cases.

The expression evaluator and a SwiftUI front end with the scroll-for-more-digits<br>display.

The interesting work was what didn&rsquo;t translate directly. Java&rsquo;s<br>synchronized, checked exceptions, and AsyncTask have no Swift<br>equivalents, so the port re-expressed them: exceptions became Swift throws,<br>precision-overflow and divergence became typed errors, and Java&rsquo;s interrupt-based<br>cancellation became Swift Concurrency&rsquo;s Task.checkCancellation(). The 2013<br>Mac I develop on can&rsquo;t run a current Xcode, so signing and TestFlight uploads run on<br>GitHub Actions; the local machine never touches the submission.

Then I had a different model, Fable 5, do a clean-room review: fresh context, no<br>memory of how the code was built, just the repository. It earned its keep:

A concurrency bug : @MainActor was attached to the wrong type, so the<br>view model wasn&rsquo;t main-actor isolated and a background task was mutating<br>@Published state off the main thread.

A subtler one: Boehm&rsquo;s Java get_appr (the memoized approximation<br>routine) is synchronized; the port had dropped the lock and leaned on an actor<br>to serialize access, but two paths slipped around the actor, so two threads could race the<br>same constructive real&rsquo;s cache. For shared singletons like &pi; that is a<br>memory-safety bug, not just a wrong digit. The fix was the faithful one: put the lock back<br>(reentrant, since the square-root routine re-enters itself).

A main-thread freeze : a large factorial was evaluated synchronously, hanging the<br>UI with no way to cancel. It now runs off the main thread, cancellable, with a sane cap.

And the boring-but-mandatory App Store gaps: a missing privacy manifest, the<br>export-compliance flag, accessibility labels.

As a data point on AI-assisted development, I found this quite useful: the port<br>itself was faithful (Fable 5 checked the algorithms against Boehm&rsquo;s Java and<br>found no transcription errors), but the adaptation to a different concurrency model is<br>where the bugs lived, and a second model with no stake in the first one&rsquo;s choices caught<br>them. None would have shown up in the unit tests.

Known Limitations

Exact real arithmetic has a hard wall: equality is undecidable . You cannot, in<br>general, prove two constructive reals are equal by any finite computation. So<br>exp(100)+42&minus;exp(100) is correctly 42, but the app can&rsquo;t prove<br>it terminates; it prints 42.000… with a &ldquo;more digits&rdquo; arrow<br>and will emit zeros forever. It always gives you the right number to as many digits as you<br>want; it only labels a result &ldquo;exact&rdquo; when the value stays inside the<br>structured rational-times-known-constant form. cos(&pi;/3)=&frac12; does;<br>exp(100)+42 doesn&rsquo;t....

rsquo constructive calculator digits boehm java

Related Articles