Skip to main content

feanor_math/algorithms/
int_bisect.rs

1use crate::integer::*;
2use crate::ordered::*;
3use crate::ring::*;
4
5/// Finds some integer `left <= n < right` such that `f(n) <= 0` and `f(n + 1) > 0`, given
6/// that `f(left) <= 0` and `f(right) > 0`.
7///
8/// If we consider a continuous extension of `f` to the real numbers, this means that
9/// the function finds `floor(x)` for some root `x` of `f` between `left` and `right`.
10pub fn bisect_floor<R, F>(ZZ: R, left: El<R>, right: El<R>, mut func: F) -> El<R>
11where
12    R: RingStore,
13    R::Type: IntegerRing,
14    F: FnMut(&El<R>) -> El<R>,
15{
16    assert!(ZZ.is_lt(&left, &right));
17    let mut l = left;
18    let mut r = right;
19    assert!(!ZZ.is_pos(&func(&l)));
20    assert!(ZZ.is_pos(&func(&r)));
21    loop {
22        let mut mid = ZZ.add_ref(&l, &r);
23        ZZ.euclidean_div_pow_2(&mut mid, 1);
24
25        if ZZ.eq_el(&mid, &l) || ZZ.eq_el(&mid, &r) {
26            return l;
27        } else if ZZ.is_pos(&func(&mid)) {
28            r = mid;
29        } else {
30            l = mid;
31        }
32    }
33}
34
35/// Given a function `f` with `lim_{x -> -inf} f(x) = -inf` and `lim_{x -> inf} f(x) = inf`,
36/// finds some integer `n` such that `f(n) <= 0` and `f(n + 1) > 0`.
37///
38/// Good performance is only guaranteed for increasing functions `f`. In this case, the
39/// solution is unique, and the function terminates `O(log|approx - solution|)` steps.
40///
41/// If we consider a continuous extension of `f` to the real numbers, this means that
42/// the function finds `floor(x)` for some root `x` of `f`.
43pub fn find_root_floor<R, F>(ZZ: R, approx: El<R>, mut func: F) -> El<R>
44where
45    R: RingStore,
46    R::Type: IntegerRing,
47    F: FnMut(&El<R>) -> El<R>,
48{
49    let mut left = ZZ.clone_el(&approx);
50    let mut step = ZZ.one();
51    while ZZ.is_pos(&func(&left)) {
52        ZZ.sub_assign_ref(&mut left, &step);
53        ZZ.mul_pow_2(&mut step, 1);
54    }
55    step = ZZ.one();
56    let mut right = approx;
57    while !ZZ.is_pos(&func(&right)) {
58        ZZ.add_assign_ref(&mut right, &step);
59        ZZ.mul_pow_2(&mut step, 1);
60    }
61    return bisect_floor(ZZ, left, right, func);
62}
63
64/// Computes the largest integer `x` such that `x^root <= n`, where `n` is a positive integer.
65///
66/// # Integer overflow
67///
68/// This function is designed to avoid integer overflow if possible without a huge performance
69/// benefit. In particular, in some cases the naive way would require evaluating `(x + 1)^root`
70/// to ensure this is really larger than `n` - however, this might lead to integer overflow as in
71/// ```should_panic
72/// // compute the 62nd root of 2^62
73/// let result: i64 = 2;
74/// assert!(result.pow(62) <= (1 << 62) && (result + 1).pow(62) > (1 << 62));
75/// ```
76/// These cases can somewhat be avoided by first doing a size estimate.
77/// ```rust
78/// # use feanor_math::ring::*;
79/// # use feanor_math::algorithms::int_bisect::*;
80/// # use feanor_math::primitive_int::*;
81/// let ZZ = StaticRing::<i64>::RING;
82/// assert_eq!(2, root_floor(&ZZ, 1 << 62, 62));
83/// ```
84/// I some edge cases this is not enough, and so integer overflow can occur.
85/// ```should_panic
86/// # use feanor_math::ring::*;
87/// # use feanor_math::algorithms::int_bisect::*;
88/// # use feanor_math::primitive_int::*;
89/// let ZZ = StaticRing::<i64>::RING;
90/// assert_eq!(26, root_floor(&ZZ, ZZ.pow(5, 26), 26));
91/// ```
92pub fn root_floor<R>(ZZ: R, n: El<R>, root: usize) -> El<R>
93where
94    R: RingStore,
95    R::Type: IntegerRing,
96{
97    assert!(root > 0);
98    if root == 1 {
99        return n;
100    }
101    if ZZ.is_zero(&n) {
102        return ZZ.zero();
103    }
104    assert!(!ZZ.is_neg(&n));
105    let log2_ceil_n = ZZ.abs_log2_ceil(&n).unwrap();
106
107    return find_root_floor(
108        &ZZ,
109        ZZ.from_float_approx(ZZ.to_float_approx(&n).powf(1.0 / root as f64))
110            .unwrap_or(ZZ.zero()),
111        |x| {
112            let x_pow_root_half = ZZ.pow(ZZ.clone_el(x), root / 2);
113            // we first make a size estimate, mainly to avoid situations (high `root`) where we get
114            // avoidable integer overflow (also might improve performance)
115            if ZZ.abs_log2_ceil(x).unwrap_or(0) >= 2
116                && (ZZ.abs_log2_ceil(&x_pow_root_half).unwrap() - 1) * 2 >= log2_ceil_n
117            {
118                if ZZ.is_neg(x) {
119                    return ZZ.negate(ZZ.clone_el(&n));
120                } else {
121                    return ZZ.clone_el(&n);
122                }
123            } else {
124                let x_pow_root = if root.is_multiple_of(2) {
125                    ZZ.pow(x_pow_root_half, 2)
126                } else {
127                    ZZ.mul_ref_snd(ZZ.pow(x_pow_root_half, 2), x)
128                };
129                return ZZ.sub_ref_snd(x_pow_root, &n);
130            }
131        },
132    );
133}
134
135#[cfg(test)]
136use crate::primitive_int::StaticRing;
137
138#[test]
139fn test_bisect_floor() {
140    assert_eq!(
141        0,
142        bisect_floor(&StaticRing::<i64>::RING, 0, 10, |x| if *x == 0 { 0 } else { 1 })
143    );
144    assert_eq!(
145        9,
146        bisect_floor(&StaticRing::<i64>::RING, 0, 10, |x| if *x == 10 { 1 } else { 0 })
147    );
148    assert_eq!(-15, bisect_floor(&StaticRing::<i64>::RING, -20, -10, |x| *x + 15));
149}
150
151#[test]
152fn test_root_floor() {
153    assert_eq!(4, root_floor(&StaticRing::<i64>::RING, 16, 2));
154    assert_eq!(3, root_floor(&StaticRing::<i64>::RING, 27, 3));
155    assert_eq!(4, root_floor(&StaticRing::<i64>::RING, 17, 2));
156    assert_eq!(3, root_floor(&StaticRing::<i64>::RING, 28, 3));
157    assert_eq!(4, root_floor(&StaticRing::<i64>::RING, 24, 2));
158    assert_eq!(3, root_floor(&StaticRing::<i64>::RING, 63, 3));
159    assert_eq!(
160        5,
161        root_floor(&StaticRing::<i64>::RING, StaticRing::<i64>::RING.pow(5, 25), 25)
162    );
163    assert_eq!(
164        4,
165        root_floor(&StaticRing::<i64>::RING, StaticRing::<i64>::RING.pow(5, 25), 26)
166    );
167    assert_eq!(
168        4,
169        root_floor(&StaticRing::<i64>::RING, StaticRing::<i64>::RING.pow(5, 25), 27)
170    );
171    assert_eq!(
172        4,
173        root_floor(&StaticRing::<i64>::RING, StaticRing::<i64>::RING.pow(5, 25), 28)
174    );
175}