arb: add arb_get_simplest_fmpq#2673
Conversation
|
No objections to having this function with this API, but doesn't the implementation duplicate |
|
BTW, this function probably ought to have a fast path for input of the form |
|
Thanks!!!! |
631e6e1 to
cd5f9f0
Compare
|
Rewrote in cd5f9f0 (force-pushed). Two changes: 1. Wrapping Performance, single per-call timing under
The rational-target rows are noise (both terminate in 2. Fast path for
At 50000-bit precision that is ~2000× cheaper than the non-exact PR body has been updated to match. |
|
You can use The |
Set res to the rational p/q in lowest terms with smallest positive denominator q lying in the closed real interval represented by b. Among rationals with the smallest denominator, returns the one with smallest absolute numerator (0/1 if 0 lies in b). Exact balls (radius 0) take a fast path returning the midpoint directly. Otherwise the implementation extracts the exact dyadic endpoints of b as fmpq_t and delegates to fmpq_simplest_between, handling the zero-crossing and fully-negative cases explicitly so the tie-break agrees with the contract (smallest absolute numerator, not smallest value).
cd5f9f0 to
c7bc389
Compare
Body is down to ~60 lines. Valgrind on the new test dropped from 1.32 s to 0.91 s (the fuzz iterations that hit a zero-crossing ball no longer allocate). Per-call timings are within noise of the previous numbers. |
Adds a new
arbfunction:Sets
resto the rationalp/qin lowest terms with smallest positivedenominator
qlying in the closed real interval represented byb.Among rationals with the smallest denominator, returns the one with
smallest absolute numerator (
0/1if 0 lies inb).This is the FLINT counterpart to Sage's
RealIntervalFieldElement.simplest_rational(): the same well-knownStern-Brocot / continued-fraction characterisation of the simplest
rational in an interval, exposed at the arb level so other FLINT
callers (and Sage itself, via a follow-up wrapper) can use it without
going through Python.
Where it fits in the arb API
arb_get_unique_fmpz: unique integer in a ball, or fail.arb_get_interval_fmpz_2exp: dyadic endpoints, caller does the rest.arb_get_simplest_fmpq(new): rational analogue of the first;unambiguously returns the simplest rational in the ball.
Algorithm
A thin wrapper around the existing
fmpq_simplest_between(mid, l, r)(HGCD-based Stern-Brocot / continued-fraction engine, due to Daniel
Schultz, in
src/fmpq/simplest_between.c):b.arb_is_exact(b), equivalentlyr = 0in the[n·2^e ± r]representation): the midpoint is theonly element of the interval, so
arf_get_fmpq(res, arb_midref(b))is the answer.
lo, hias exactfmpq_t(dyadic endpoints viaarb_get_lbound_arf/arb_get_ubound_arf+arf_get_fmpq).0/1directly.fmpq_simplest_between, then negate the result.fmpq_simplest_betweendirectly.Steps 4 and 5 are necessary because
fmpq_simplest_betweenbreaksdenominator ties by smallest value (returns
ceil(l)in theinteger-contains case), whereas this function's contract is smallest
absolute numerator. They agree on positive intervals; the sign and
zero-crossing cases need explicit handling.
Reusing
fmpq_simplest_betweenAn earlier draft of this PR re-implemented the iterative Stern-Brocot
descent from scratch. After noticing
fmpq_simplest_betweenalreadyprovides the HGCD-based engine, the body collapsed from ~150 lines of
hand-rolled CF descent to ~50 lines of wrapper, and pi at 50000-bit
precision went from 34 ms per call to under 2 ms (~18×). All 13 tests
unchanged; semantics identical.
Microbench
-O2, x86_64 raptorlake, single call timed viaclock_gettime.Non-exact balls:
355/113Exact balls (fast path applies:
arf_get_fmpqof the midpoint,no
fmpq_simplest_betweencall), averaged over 1000 calls:At 50000-bit precision the exact-ball path is about 2000× cheaper than
the non-exact path on the same input size.
Tests
src/arb/test/t-get_simplest_fmpq.ccovers 13 cases:0/10/17/1[2.3, 3.7]containing 3 →3/11.5→3/2355/113at prec 53 →355/113-355/113→-355/113355/113at prec 50000 →355/113arb_zero_pm_inf→ returns 0fmpqmust bearb_contains_fmpqof the ballp/qwithq <= 1000): the best-approximation bound|r/s - p/q| >= 1/(s*q)implies that a ball of radius< 1/((q-1)*q)aroundp/qhasp/qas its unique simplestrational. The test builds such balls at prec 100 (radius
~ 2^-90,well inside the bound) and asserts the algorithm recovers
p/qexactly.
Verified locally:
make check MOD=arbclean.make check MOD=arb ARGS=arb_get_simplest_fmpqpasses in 0.04 s; the50000-bit tests complete in a few seconds total.
FLINT_TEST_MULTIPLIER=10still under a second.valgrind --leak-check=full, 0 errors, 0 leaks).Test plan
Verified locally on x86_64 Linux (raptorlake, GCC):
make check MOD=arbclean (full arb suite, 4.98 s wall).make check MOD=arb ARGS=arb_get_simplest_fmpqPASS in 0.01 sreported test time, 0.36 s wall (default
FLINT_TEST_MULTIPLIER=1).FLINT_TEST_MULTIPLIER=10PASS in 0.39 s wall(Test 12 runs 10 000 iters and Test 13 runs 2 000 iters).
valgrind --leak-check=full build/arb/test/main arb_get_simplest_fmpqPASS in 1.24 s wall. 0 errors, 0 leaks, 14 388 allocs / 14 388
frees, 3.3 MB total heap.
make valgrind MOD=arb(full arb suite under valgrind) exits 0in ~392 s wall (verified on prior implementation; this change only
shrinks the new test's footprint, so still clean).