Skip to content

Commit cd5f9f0

Browse files
committed
feat(arb): add arb_get_simplest_fmpq
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).
1 parent 741b017 commit cd5f9f0

5 files changed

Lines changed: 492 additions & 0 deletions

File tree

doc/source/arb.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,24 @@ Radius and interval operations
558558
in swapping. It is recommended to check that the midpoint of *x* is
559559
within a reasonable range before calling this method.
560560

561+
.. function:: int arb_get_simplest_fmpq(fmpq_t res, const arb_t b)
562+
563+
Sets *res* to the rational `p/q` in lowest terms with smallest
564+
positive denominator `q` contained in the closed real interval
565+
represented by *b*, and returns 1. Among rationals with the smallest
566+
denominator, returns the one with smallest absolute numerator (i.e.
567+
closest to 0); if 0 lies in *b*, returns 0/1.
568+
569+
Returns 0 and leaves *res* unchanged if *b* is not finite.
570+
571+
Exact balls (radius 0) take the fast path of returning the
572+
midpoint as a rational directly. Otherwise the implementation
573+
extracts the exact dyadic endpoints of *b* as :type:`fmpq_t` and
574+
delegates to :func:`fmpq_simplest_between`, after handling the
575+
zero-crossing and fully-negative cases explicitly so that the
576+
answer has smallest absolute numerator rather than smallest value
577+
among the simplest-denominator rationals.
578+
561579
.. function:: void arb_floor(arb_t y, const arb_t x, slong prec)
562580
void arb_ceil(arb_t y, const arb_t x, slong prec)
563581
void arb_trunc(arb_t y, const arb_t x, slong prec)

src/arb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ int arb_contains_int(const arb_t x);
338338

339339
void arb_get_interval_fmpz_2exp(fmpz_t a, fmpz_t b, fmpz_t exp, const arb_t x);
340340
int arb_get_unique_fmpz(fmpz_t z, const arb_t x);
341+
int arb_get_simplest_fmpq(fmpq_t res, const arb_t b);
341342

342343
void arb_get_fmpz_mid_rad_10exp(fmpz_t mid, fmpz_t rad, fmpz_t exp, const arb_t x, slong n);
343344

src/arb/get_simplest_fmpq.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright (C) 2026 Edgar Costa
3+
4+
This file is part of FLINT.
5+
6+
FLINT is free software: you can redistribute it and/or modify it under
7+
the terms of the GNU Lesser General Public License (LGPL) as published
8+
by the Free Software Foundation; either version 3 of the License, or
9+
(at your option) any later version. See <https://www.gnu.org/licenses/>.
10+
*/
11+
12+
#include "arb.h"
13+
#include "arf.h"
14+
#include "fmpq.h"
15+
16+
int
17+
arb_get_simplest_fmpq(fmpq_t res, const arb_t b)
18+
{
19+
arf_t lo_arf, hi_arf;
20+
fmpq_t lo, hi;
21+
int negate = 0;
22+
int result = 0;
23+
24+
if (!arb_is_finite(b))
25+
return 0;
26+
27+
/* Fast path: exact ball. The midpoint is the only element of the
28+
* interval, so it is trivially the simplest rational. arf is
29+
* dyadic, so arf_get_fmpq produces the canonical form exactly. */
30+
if (arb_is_exact(b))
31+
{
32+
arf_get_fmpq(res, arb_midref(b));
33+
return 1;
34+
}
35+
36+
arf_init(lo_arf);
37+
arf_init(hi_arf);
38+
fmpq_init(lo);
39+
fmpq_init(hi);
40+
41+
/* Extract exact dyadic endpoints. */
42+
arb_get_lbound_arf(lo_arf, b, ARF_PREC_EXACT);
43+
arb_get_ubound_arf(hi_arf, b, ARF_PREC_EXACT);
44+
arf_get_fmpq(lo, lo_arf);
45+
arf_get_fmpq(hi, hi_arf);
46+
47+
if (fmpq_cmp(lo, hi) > 0)
48+
goto cleanup;
49+
50+
/* If the interval crosses zero, 0/1 is the simplest rational and
51+
* the only one with smallest absolute numerator. We must handle
52+
* this before delegating to fmpq_simplest_between: that function
53+
* breaks denominator ties by smallest value (ceil(lo)), whereas
54+
* our contract is smallest absolute numerator. */
55+
if (fmpq_sgn(lo) <= 0 && fmpq_sgn(hi) >= 0)
56+
{
57+
fmpq_zero(res);
58+
result = 1;
59+
goto cleanup;
60+
}
61+
62+
/* Reduce a fully negative interval to a positive one so that
63+
* fmpq_simplest_between's "smallest value" tie-break agrees with
64+
* our "smallest absolute numerator" contract on the original. */
65+
if (fmpq_sgn(hi) < 0)
66+
{
67+
negate = 1;
68+
fmpq_neg(lo, lo);
69+
fmpq_neg(hi, hi);
70+
fmpq_swap(lo, hi);
71+
}
72+
73+
fmpq_simplest_between(res, lo, hi);
74+
if (negate)
75+
fmpq_neg(res, res);
76+
result = 1;
77+
78+
cleanup:
79+
arf_clear(lo_arf);
80+
arf_clear(hi_arf);
81+
fmpq_clear(lo);
82+
fmpq_clear(hi);
83+
return result;
84+
}

src/arb/test/main.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
#include "t-get_mpn_fixed_mod_log2.c"
121121
#include "t-get_mpn_fixed_mod_pi4.c"
122122
#include "t-get_rand_fmpq.c"
123+
#include "t-get_simplest_fmpq.c"
123124
#include "t-get_str.c"
124125
#include "t-get_unique_fmpz.c"
125126
#include "t-hurwitz_zeta.c"
@@ -328,6 +329,7 @@ test_struct tests[] =
328329
TEST_FUNCTION(arb_get_mpn_fixed_mod_log2),
329330
TEST_FUNCTION(arb_get_mpn_fixed_mod_pi4),
330331
TEST_FUNCTION(arb_get_rand_fmpq),
332+
TEST_FUNCTION(arb_get_simplest_fmpq),
331333
TEST_FUNCTION(arb_get_str),
332334
TEST_FUNCTION(arb_get_unique_fmpz),
333335
TEST_FUNCTION(arb_hurwitz_zeta),

0 commit comments

Comments
 (0)