1use crate::hyperreal::{check_power_degree, Hyperreal, PowerError};
21use crate::limit_value::{Depends, ResidueError, ResidueType, MAX_MODULUS};
22use crate::numeric::{BigInt, Rational};
23use crate::poly::Poly;
24use std::cmp::Ordering;
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum FloorError {
29 Irrational,
32 Residue(ResidueError),
34}
35
36impl FloorError {
37 pub fn describe(&self, inf: &str) -> String {
39 match self {
40 FloorError::Irrational => format!(
41 "the integer part of a value with an irrational multiple of a power of 1/{inf} is \
42 no polynomial in 1/{inf}"
43 ),
44 FloorError::Residue(e) => e.describe(&format!("⌈1/{inf}⌉")),
45 }
46 }
47}
48
49const DIRECT_PERIOD: u64 = 1 << 16;
52
53fn integer_on(p: &Poly, first: u64, step: u64) -> bool {
59 let coeffs = p.coeffs();
60 if coeffs.iter().all(Rational::is_integer) {
61 return true;
62 }
63 let d = coeffs.iter().fold(BigInt::from_u64(1), |l, c| {
64 let den = c.denominator();
65 let g = l.gcd(den);
66 l.div_rem(&g).expect("a gcd of positive denominators is positive").0.mul(den)
67 });
68 let modular = d.to_u64().and_then(|dd| {
69 let period = dd / num_gcd(step % dd, dd).max(1);
70 (period <= DIRECT_PERIOD).then_some((dd, period))
71 });
72 let Some((dd, period)) = modular else {
73 let degree = p.degree().unwrap_or(0) as u64;
74 return (0..=degree).all(|t| {
75 let h = BigInt::from_u64(first).add(&BigInt::from_u64(step).mul(&BigInt::from_u64(t)));
76 p.eval(&Rational::from_bigint(h)).is_integer()
77 });
78 };
79 let residues: Vec<u128> = coeffs
80 .iter()
81 .map(|c| {
82 let q = c.numerator().mul(&d.div_rem(c.denominator()).expect("D is a multiple of each denominator").0);
83 let r = q.div_rem(&d).expect("D is positive").1;
84 let r = if r.is_negative() { r.add(&d) } else { r };
85 u128::from(r.to_u64().expect("a residue below D"))
86 })
87 .collect();
88 let dd = u128::from(dd);
89 (0..period).all(|t| {
90 let h = (u128::from(first) + u128::from(step) * u128::from(t)) % dd;
91 residues.iter().rev().fold(0u128, |acc, c| (acc * h + c) % dd) == 0
92 })
93}
94
95fn num_gcd(a: u64, b: u64) -> u64 {
96 if b == 0 {
97 a
98 } else {
99 num_gcd(b, a % b)
100 }
101}
102
103fn floor_limited(y: &Hyperreal) -> BigInt {
106 let st = y.standard_part().expect("a limited value has a standard part");
107 let fl = st.floor();
108 let on_integer = st == crate::algebraic::Algebraic::from_rational(Rational::from_bigint(fl.clone()));
109 if on_integer && y.sub(&Hyperreal::from_algebraic(st)).signum() < 0 {
110 fl.sub(&BigInt::from_u64(1))
111 } else {
112 fl
113 }
114}
115
116#[derive(Clone, Debug, PartialEq, Eq, Hash)]
118pub struct Hypernatural {
119 value: Depends<Poly>,
120}
121
122fn lcm(a: &BigInt, b: &BigInt) -> BigInt {
124 a.mul(b).div_rem(&a.gcd(b)).expect("a positive gcd").0
125}
126
127fn smooth_part(n: &BigInt, q: &BigInt) -> BigInt {
129 let one = BigInt::from_u64(1);
130 let mut rest = n.clone();
131 loop {
132 let g = rest.gcd(q);
133 if g == one {
134 break;
135 }
136 rest = rest.div_rem(&g).expect("a positive gcd").0;
137 }
138 n.div_rem(&rest).expect("a positive remainder").0
139}
140
141fn eval_integer(f: &Poly, x: u64) -> BigInt {
143 f.eval(&Rational::from_bigint(BigInt::from_u64(x)))
144 .to_bigint()
145 .expect("a hypernatural branch is integer-valued on its class")
146}
147
148fn poly_text(p: &Poly, generator: &str) -> String {
150 let terms: Vec<(bool, String)> = (0..p.coeffs().len())
151 .rev()
152 .filter(|&i| !p.coeff(i).is_zero())
153 .map(|i| {
154 let c = p.coeff(i);
155 let negative = c.is_negative();
156 let m = c.abs();
157 let power = match i {
158 0 => String::new(),
159 1 => generator.to_string(),
160 k => format!("{generator}^{k}"),
161 };
162 let body = if i == 0 {
163 m.to_string()
164 } else if m == Rational::one() {
165 power
166 } else if m.is_integer() {
167 format!("{m}·{power}")
168 } else {
169 format!("({m})·{power}")
170 };
171 (negative, body)
172 })
173 .collect();
174 join_signed(terms)
175}
176
177fn reciprocal_text(p: &Poly, inf: &str) -> String {
179 let terms: Vec<(bool, String)> = (0..p.coeffs().len())
180 .rev()
181 .filter(|&i| !p.coeff(i).is_zero())
182 .map(|i| {
183 let c = p.coeff(i);
184 let negative = c.is_negative();
185 let m = c.abs();
186 if i == 0 {
187 return (negative, m.to_string());
188 }
189 let power = if i == 1 {
190 inf.to_string()
191 } else {
192 format!("{inf}^{i}")
193 };
194 let (num, den) = (m.numerator().clone(), m.denominator().clone());
195 let body = if den == BigInt::from_i64(1) {
196 format!("{num}/{power}")
197 } else {
198 format!("{num}/({den}·{power})")
199 };
200 (negative, body)
201 })
202 .collect();
203 join_signed(terms)
204}
205
206fn join_signed(terms: Vec<(bool, String)>) -> String {
207 if terms.is_empty() {
208 return "0".to_string();
209 }
210 let mut out = String::new();
211 for (i, (negative, body)) in terms.into_iter().enumerate() {
212 match (i, negative) {
213 (0, true) => out.push_str(&format!("-{body}")),
214 (0, false) => out.push_str(&body),
215 (_, true) => out.push_str(&format!(" - {body}")),
216 (_, false) => out.push_str(&format!(" + {body}")),
217 }
218 }
219 out
220}
221
222impl Hypernatural {
223 pub fn generator(ty: &ResidueType) -> Hypernatural {
225 Hypernatural {
226 value: Depends::constant(Poly::x(), ty),
227 }
228 }
229
230 pub fn from_integer(n: BigInt, ty: &ResidueType) -> Hypernatural {
232 Hypernatural {
233 value: Depends::constant(Poly::constant(Rational::from_bigint(n)), ty),
234 }
235 }
236
237 pub fn from_polynomial(p: Poly, ty: &ResidueType) -> Option<Hypernatural> {
241 let m = ty.modulus();
242 if !integer_on(&p, 0, 1) && !ty.residues().into_iter().all(|r| integer_on(&p, r, m)) {
243 return None;
244 }
245 Some(Hypernatural {
246 value: Depends::constant(p, ty),
247 })
248 }
249
250 pub fn from_branches(value: Depends<Poly>) -> Option<Hypernatural> {
253 let candidate = Hypernatural { value };
254 let (l, classes) = candidate.admitted_classes();
255 for (c, p) in classes {
256 if !integer_on(p, c, l) {
257 return None;
258 }
259 }
260 Some(candidate)
261 }
262
263 pub fn floor_of(x: &Hyperreal, ty: &ResidueType) -> Result<Hypernatural, FloorError> {
268 let (principal, rest) = x.principal_part();
269 let mut coefficients = vec![Rational::zero()];
270 for a in &principal {
271 coefficients.push(a.to_rational().ok_or(FloorError::Irrational)?);
272 }
273 let p = Poly::from_coeffs(coefficients);
274 let denominator = p
275 .coeffs()
276 .iter()
277 .fold(BigInt::from_u64(1), |acc, c| lcm(&acc, c.denominator()));
278 let scaled = Hypernatural::from_polynomial(p.scale(&Rational::from_bigint(denominator.clone())), ty)
279 .expect("an integer polynomial is an integer everywhere");
280 let fractions = scaled.rem(&denominator).map_err(FloorError::Residue)?;
281 let value = fractions.map(|f| {
282 let frac = Rational::new(f.clone(), denominator.clone()).expect("a positive denominator");
283 let carry = floor_limited(&rest.add(&Hyperreal::from_rational(frac.clone())));
284 p.sub(&Poly::constant(frac))
285 .add(&Poly::constant(Rational::from_bigint(carry)))
286 });
287 Ok(Hypernatural::from_branches(value).expect("each branch is an integer on its class"))
288 }
289
290 pub fn ceil_of(x: &Hyperreal, ty: &ResidueType) -> Result<Hypernatural, FloorError> {
292 Ok(Self::floor_of(&x.neg(), ty)?.neg())
293 }
294
295 pub fn depends(&self) -> &Depends<Poly> {
296 &self.value
297 }
298
299 pub fn add(&self, o: &Hypernatural) -> Result<Hypernatural, ResidueError> {
300 Ok(Hypernatural {
301 value: self.value.zip_with(&o.value, |a, b| a.add(b))?,
302 })
303 }
304
305 pub fn sub(&self, o: &Hypernatural) -> Result<Hypernatural, ResidueError> {
306 Ok(Hypernatural {
307 value: self.value.zip_with(&o.value, |a, b| a.sub(b))?,
308 })
309 }
310
311 pub fn mul(&self, o: &Hypernatural) -> Result<Hypernatural, ResidueError> {
312 Ok(Hypernatural {
313 value: self.value.zip_with(&o.value, |a, b| a.mul(b))?,
314 })
315 }
316
317 pub fn degree(&self) -> usize {
319 self.value
320 .branches()
321 .map(|(_, p)| p.degree().unwrap_or(0))
322 .max()
323 .unwrap_or(0)
324 }
325
326 pub fn pow(&self, k: u64) -> Result<Hypernatural, PowerError> {
329 check_power_degree(self.degree(), k)?;
330 let square = |a: &Hypernatural, b: &Hypernatural| {
331 a.mul(b).expect("a value meets its own residue classes")
332 };
333 let mut acc = Hypernatural::from_integer(BigInt::from_u64(1), self.value.residue_type());
334 let mut base = self.clone();
335 let mut e = k;
336 while e > 0 {
337 if e & 1 == 1 {
338 acc = square(&acc, &base);
339 }
340 e >>= 1;
341 if e > 0 {
342 base = square(&base, &base);
343 }
344 }
345 Ok(acc)
346 }
347
348 pub fn neg(&self) -> Hypernatural {
349 Hypernatural {
350 value: self.value.map(|a| a.neg()),
351 }
352 }
353
354 fn admitted_classes(&self) -> (u64, Vec<(u64, &Poly)>) {
358 let l = lcm(
359 &BigInt::from_u64(self.value.modulus()),
360 &BigInt::from_u64(self.value.residue_type().modulus()),
361 )
362 .to_u64()
363 .expect("a value is held within the bound of its own type");
364 let classes = (0..l).filter_map(|c| self.value.at(c).map(|f| (c, f))).collect();
365 (l, classes)
366 }
367
368 fn residue_period(&self, k: &BigInt) -> BigInt {
370 let one = BigInt::from_u64(1);
371 let (l, classes) = self.admitted_classes();
372 let mut period = one.clone();
373 for (c, f) in classes {
374 let degree = f.degree().unwrap_or(0) as u64;
375 let mut differences: Vec<BigInt> =
376 (0..=degree).map(|t| eval_integer(f, c + l * t)).collect();
377 let mut lcm_to_j = one.clone();
378 for j in 1..=degree {
379 differences = differences.windows(2).map(|w| w[1].sub(&w[0])).collect();
380 lcm_to_j = lcm(&lcm_to_j, &BigInt::from_u64(j));
381 let q = k.div_rem(&differences[0].gcd(k)).expect("k > 0").0;
382 if q != one {
383 period = lcm(&period, &q.mul(&smooth_part(&lcm_to_j, &q)));
384 }
385 }
386 }
387 period.mul(&BigInt::from_u64(l))
388 }
389
390 pub fn rem(&self, k: &BigInt) -> Result<Depends<BigInt>, ResidueError> {
393 assert!(k.is_positive(), "a modulus is positive");
394 let period = self.residue_period(k);
395 let ty = self.value.residue_type();
396 let classes = lcm(&period, &BigInt::from_u64(ty.modulus()));
397 let period = match period.to_u64() {
398 Some(p) if classes <= BigInt::from_u64(MAX_MODULUS) => p,
399 _ => return Err(ResidueError::TooFine { classes }),
400 };
401 let m = self.value.modulus();
402 let raw = self.value.raw_branches();
403 Depends::from_fn(period, ty, move |x| {
404 let f = raw[(x % m) as usize]
405 .as_ref()
406 .expect("an admitted residue has a branch");
407 let r = eval_integer(f, x).div_rem(k).expect("k > 0").1;
408 if r.is_negative() {
409 r.add(k)
410 } else {
411 r
412 }
413 })
414 }
415
416 pub fn scaled_floor(&self, t: &Rational) -> Result<Hypernatural, ResidueError> {
418 let p = Rational::from_bigint(t.numerator().clone());
419 let q = t.denominator().clone();
420 let scaled = Hypernatural {
421 value: self.value.map(|f| f.scale(&p)),
422 };
423 let rem = scaled.rem(&q)?;
424 let inv = Rational::from_bigint(q).recip().expect("q > 0");
425 Ok(Hypernatural {
426 value: scaled.value.zip_with(&rem, |f, r| {
427 f.sub(&Poly::constant(Rational::from_bigint(r.clone()))).scale(&inv)
428 })?,
429 })
430 }
431
432 pub fn scaled_ceil(&self, t: &Rational) -> Result<Hypernatural, ResidueError> {
434 Ok(self.scaled_floor(&t.negated())?.neg())
435 }
436
437 pub fn compare(&self, o: &Hypernatural) -> Result<Depends<Ordering>, ResidueError> {
440 self.value.zip_with(&o.value, |a, b| {
441 let d = a.sub(b);
442 match d.leading() {
443 None => Ordering::Equal,
444 Some(c) if c.is_negative() => Ordering::Less,
445 Some(_) => Ordering::Greater,
446 }
447 })
448 }
449
450 pub fn is_infinite(&self) -> Depends<bool> {
451 self.value.map(|p| p.degree().unwrap_or(0) > 0)
452 }
453
454 pub fn is_negative(&self) -> Depends<bool> {
455 self.value.map(|p| p.leading().is_some_and(Rational::is_negative))
456 }
457
458 pub fn eval_at(&self, h: u64) -> Option<BigInt> {
460 self.value.at(h).map(|f| eval_integer(f, h))
461 }
462
463 pub fn to_hyperreal(&self) -> Depends<Hyperreal> {
465 self.value.map(|p| Hyperreal::from_reciprocal_powers(p.coeffs()))
466 }
467
468 pub fn render(&self, generator: &str) -> String {
471 self.value.render(generator, |p| poly_text(p, generator))
472 }
473
474 pub fn render_reciprocal(&self, inf: &str) -> String {
476 let generator = format!("⌈1/{inf}⌉");
477 self.value.render(&generator, |p| reciprocal_text(p, inf))
478 }
479}
480
481impl crate::exact_codec::ExactCodec for Hypernatural {
482 fn write(&self, out: &mut Vec<u8>) {
484 self.value.write(out);
485 }
486
487 fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
491 Hypernatural::from_branches(crate::exact_codec::ExactCodec::read(buf, pos)?)
492 }
493}
494
495#[cfg(test)]
496#[path = "../../logicaffeine_tests/unit/logicaffeine_base/hypernatural/tests.rs"]
497mod tests;