use std::cmp::Ordering;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct W(pub [u64; 8]);
impl W {
pub const ZERO: W = W([0; 8]);
pub fn from_u64(v: u64) -> W {
let mut w = [0u64; 8];
w[0] = v;
W(w)
}
pub fn from_u128(v: u128) -> W {
let mut w = [0u64; 8];
w[0] = v as u64;
w[1] = (v >> 64) as u64;
W(w)
}
pub fn is_zero(&self) -> bool {
self.0.iter().all(|l| *l == 0)
}
pub fn bits(&self) -> u32 {
for i in (0..8).rev() {
if self.0[i] != 0 {
return 64 * i as u32 + 64 - self.0[i].leading_zeros();
}
}
0
}
pub fn to_u64(&self) -> Option<u64> {
self.0[1..].iter().all(|l| *l == 0).then_some(self.0[0])
}
pub fn checked_add(&self, o: &W) -> Option<W> {
let mut out = [0u64; 8];
let mut carry = 0u128;
for (i, slot) in out.iter_mut().enumerate() {
let s = self.0[i] as u128 + o.0[i] as u128 + carry;
*slot = s as u64;
carry = s >> 64;
}
(carry == 0).then_some(W(out))
}
fn sub_assign(&mut self, o: &W) {
let mut borrow = 0i128;
for i in 0..8 {
let d = self.0[i] as i128 - o.0[i] as i128 - borrow;
if d < 0 {
self.0[i] = (d + (1i128 << 64)) as u64;
borrow = 1;
} else {
self.0[i] = d as u64;
borrow = 0;
}
}
debug_assert_eq!(borrow, 0, "sub underflow");
}
pub fn checked_mul(&self, o: &W) -> Option<W> {
let mut out = [0u64; 16];
for i in 0..8 {
if self.0[i] == 0 {
continue;
}
let mut carry = 0u128;
for j in 0..8 {
let cur = out[i + j] as u128 + self.0[i] as u128 * o.0[j] as u128 + carry;
out[i + j] = cur as u64;
carry = cur >> 64;
}
let mut k = i + 8;
while carry > 0 {
let cur = out[k] as u128 + carry;
out[k] = cur as u64;
carry = cur >> 64;
k += 1;
}
}
if out[8..].iter().any(|l| *l != 0) {
return None;
}
let mut w = [0u64; 8];
w.copy_from_slice(&out[..8]);
Some(W(w))
}
fn bit(&self, i: u32) -> bool {
(self.0[(i / 64) as usize] >> (i % 64)) & 1 == 1
}
fn shl1_or(&mut self, b: bool) {
let mut carry = b as u64;
for l in self.0.iter_mut() {
let next = *l >> 63;
*l = (*l << 1) | carry;
carry = next;
}
}
pub fn div_u64(&self, d: u64) -> W {
assert!(d != 0, "division by zero");
let mut out = [0u64; 8];
let mut rem = 0u128;
for i in (0..8).rev() {
let cur = (rem << 64) | self.0[i] as u128;
out[i] = (cur / d as u128) as u64;
rem = cur % d as u128;
}
W(out)
}
pub fn div(&self, d: &W) -> W {
assert!(!d.is_zero(), "division by zero");
if let Some(small) = d.to_u64() {
return self.div_u64(small);
}
let mut q = W::ZERO;
let mut r = W::ZERO;
for i in (0..self.bits()).rev() {
r.shl1_or(self.bit(i));
q.shl1_or(false);
if r >= *d {
r.sub_assign(d);
q.0[0] |= 1;
}
}
q
}
}
impl Ord for W {
fn cmp(&self, o: &W) -> Ordering {
for i in (0..8).rev() {
match self.0[i].cmp(&o.0[i]) {
Ordering::Equal => continue,
other => return other,
}
}
Ordering::Equal
}
}
impl PartialOrd for W {
fn partial_cmp(&self, o: &W) -> Option<Ordering> {
Some(self.cmp(o))
}
}
pub fn mul_div(a: &W, b: &W, c: &W) -> W {
a.checked_mul(b).expect("attribution products fit 512 bits").div(c)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arithmetic_agrees_with_u128_where_it_fits() {
let cases: [(u128, u128, u128); 5] =
[(0, 5, 3), (7, 9, 4), (u64::MAX as u128, u64::MAX as u128, 3), (1 << 100, 1 << 20, (1 << 64) + 7), ((1 << 120) + 99, 255, 1 << 70)];
for (a, b, c) in cases {
let got = mul_div(&W::from_u128(a), &W::from_u128(b), &W::from_u128(c));
assert_eq!(got, W::from_u128(a * b / c), "{a} * {b} / {c}");
}
}
#[test]
fn wide_products_and_divisions() {
let mut big = W::ZERO;
big.0[3] = 1 << 63;
let got = mul_div(&big, &W::from_u64(u64::MAX), &big);
assert_eq!(got.to_u64(), Some(u64::MAX));
let mut a = W::ZERO;
a.0[3] = 1 << 8;
a.0[0] = 1;
let mut c = W::ZERO;
c.0[3] = 1 << 7;
assert_eq!(mul_div(&a, &W::from_u64(3), &c).to_u64(), Some(6));
assert_eq!(a.bits(), 201);
assert!(W::from_u64(1).checked_add(&W([u64::MAX; 8])).is_none());
assert!(W([u64::MAX; 8]).checked_mul(&W::from_u64(2)).is_none());
}
}