Skip to content

Commit 3ea8315

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 3ea8315

5 files changed

Lines changed: 484 additions & 0 deletions

File tree

doc/source/arb.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,22 @@ 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 x)
562+
563+
Sets *res* to the rational `p/q` in lowest terms, with smallest
564+
positive denominator `q`, that lies in the closed real interval
565+
represented by *x*, 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 *x*, returns 0/1.
568+
569+
Returns 0 and leaves *res* unchanged if *x* is not finite.
570+
571+
Warning: like :func:`arb_get_unique_fmpz`, this method may allocate
572+
a large amount of memory when the midpoint or dyadic endpoints of
573+
*x* have very large or very small magnitude. It is recommended to
574+
check that the midpoint of *x* is within a reasonable range before
575+
calling this method.
576+
561577
.. function:: void arb_floor(arb_t y, const arb_t x, slong prec)
562578
void arb_ceil(arb_t y, const arb_t x, slong prec)
563579
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 x);
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: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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 "fmpq.h"
14+
15+
int
16+
arb_get_simplest_fmpq(fmpq_t res, const arb_t x)
17+
{
18+
arf_t lo_arf, hi_arf;
19+
fmpq_t lo, hi;
20+
int negate;
21+
22+
if (!arb_is_finite(x))
23+
return 0;
24+
25+
/* Fast path: exact ball. The midpoint is the only element of the
26+
* interval, so it is trivially the simplest rational. arf is
27+
* dyadic, so arf_get_fmpq produces the canonical form exactly. */
28+
if (arb_is_exact(x))
29+
{
30+
arf_get_fmpq(res, arb_midref(x));
31+
return 1;
32+
}
33+
34+
/* Fast path: 0 lies in the ball. 0/1 has the smallest possible
35+
* denominator and smallest absolute numerator, so it wins both
36+
* sub-criteria. arb_contains_zero is an exact predicate on the
37+
* arf/mag pair, avoiding any fmpq conversion. */
38+
if (arb_contains_zero(x))
39+
{
40+
fmpq_zero(res);
41+
return 1;
42+
}
43+
44+
/* The remaining intervals are strictly positive or strictly
45+
* negative. Reduce to the positive case so that
46+
* fmpq_simplest_between's "smallest value" tie-break agrees with
47+
* our "smallest absolute numerator" contract on the original. */
48+
negate = arb_is_negative(x);
49+
50+
arf_init(lo_arf);
51+
arf_init(hi_arf);
52+
fmpq_init(lo);
53+
fmpq_init(hi);
54+
55+
arb_get_lbound_arf(lo_arf, x, ARF_PREC_EXACT);
56+
arb_get_ubound_arf(hi_arf, x, ARF_PREC_EXACT);
57+
arf_get_fmpq(lo, lo_arf);
58+
arf_get_fmpq(hi, hi_arf);
59+
60+
if (negate)
61+
{
62+
fmpq_neg(lo, lo);
63+
fmpq_neg(hi, hi);
64+
fmpq_swap(lo, hi);
65+
}
66+
67+
fmpq_simplest_between(res, lo, hi);
68+
if (negate)
69+
fmpq_neg(res, res);
70+
71+
arf_clear(lo_arf);
72+
arf_clear(hi_arf);
73+
fmpq_clear(lo);
74+
fmpq_clear(hi);
75+
return 1;
76+
}

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)