1#![forbid(unsafe_code)]
14
15use std::fmt;
16
17pub mod random;
18
19pub const UNITY: i32 = 0x1_0000;
21pub const TWO: i32 = 0x2_0000;
23pub const MAX_DIMEN: i32 = 0o7777777777;
25pub const INFINITY: i32 = 0o17777777777;
27pub const INF_BAD: i32 = 10000;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct Overflow;
33
34impl fmt::Display for Overflow {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.write_str("arithmetic overflow")
37 }
38}
39
40impl std::error::Error for Overflow {}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
44pub struct Scaled(pub i32);
45
46impl Scaled {
47 pub const ZERO: Scaled = Scaled(0);
49 pub const MAX: Scaled = Scaled(MAX_DIMEN);
51
52 pub fn to_tex_string(self) -> String {
55 let mut out = String::new();
56 print_scaled(&mut out, self.0);
57 out
58 }
59}
60
61impl fmt::Display for Scaled {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 let mut s = String::new();
64 print_scaled(&mut s, self.0);
65 f.write_str(&s)
66 }
67}
68
69pub fn half(x: i32) -> i32 {
71 if x & 1 != 0 {
72 (x + 1) / 2
74 } else {
75 x / 2
76 }
77}
78
79pub fn round_decimals(digits: &[u8]) -> i32 {
83 let mut a: i32 = 0;
84 for &d in digits.iter().rev() {
85 a = (a + i32::from(d) * TWO) / 10;
86 }
87 (a + 1) / 2
88}
89
90pub fn print_scaled(out: &mut String, s: i32) {
94 if s == i32::MIN {
95 out.push_str("--32768.0");
99 return;
100 }
101 let mut s = i64::from(s);
102 if s < 0 {
103 out.push('-');
104 s = -s;
105 }
106 let unity = i64::from(UNITY);
107 out.push_str(&(s / unity).to_string());
108 out.push('.');
109 s = 10 * (s % unity) + 5;
110 let mut delta: i64 = 10;
111 loop {
112 if delta > unity {
113 s += 0o100000 - 50000;
115 }
116 let digit = (s / unity) as u8; out.push(char::from(b'0' + digit));
118 s = 10 * (s % unity);
119 delta *= 10;
120 if s <= delta {
121 break;
122 }
123 }
124}
125
126pub fn mult_and_add(mut n: i32, mut x: i32, y: i32, max_answer: i32) -> Result<i32, Overflow> {
129 if n < 0 {
130 x = x.wrapping_neg();
131 n = -n;
132 }
133 if n == 0 {
134 return Ok(y);
135 }
136 let (n64, x64, y64, m) = (
138 i64::from(n),
139 i64::from(x),
140 i64::from(y),
141 i64::from(max_answer),
142 );
143 if x64 <= (m - y64) / n64 && -x64 <= (m + y64) / n64 {
144 Ok((n64 * x64 + y64) as i32)
145 } else {
146 Err(Overflow)
147 }
148}
149
150pub fn nx_plus_y(n: i32, x: i32, y: i32) -> Result<i32, Overflow> {
153 mult_and_add(n, x, y, 0o7777777777)
154}
155
156pub fn mult_integers(n: i32, x: i32) -> Result<i32, Overflow> {
158 mult_and_add(n, x, 0, 0o17777777777)
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub struct DivResult {
164 pub quotient: i32,
166 pub remainder: i32,
168}
169
170pub fn x_over_n(x: i32, n: i32) -> Result<DivResult, Overflow> {
174 if n == 0 {
175 return Err(Overflow);
176 }
177 Ok(x_over_n_unchecked(x, n))
178}
179
180pub fn x_over_n_unchecked(mut x: i32, mut n: i32) -> DivResult {
183 if n == 0 {
184 return DivResult {
185 quotient: 0,
186 remainder: x,
187 };
188 }
189 let mut negative = false;
190 if n < 0 {
191 x = x.wrapping_neg();
192 n = n.wrapping_neg();
193 negative = true;
194 }
195 let (q, mut r) = if x >= 0 {
196 (x / n, x % n)
197 } else {
198 let nx = x.wrapping_neg();
200 (-(nx / n), -(nx % n))
201 };
202 if negative {
203 r = -r;
204 }
205 DivResult {
206 quotient: q,
207 remainder: r,
208 }
209}
210
211pub fn xn_over_d(x: i32, n: i32, d: i32) -> Result<DivResult, Overflow> {
217 if d == 0 {
218 return Err(Overflow);
219 }
220 const B: i64 = 0o100000; let positive = x >= 0;
222 let x = i64::from(x).abs();
223 let (n, d) = (i64::from(n), i64::from(d));
224 let t = (x % B) * n;
225 let mut u = (x / B) * n + (t / B);
226 let v = (u % d) * B + (t % B);
227 if u / d >= B {
228 return Err(Overflow);
229 }
230 u = B * (u / d) + (v / d);
231 let (q, r) = (u as i32, (v % d) as i32);
232 Ok(if positive {
233 DivResult {
234 quotient: q,
235 remainder: r,
236 }
237 } else {
238 DivResult {
239 quotient: -q,
240 remainder: -r,
241 }
242 })
243}
244
245pub fn badness(t: i32, s: i32) -> i32 {
249 if t == 0 {
250 return 0;
251 }
252 if s <= 0 {
253 return INF_BAD;
254 }
255 let r: i32 = if t <= 7230584 {
256 (t * 297) / s
257 } else if s >= 1663497 {
258 t / (s / 297)
259 } else {
260 t
261 };
262 if r > 1290 {
263 INF_BAD
264 } else {
265 (r * r * r + 0o400000) / 0o1000000
266 }
267}
268
269pub fn add_or_sub(x: i32, mut y: i32, max_answer: i32, negative: bool) -> Result<i32, Overflow> {
272 if negative {
273 y = y.wrapping_neg();
274 }
275 if x >= 0 {
276 if y <= max_answer - x {
277 Ok(x + y)
278 } else {
279 Err(Overflow)
280 }
281 } else if y >= -max_answer - x {
282 Ok(x + y)
283 } else {
284 Err(Overflow)
285 }
286}
287
288pub fn quotient(mut n: i32, mut d: i32) -> Result<i32, Overflow> {
291 if d == 0 {
292 return Err(Overflow);
293 }
294 let mut negative = if d > 0 {
295 false
296 } else {
297 d = -d;
298 true
299 };
300 if n < 0 {
301 n = n.wrapping_neg();
302 negative = !negative;
303 }
304 let mut a = n / d;
305 n -= a * d;
306 d = n - d;
307 if d + n >= 0 {
308 a += 1;
309 }
310 Ok(if negative { -a } else { a })
311}
312
313pub fn fract(mut x: i32, mut n: i32, mut d: i32, max_answer: i32) -> Result<i32, Overflow> {
317 if d == 0 {
318 return Err(Overflow);
319 }
320 let mut a: i32 = 0;
321 let mut negative = if d > 0 {
322 false
323 } else {
324 d = -d;
325 true
326 };
327 if x < 0 {
328 x = x.wrapping_neg();
329 negative = !negative;
330 } else if x == 0 {
331 return Ok(0);
332 }
333 if n < 0 {
334 n = n.wrapping_neg();
335 negative = !negative;
336 }
337 let finish = |a: i32, negative: bool| if negative { -a } else { a };
338 let mut t = n / d;
339 if t > max_answer / x {
340 return Err(Overflow);
341 }
342 a = a.wrapping_add(t * x);
343 n -= t * d;
344 if n == 0 {
345 return Ok(finish(a, negative));
346 }
347 t = x / d;
348 if t > (max_answer - a) / n {
349 return Err(Overflow);
350 }
351 a += t * n;
352 x -= t * d;
353 if x == 0 {
354 return Ok(finish(a, negative));
355 }
356 if x < n {
357 std::mem::swap(&mut x, &mut n);
358 }
359 let mut f: i32 = 0;
361 let mut r: i32 = (d / 2) - d;
362 let h = -r;
363 loop {
364 if n & 1 != 0 {
365 r += x;
366 if r >= 0 {
367 r -= d;
368 f += 1;
369 }
370 }
371 n /= 2;
372 if n == 0 {
373 break;
374 }
375 if x < h {
376 x += x;
377 } else {
378 t = x - d;
379 x += t;
380 f += n;
381 if x < n {
382 if x == 0 {
383 break;
384 }
385 std::mem::swap(&mut x, &mut n);
386 }
387 }
388 }
389 if f > max_answer - a {
390 return Err(Overflow);
391 }
392 a += f;
393 Ok(finish(a, negative))
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399 use proptest::prelude::*;
400
401 #[test]
402 fn print_scaled_examples() {
403 let cases = [
404 (0, "0.0"),
405 (UNITY, "1.0"),
406 (-UNITY, "-1.0"),
407 (UNITY / 2, "0.5"),
408 (1, "0.00002"),
409 (MAX_DIMEN, "16383.99998"),
410 (655360, "10.0"),
411 (26214, "0.4"),
412 (32768 + 1, "0.50002"),
413 (i32::MIN, "--32768.0"),
415 ];
416 for (v, s) in cases {
417 assert_eq!(Scaled(v).to_tex_string(), s, "value {v}");
418 }
419 }
420
421 #[test]
422 fn xn_over_d_by_zero_overflows() {
423 assert!(xn_over_d(UNITY, 1000, 0).is_err());
424 assert!(xn_over_d(-UNITY, 0, 0).is_err());
425 }
426
427 #[test]
428 fn round_decimals_examples() {
429 assert_eq!(round_decimals(&[5]), 32768);
430 assert_eq!(round_decimals(&[2, 5]), 16384);
431 assert_eq!(round_decimals(&[]), 0);
432 assert_eq!(round_decimals(&[9, 9, 9, 9, 9, 9]), UNITY);
433 }
434
435 #[test]
436 fn half_rounds_up_on_odd() {
437 assert_eq!(half(3), 2);
438 assert_eq!(half(-3), -1);
439 assert_eq!(half(4), 2);
440 assert_eq!(half(-4), -2);
441 }
442
443 #[test]
444 fn badness_examples() {
445 assert_eq!(badness(0, 0), 0);
446 assert_eq!(badness(1, 0), INF_BAD);
447 assert_eq!(badness(UNITY, UNITY), 100);
448 assert_eq!(badness(UNITY, 2 * UNITY), 12);
449 assert_eq!(badness(3 * UNITY, UNITY), 2698);
450 assert_eq!(badness(5 * UNITY, UNITY), INF_BAD);
451 }
452
453 #[test]
454 fn overflow_cases() {
455 assert_eq!(nx_plus_y(2, MAX_DIMEN, 0), Err(Overflow));
456 assert_eq!(x_over_n(5, 0), Err(Overflow));
457 assert_eq!(quotient(5, 0), Err(Overflow));
458 assert_eq!(fract(5, 1, 0, INFINITY), Err(Overflow));
459 assert_eq!(add_or_sub(INFINITY, 1, INFINITY, false), Err(Overflow));
460 }
461
462 proptest! {
463 #[test]
466 fn print_scaled_round_trips(v in -MAX_DIMEN..=MAX_DIMEN) {
467 let s = Scaled(v).to_tex_string();
468 let (neg, body) = match s.strip_prefix('-') {
469 Some(b) => (true, b),
470 None => (false, s.as_str()),
471 };
472 let (int, frac) = body.split_once('.').unwrap();
473 let digits: Vec<u8> = frac.bytes().map(|b| b - b'0').collect();
474 let back = int.parse::<i32>().unwrap() * UNITY + round_decimals(&digits);
475 prop_assert_eq!(if neg { -back } else { back }, v);
476 }
477
478 #[test]
479 fn x_over_n_truncates(x in -MAX_DIMEN..=MAX_DIMEN, n in -100_000i32..100_000) {
480 prop_assume!(n != 0);
481 let r = x_over_n(x, n).unwrap();
482 prop_assert_eq!(r.quotient, x / n);
483 prop_assert_eq!(r.remainder, x % n);
485 }
486
487 #[test]
488 fn xn_over_d_matches_wide(x in -MAX_DIMEN..=MAX_DIMEN, n in 0i32..=0x10000, d in 1i32..=0x10000) {
489 let wide = i64::from(x) * i64::from(n);
490 let q = wide / i64::from(d);
491 match xn_over_d(x, n, d) {
492 Ok(r) => {
493 prop_assert_eq!(i64::from(r.quotient), q);
494 prop_assert_eq!(i64::from(r.remainder), wide % i64::from(d));
495 }
496 Err(Overflow) => prop_assert!(q.abs() >= 1 << 30),
497 }
498 }
499
500 #[test]
501 fn fract_matches_wide(x in -INFINITY..=INFINITY, n in -INFINITY..=INFINITY, d in -INFINITY..=INFINITY) {
502 prop_assume!(d != 0);
503 let (ax, an, ad) = (i128::from(x).abs(), i128::from(n).abs(), i128::from(d).abs());
504 let q = (2 * ax * an + ad) / (2 * ad);
505 let neg = (x < 0) ^ (n < 0) ^ (d < 0);
506 match fract(x, n, d, INFINITY) {
507 Ok(v) => prop_assert_eq!(i128::from(v), if neg && x != 0 { -q } else { q }),
508 Err(Overflow) => prop_assert!(q > i128::from(INFINITY)),
509 }
510 }
511
512 #[test]
513 fn quotient_rounds_half_away(n in -INFINITY..=INFINITY, d in -INFINITY..=INFINITY) {
514 prop_assume!(d != 0);
515 let (an, ad) = (i64::from(n).abs(), i64::from(d).abs());
516 let q = (2 * an + ad) / (2 * ad);
517 let neg = (n < 0) ^ (d < 0);
518 prop_assert_eq!(i64::from(quotient(n, d).unwrap()), if neg { -q } else { q });
519 }
520 }
521}