use std::fmt;
use rug::Rational;
use super::{parse_nonnegative, parse_positive, NumberTheoryError};
use crate::budget::BudgetTrip;
use crate::errors::AlkahestError;
use crate::flint::ffi;
use crate::flint::rational::FlintRational;
use crate::flint::FlintInteger;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ArithmeticError {
WorkLimitExceeded {
function: &'static str,
limit: u64,
},
Input(NumberTheoryError),
Resource(BudgetTrip),
}
impl fmt::Display for ArithmeticError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArithmeticError::WorkLimitExceeded { function, limit } => write!(
f,
"{function} refuses arguments above {limit} here; the value exists, \
this module just will not spend unbounded time computing it"
),
ArithmeticError::Input(e) => write!(f, "{e}"),
ArithmeticError::Resource(t) => write!(f, "{t}"),
}
}
}
impl std::error::Error for ArithmeticError {}
impl From<BudgetTrip> for ArithmeticError {
fn from(t: BudgetTrip) -> Self {
ArithmeticError::Resource(t)
}
}
impl From<NumberTheoryError> for ArithmeticError {
fn from(e: NumberTheoryError) -> Self {
ArithmeticError::Input(e)
}
}
impl AlkahestError for ArithmeticError {
fn code(&self) -> &'static str {
match self {
ArithmeticError::WorkLimitExceeded { .. } => "E-NT-006",
ArithmeticError::Input(e) => e.code(),
ArithmeticError::Resource(t) => t.code(),
}
}
fn remediation(&self) -> Option<&'static str> {
match self {
ArithmeticError::WorkLimitExceeded { .. } => {
Some("reduce the argument, or call FLINT directly if the wait is acceptable")
}
ArithmeticError::Input(e) => e.remediation(),
ArithmeticError::Resource(t) => t.remediation(),
}
}
}
pub const MAX_PARTITION_N: u64 = 1_000_000;
pub const MAX_BERNOULLI_N: u64 = 50_000;
pub const MAX_EULER_N: u64 = 20_000;
pub const MAX_HARMONIC_N: u64 = 1_000_000;
pub const MAX_STIRLING_N: u64 = 10_000;
pub const MAX_SUM_OF_SQUARES_RECURSIVE_N: u64 = 1_000_000_000_000;
pub const MAX_SUM_OF_SQUARES_SERIES_N: u64 = 100_000;
fn work_cap(function: &'static str, n: u64, limit: u64) -> Result<(), ArithmeticError> {
if n > limit {
Err(ArithmeticError::WorkLimitExceeded { function, limit })
} else {
Ok(())
}
}
pub fn partition_number(n: u64) -> Result<String, ArithmeticError> {
work_cap("partition_number", n, MAX_PARTITION_N)?;
let mut out = FlintInteger::new();
unsafe { ffi::arith_number_of_partitions(out.inner_mut_ptr(), n) };
Ok(out.to_string())
}
pub fn bernoulli_number(n: u64) -> Result<Rational, ArithmeticError> {
work_cap("bernoulli_number", n, MAX_BERNOULLI_N)?;
let mut out = FlintRational::new();
unsafe { ffi::arith_bernoulli_number(out.as_mut_ptr(), n) };
Ok(out.to_rug())
}
pub fn euler_number(n: u64) -> Result<String, ArithmeticError> {
work_cap("euler_number", n, MAX_EULER_N)?;
let mut out = FlintInteger::new();
unsafe { ffi::arith_euler_number(out.inner_mut_ptr(), n) };
Ok(out.to_string())
}
pub fn harmonic_number(n: u64) -> Result<Rational, ArithmeticError> {
work_cap("harmonic_number", n, MAX_HARMONIC_N)?;
let mut out = FlintRational::new();
unsafe { ffi::arith_harmonic_number(out.as_mut_ptr(), n as ffi::slong) };
Ok(out.to_rug())
}
pub fn stirling_first(n: u64, k: u64) -> Result<String, ArithmeticError> {
stirling(ffi::arith_stirling_number_1, "stirling_first", n, k)
}
pub fn stirling_first_unsigned(n: u64, k: u64) -> Result<String, ArithmeticError> {
stirling(
ffi::arith_stirling_number_1u,
"stirling_first_unsigned",
n,
k,
)
}
pub fn stirling_second(n: u64, k: u64) -> Result<String, ArithmeticError> {
stirling(ffi::arith_stirling_number_2, "stirling_second", n, k)
}
fn stirling(
f: unsafe extern "C" fn(*mut ffi::fmpz, ffi::slong, ffi::slong),
name: &'static str,
n: u64,
k: u64,
) -> Result<String, ArithmeticError> {
work_cap(name, n, MAX_STIRLING_N)?;
work_cap(name, k, MAX_STIRLING_N)?;
let mut out = FlintInteger::new();
unsafe { f(out.inner_mut_ptr(), n as ffi::slong, k as ffi::slong) };
Ok(out.to_string())
}
pub fn moebius_mu(n: &str) -> Result<i32, ArithmeticError> {
let z = parse_positive(n).map_err(ArithmeticError::Input)?;
let fz = FlintInteger::from_rug(&z);
Ok(unsafe { ffi::fmpz_moebius_mu(fz.inner_ptr()) } as i32)
}
pub fn divisor_sigma(k: u64, n: &str) -> Result<String, ArithmeticError> {
let z = parse_positive(n).map_err(ArithmeticError::Input)?;
if z == 1 {
return Ok("1".into());
}
let bits = k
.saturating_mul(2)
.saturating_mul(u64::from(z.significant_bits()));
crate::budget::preflight_bignum_bits(bits, 4)?;
let fz = FlintInteger::from_rug(&z);
let mut out = FlintInteger::new();
unsafe { ffi::fmpz_divisor_sigma(out.inner_mut_ptr(), k, fz.inner_ptr()) };
Ok(out.to_string())
}
pub fn sum_of_squares(k: u64, n: &str) -> Result<String, ArithmeticError> {
if k == 0 {
return Err(ArithmeticError::Input(NumberTheoryError::Domain {
msg: "the number of squares k must be at least 1",
}));
}
let z = parse_nonnegative(n).map_err(ArithmeticError::Input)?;
match k {
1 | 2 | 4 => {}
3 | 5 => {
let cap = MAX_SUM_OF_SQUARES_RECURSIVE_N;
if z > cap {
return Err(ArithmeticError::WorkLimitExceeded {
function: "sum_of_squares",
limit: cap,
});
}
}
_ => {
let cap = MAX_SUM_OF_SQUARES_SERIES_N;
if z > cap {
return Err(ArithmeticError::WorkLimitExceeded {
function: "sum_of_squares",
limit: cap,
});
}
let n_small = z.to_u64().unwrap_or(u64::MAX);
let k_bits = 64 - (k.saturating_mul(2)).leading_zeros() as u64 + 1;
let bits = n_small.saturating_mul(k_bits.saturating_add(1)).max(64);
crate::budget::preflight_bignum_bits(
bits,
n_small.saturating_add(1).saturating_mul(3),
)?;
}
}
let fz = FlintInteger::from_rug(&z);
let mut out = FlintInteger::new();
unsafe { ffi::arith_sum_of_squares(out.inner_mut_ptr(), k, fz.inner_ptr()) };
Ok(out.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::AlkahestError;
use crate::number_theory::{isprime, nextprime};
use rug::ops::Pow;
use rug::Integer;
use std::str::FromStr;
#[test]
fn wrapped_input_errors_delegate_their_code() {
let inner = moebius_mu_inner_error();
assert_eq!(inner.code(), "E-NT-002");
let wrapped: ArithmeticError = inner.clone().into();
assert_eq!(wrapped.code(), inner.code());
assert_eq!(wrapped.remediation(), inner.remediation());
assert_eq!(wrapped.to_string(), inner.to_string());
for e in [
moebius_mu("0").unwrap_err(),
moebius_mu("-5").unwrap_err(),
divisor_sigma(1, "0").unwrap_err(),
sum_of_squares(2, "-1").unwrap_err(),
sum_of_squares(0, "5").unwrap_err(),
] {
assert_eq!(e.code(), "E-NT-002", "{e}");
assert!(matches!(e, ArithmeticError::Input(_)));
}
assert_eq!(
moebius_mu("not a number").unwrap_err().code(),
"E-NT-001",
"a parse failure keeps its own code too"
);
let capped = partition_number(MAX_PARTITION_N + 1).unwrap_err();
assert_eq!(capped.code(), "E-NT-006");
assert!(matches!(capped, ArithmeticError::WorkLimitExceeded { .. }));
assert!(capped.remediation().is_some());
}
fn moebius_mu_inner_error() -> NumberTheoryError {
match moebius_mu("0").unwrap_err() {
ArithmeticError::Input(e) => e,
other => panic!("expected a wrapped input error, got {other:?}"),
}
}
#[test]
fn divisor_sigma_refuses_results_too_large_to_hold() {
for (k, n) in [
(1_000_000_000_000u64, "2"),
(1u64 << 40, "2"),
((1u64 << 32) + 1, "2"),
(u64::MAX, "12"),
] {
let e = divisor_sigma(k, n).unwrap_err();
assert!(matches!(e, ArithmeticError::Resource(_)), "{k} {n}: {e:?}");
assert!(e.code().starts_with("E-BUDGET-"), "{e}");
}
assert_eq!(divisor_sigma(u64::MAX, "1").unwrap(), "1");
assert_eq!(divisor_sigma(3, "2").unwrap(), "9");
assert_eq!(divisor_sigma(2, "1000000000000").unwrap().len(), 25);
}
#[test]
fn sum_of_squares_bounds_the_series_paths() {
let big = "18446744073709551617"; for k in [3u64, 5, 6, 7, 100] {
let e = sum_of_squares(k, big).unwrap_err();
assert_eq!(e.code(), "E-NT-006", "k={k}");
}
assert_eq!(
sum_of_squares(6, "1000000000000").unwrap_err().code(),
"E-NT-006"
);
assert_eq!(sum_of_squares(2, big).unwrap(), "16");
assert_eq!(sum_of_squares(1, big).unwrap(), "0");
assert_eq!(sum_of_squares(3, "5").unwrap(), "24");
assert_eq!(sum_of_squares(6, "1").unwrap(), "12");
assert_eq!(sum_of_squares(8, "1").unwrap(), "16");
assert!(sum_of_squares(1_000_000_000_000, "5").is_ok());
}
#[test]
fn partition_function_anchors() {
assert_eq!(partition_number(0).unwrap(), "1");
assert_eq!(partition_number(1).unwrap(), "1");
assert_eq!(partition_number(5).unwrap(), "7");
assert_eq!(partition_number(100).unwrap(), "190569292");
assert_eq!(
partition_number(1000).unwrap(),
"24061467864032622473692149727991"
);
}
#[test]
fn partition_function_refuses_past_the_cap() {
let e = partition_number(MAX_PARTITION_N + 1).unwrap_err();
assert_eq!(AlkahestError::code(&e), "E-NT-006");
}
#[test]
fn bernoulli_convention_is_b1_minus_one_half() {
assert_eq!(bernoulli_number(1).unwrap(), Rational::from((-1, 2)));
}
#[test]
fn bernoulli_anchors() {
assert_eq!(bernoulli_number(0).unwrap(), Rational::from(1));
assert_eq!(bernoulli_number(2).unwrap(), Rational::from((1, 6)));
assert_eq!(bernoulli_number(4).unwrap(), Rational::from((-1, 30)));
assert_eq!(bernoulli_number(6).unwrap(), Rational::from((1, 42)));
assert_eq!(bernoulli_number(10).unwrap(), Rational::from((5, 66)));
assert_eq!(bernoulli_number(12).unwrap(), Rational::from((-691, 2730)));
for n in (3..=21).step_by(2) {
assert_eq!(
bernoulli_number(n).unwrap(),
Rational::new(),
"B_{n} should vanish"
);
}
}
#[test]
fn bernoulli_denominators_satisfy_von_staudt_clausen() {
for n in 1..=20u64 {
let two_n = 2 * n;
let mut expected = Integer::from(1);
let mut p = Integer::from(2);
while p <= two_n + 1 {
if isprime(&p.to_string()).unwrap()
&& two_n % (p.clone() - 1u32).to_u64().unwrap() == 0
{
expected *= &p;
}
p = Integer::from_str(&nextprime(&p.to_string(), true).unwrap()).unwrap();
}
assert_eq!(
*bernoulli_number(two_n).unwrap().denom(),
expected,
"denominator of B_{two_n}"
);
}
}
#[test]
fn euler_number_anchors() {
assert_eq!(euler_number(0).unwrap(), "1");
assert_eq!(euler_number(2).unwrap(), "-1");
assert_eq!(euler_number(4).unwrap(), "5");
assert_eq!(euler_number(6).unwrap(), "-61");
assert_eq!(euler_number(8).unwrap(), "1385");
assert_eq!(euler_number(10).unwrap(), "-50521");
for n in (1..=11).step_by(2) {
assert_eq!(euler_number(n).unwrap(), "0", "E_{n} should vanish");
}
}
#[test]
fn harmonic_number_anchors() {
assert_eq!(harmonic_number(0).unwrap(), Rational::new());
assert_eq!(harmonic_number(1).unwrap(), Rational::from(1));
assert_eq!(harmonic_number(4).unwrap(), Rational::from((25, 12)));
assert_eq!(harmonic_number(5).unwrap(), Rational::from((137, 60)));
for n in 1..=40u64 {
let d = harmonic_number(n).unwrap() - harmonic_number(n - 1).unwrap();
assert_eq!(d, Rational::from((1, n as i64)), "H_{n} step");
}
}
#[test]
fn stirling_anchors() {
assert_eq!(stirling_first(4, 2).unwrap(), "11");
assert_eq!(stirling_first_unsigned(4, 2).unwrap(), "11");
assert_eq!(stirling_first(3, 2).unwrap(), "-3");
assert_eq!(stirling_first_unsigned(3, 2).unwrap(), "3");
assert_eq!(stirling_first(0, 0).unwrap(), "1");
assert_eq!(stirling_first(5, 6).unwrap(), "0");
assert_eq!(stirling_second(4, 2).unwrap(), "7");
assert_eq!(stirling_second(5, 3).unwrap(), "25");
for n in 1..=12u64 {
assert_eq!(stirling_second(n, 1).unwrap(), "1", "S({n},1)");
assert_eq!(stirling_second(n, n).unwrap(), "1", "S({n},{n})");
assert_eq!(stirling_second(n, n + 1).unwrap(), "0");
}
}
#[test]
fn stirling_second_sums_to_bell_numbers() {
let bell = [
"1", "1", "2", "5", "15", "52", "203", "877", "4140", "21147",
];
for (n, want) in bell.iter().enumerate().skip(1) {
let mut total = Integer::new();
for k in 1..=n as u64 {
total += Integer::from_str(&stirling_second(n as u64, k).unwrap()).unwrap();
}
assert_eq!(total.to_string(), *want, "Bell({n})");
}
}
#[test]
fn stirling_first_expands_the_falling_factorial() {
for n in 1..=8u64 {
for x in -3i64..=6 {
let mut lhs = Integer::from(1);
for i in 0..n as i64 {
lhs *= Integer::from(x - i);
}
let mut rhs = Integer::new();
for k in 0..=n {
let s = Integer::from_str(&stirling_first(n, k).unwrap()).unwrap();
rhs += s * Integer::from(x).pow(k as u32);
}
assert_eq!(lhs, rhs, "falling factorial n={n}, x={x}");
}
}
}
#[test]
fn moebius_anchors() {
assert_eq!(moebius_mu("1").unwrap(), 1);
assert_eq!(moebius_mu("2").unwrap(), -1);
assert_eq!(moebius_mu("6").unwrap(), 1);
assert_eq!(moebius_mu("12").unwrap(), 0);
assert_eq!(moebius_mu("30").unwrap(), -1);
assert!(moebius_mu("0").is_err());
assert!(moebius_mu("-5").is_err());
for n in 1..=60u64 {
let mut total = 0i32;
for d in 1..=n {
if n % d == 0 {
total += moebius_mu(&d.to_string()).unwrap();
}
}
assert_eq!(total, i32::from(n == 1), "Mobius sum at {n}");
}
}
#[test]
fn divisor_sigma_anchors() {
assert_eq!(divisor_sigma(0, "12").unwrap(), "6");
assert_eq!(divisor_sigma(1, "12").unwrap(), "28");
assert_eq!(divisor_sigma(2, "12").unwrap(), "210");
assert_eq!(divisor_sigma(1, "6").unwrap(), "12", "6 is perfect");
assert_eq!(divisor_sigma(1, "1").unwrap(), "1");
assert!(divisor_sigma(1, "0").is_err());
for n in 1..=50u64 {
for k in 0..=3u32 {
let want: u64 = (1..=n).filter(|d| n % d == 0).map(|d| d.pow(k)).sum();
assert_eq!(
divisor_sigma(u64::from(k), &n.to_string()).unwrap(),
want.to_string(),
"sigma_{k}({n})"
);
}
}
}
#[test]
fn sum_of_squares_anchors() {
assert_eq!(sum_of_squares(2, "0").unwrap(), "1");
assert_eq!(sum_of_squares(2, "5").unwrap(), "8");
assert_eq!(sum_of_squares(4, "1").unwrap(), "8");
assert!(sum_of_squares(0, "5").is_err());
assert!(sum_of_squares(2, "-1").is_err());
for n in 1..=40u64 {
let want: u64 = 8 * (1..=n).filter(|d| n % d == 0 && d % 4 != 0).sum::<u64>();
assert_eq!(
sum_of_squares(4, &n.to_string()).unwrap(),
want.to_string(),
"r_4({n})"
);
}
for n in 0..=25i64 {
let mut count = 0u64;
for a in -6i64..=6 {
for b in -6i64..=6 {
if a * a + b * b == n {
count += 1;
}
}
}
assert_eq!(
sum_of_squares(2, &n.to_string()).unwrap(),
count.to_string(),
"r_2({n})"
);
}
}
#[test]
fn work_caps_refuse_rather_than_hang() {
for e in [
bernoulli_number(MAX_BERNOULLI_N + 1).unwrap_err(),
euler_number(MAX_EULER_N + 1).unwrap_err(),
harmonic_number(MAX_HARMONIC_N + 1).unwrap_err(),
stirling_first(MAX_STIRLING_N + 1, 2).unwrap_err(),
stirling_second(2, MAX_STIRLING_N + 1).unwrap_err(),
] {
assert_eq!(AlkahestError::code(&e), "E-NT-006");
assert!(AlkahestError::remediation(&e).is_some());
assert!(!e.to_string().is_empty());
}
}
}