libm: Add tests against rug for i256

This commit is contained in:
Trevor Gross
2026-03-22 15:12:30 -05:00
parent 06ea8566a1
commit a6de51188b
3 changed files with 278 additions and 44 deletions
+261 -24
View File
@@ -5,11 +5,11 @@
use std::sync::LazyLock;
use libm::support::{HInt, u256};
use libm::support::{HInt, i256, u256};
type BigInt = rug::Integer;
use libm_test::bigint_fuzz_iteration_count;
use libm_test::generate::random::SEED;
use libm_test::{MinInt, bigint_fuzz_iteration_count};
use rand::{RngExt, SeedableRng};
use rand_chacha::ChaCha8Rng;
use rug::Assign;
@@ -25,27 +25,56 @@ fn random_u256(rng: &mut ChaCha8Rng) -> u256 {
u256 { lo, hi }
}
fn assign_bigint(bx: &mut BigInt, x: u256) {
bx.assign_digits(&[x.lo, x.hi], Order::Lsf);
fn random_i256(rng: &mut ChaCha8Rng) -> i256 {
random_u256(rng).signed()
}
fn from_bigint(bx: &mut BigInt) -> u256 {
fn assign_bigint_u256(bx: &mut BigInt, x: u256) {
bx.assign(x.hi);
*bx <<= 128;
*bx += x.lo;
}
fn assign_bigint_i256(bx: &mut BigInt, x: i256) {
bx.assign(x.hi);
*bx <<= 128;
*bx += x.lo;
}
/// Note that this destroys the result in `bx`.
fn from_bigint_u256(bx: &mut BigInt) -> u256 {
// Truncate so the result fits into `[u128; 2]`. This makes all ops overflowing.
*bx &= &*BIGINT_U256_MAX;
let mut bres = [0u128, 0];
bx.write_digits(&mut bres, Order::Lsf);
bx.assign(0);
bx.assign(0); // prevent accidental reuse
u256 {
lo: bres[0],
hi: bres[1],
}
}
fn check_one(msg: impl Fn() -> String, actual: u256, expected: &mut BigInt) {
let expected = from_bigint(expected);
/// Note that this destroys the result in `bx`.
fn from_bigint_i256(bx: &mut BigInt) -> i256 {
// Truncate so the result fits into `[u128; 2]`. This makes all ops overflowing.
*bx &= &*BIGINT_U256_MAX;
let lo = bx.to_u128_wrapping();
*bx >>= 128;
let hi = bx.to_i128_wrapping();
bx.assign(0); // prevent accidental reuse
i256 { hi, lo }
}
#[track_caller]
fn assert_same_u256(msg: impl Fn() -> String, actual: u256, expected_big: &mut BigInt) {
let expected = from_bigint_u256(expected_big);
if actual != expected {
let mut act_big = BigInt::new();
assign_bigint_u256(&mut act_big, actual);
panic!(
"Test failure: {}\n\
actual: {act_big}\n\
expected: {expected_big}\n\
actual: {actual:#x}\n\
expected: {expected:#x}\
",
@@ -54,6 +83,98 @@ fn check_one(msg: impl Fn() -> String, actual: u256, expected: &mut BigInt) {
}
}
#[track_caller]
fn assert_same_i256(msg: impl Fn() -> String, actual: i256, expected_big: &mut BigInt) {
let expected = from_bigint_i256(expected_big);
if actual != expected {
let mut act_big = BigInt::new();
assign_bigint_i256(&mut act_big, actual);
panic!(
"Test failure: {}\n\
actual: {act_big}\n\
expected: {expected_big}\n\
actual: {actual:#x}\n\
expected: {expected:#x}\
",
msg()
)
}
}
/// Verify the test setup.
#[test]
fn mp_u256_roundtrip() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_u256(&mut rng);
assign_bigint_u256(&mut bx, x);
assert_eq!(from_bigint_u256(&mut bx), x);
}
// Check wraparound
assign_bigint_u256(&mut bx, u256::MAX);
bx += 1;
assert_eq!(from_bigint_u256(&mut bx), u256::MIN);
assign_bigint_u256(&mut bx, u256::MIN);
bx -= 1;
assert_eq!(from_bigint_u256(&mut bx), u256::MAX);
}
/// Verify the test setup.
#[test]
fn mp_i256_roundtrip() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_i256(&mut rng);
assign_bigint_i256(&mut bx, x);
assert_eq!(from_bigint_i256(&mut bx), x);
}
// Check wraparound
assign_bigint_i256(&mut bx, i256::MAX);
bx += 1;
assert_eq!(from_bigint_i256(&mut bx), i256::MIN);
assign_bigint_i256(&mut bx, i256::MIN);
bx -= 1;
assert_eq!(from_bigint_i256(&mut bx), i256::MAX);
}
#[test]
fn mp_u256_ord() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
let mut by = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_u256(&mut rng);
let y = random_u256(&mut rng);
assign_bigint_u256(&mut bx, x);
assign_bigint_u256(&mut by, y);
assert_eq!(x.cmp(&y), bx.cmp(&by), "cmp({x:#x}, {y:#x})");
}
}
#[test]
fn mp_i256_ord() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
let mut by = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_i256(&mut rng);
let y = random_i256(&mut rng);
assign_bigint_i256(&mut bx, x);
assign_bigint_i256(&mut by, y);
assert_eq!(x.cmp(&y), bx.cmp(&by), "cmp({x:#x}, {y:#x})");
}
}
#[test]
fn mp_u256_bitor() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
@@ -63,11 +184,28 @@ fn mp_u256_bitor() {
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_u256(&mut rng);
let y = random_u256(&mut rng);
assign_bigint(&mut bx, x);
assign_bigint(&mut by, y);
assign_bigint_u256(&mut bx, x);
assign_bigint_u256(&mut by, y);
let actual = x | y;
bx |= &by;
check_one(|| format!("{x:#x} ^ {y:#x}"), actual, &mut bx);
assert_same_u256(|| format!("{x:#x} ^ {y:#x}"), actual, &mut bx);
}
}
#[test]
fn mp_i256_bitor() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
let mut by = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_i256(&mut rng);
let y = random_i256(&mut rng);
assign_bigint_i256(&mut bx, x);
assign_bigint_i256(&mut by, y);
let actual = x | y;
bx |= &by;
assert_same_i256(|| format!("{x:#x} ^ {y:#x}"), actual, &mut bx);
}
}
@@ -78,10 +216,24 @@ fn mp_u256_not() {
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_u256(&mut rng);
assign_bigint(&mut bx, x);
assign_bigint_u256(&mut bx, x);
let actual = !x;
bx.not_assign();
check_one(|| format!("!{x:#x}"), actual, &mut bx);
assert_same_u256(|| format!("!{x:#x}"), actual, &mut bx);
}
}
#[test]
fn mp_i256_not() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_i256(&mut rng);
assign_bigint_i256(&mut bx, x);
let actual = !x;
bx.not_assign();
assert_same_i256(|| format!("!{x:#x}"), actual, &mut bx);
}
}
@@ -94,8 +246,9 @@ fn mp_u256_add() {
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_u256(&mut rng);
let y = random_u256(&mut rng);
assign_bigint(&mut bx, x);
assign_bigint(&mut by, y);
assign_bigint_u256(&mut bx, x);
assign_bigint_u256(&mut by, y);
// Emulate wrapping semantics with panicking ops
let actual = if u256::MAX - x >= y {
x + y
} else {
@@ -104,7 +257,35 @@ fn mp_u256_add() {
y - (u256::MAX - x) - 1_u128.widen()
};
bx += &by;
check_one(|| format!("{x:#x} + {y:#x}"), actual, &mut bx);
assert_same_u256(|| format!("{x:#x} + {y:#x}"), actual, &mut bx);
}
}
#[test]
fn mp_i256_add() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
let mut by = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_i256(&mut rng);
let y = random_i256(&mut rng);
assign_bigint_i256(&mut bx, x);
assign_bigint_i256(&mut by, y);
// Emulate wrapping semantics with panicking ops
let actual = if x > i256::ZERO && y > i256::MAX - x {
// Overflow condition
(x + i256::MIN) + (y + i256::MIN)
} else if x < i256::ZERO && y < i256::MIN - x {
// Underflow condition
(x - i256::MIN) + (y - i256::MIN)
} else {
// Otherwise there is no overflow
x + y
};
bx += &by;
assert_same_i256(|| format!("{x:#x} + {y:#x}"), actual, &mut bx);
}
}
@@ -117,15 +298,41 @@ fn mp_u256_sub() {
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_u256(&mut rng);
let y = random_u256(&mut rng);
assign_bigint(&mut bx, x);
assign_bigint(&mut by, y);
assign_bigint_u256(&mut bx, x);
assign_bigint_u256(&mut by, y);
// since the operators (may) panic on overflow,
// we should test something that doesn't
let actual = if x >= y { x - y } else { y - x };
bx -= &by;
bx.abs_mut();
check_one(|| format!("{x:#x} - {y:#x}"), actual, &mut bx);
assert_same_u256(|| format!("{x:#x} - {y:#x}"), actual, &mut bx);
}
}
#[test]
fn mp_i256_sub() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
let mut by = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_i256(&mut rng);
let y = random_i256(&mut rng);
assign_bigint_i256(&mut bx, x);
assign_bigint_i256(&mut by, y);
dbg!(&bx, &by);
// Emulate wrapping semantics with panicking ops
let actual = if y > i256::ZERO && x < i256::MIN + y {
(x - i256::MIN) - (y + i256::MIN)
} else if y < i256::ZERO && x > i256::MAX + y {
(x + i256::MIN) - (y - i256::MIN)
} else {
x - y
};
bx -= &by;
assert_same_i256(|| format!("{x:#x} - {y:#x}"), actual, &mut bx);
}
}
@@ -137,10 +344,25 @@ fn mp_u256_shl() {
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_u256(&mut rng);
let shift: u32 = rng.random_range(0..256);
assign_bigint(&mut bx, x);
assign_bigint_u256(&mut bx, x);
let actual = x << shift;
bx <<= shift;
check_one(|| format!("{x:#x} << {shift}"), actual, &mut bx);
assert_same_u256(|| format!("{x:#x} << {shift}"), actual, &mut bx);
}
}
#[test]
fn mp_i256_shl() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_i256(&mut rng);
let shift: u32 = rng.random_range(0..256);
assign_bigint_i256(&mut bx, x);
let actual = x << shift;
bx <<= shift;
assert_same_i256(|| format!("{x:#x} << {shift}"), actual, &mut bx);
}
}
@@ -152,10 +374,25 @@ fn mp_u256_shr() {
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_u256(&mut rng);
let shift: u32 = rng.random_range(0..256);
assign_bigint(&mut bx, x);
assign_bigint_u256(&mut bx, x);
let actual = x >> shift;
bx >>= shift;
check_one(|| format!("{x:#x} >> {shift}"), actual, &mut bx);
assert_same_u256(|| format!("{x:#x} >> {shift}"), actual, &mut bx);
}
}
#[test]
fn mp_i256_shr() {
let mut rng = ChaCha8Rng::from_seed(*SEED);
let mut bx = BigInt::new();
for _ in 0..bigint_fuzz_iteration_count() {
let x = random_i256(&mut rng);
let shift: u32 = rng.random_range(0..256);
assign_bigint_i256(&mut bx, x);
let actual = x >> shift;
bx >>= shift;
assert_same_i256(|| format!("{x:#x} >> {shift}"), actual, &mut bx);
}
}
@@ -172,7 +409,7 @@ fn mp_u256_u128_widen_mul() {
by.assign(y);
let actual = x.widen_mul(y);
bx *= &by;
check_one(
assert_same_u256(
|| format!("{x:#034x}.widen_mul({y:#034x})"),
actual,
&mut bx,
@@ -18,11 +18,11 @@ pub struct u256 {
}
impl u256 {
#[cfg(any(test, feature = "unstable-public-internals"))]
pub const MAX: Self = Self {
lo: u128::MAX,
hi: u128::MAX,
};
pub const MIN: Self = Self { lo: 0, hi: 0 };
/// Reinterpret as a signed integer
pub fn signed(self) -> i256 {
@@ -42,6 +42,15 @@ pub struct i256 {
}
impl i256 {
pub const MAX: Self = Self {
lo: u128::MAX,
hi: i128::MAX,
};
pub const MIN: Self = Self {
lo: u128::MIN,
hi: i128::MIN,
};
/// Reinterpret as an unsigned integer
pub fn unsigned(self) -> u256 {
u256 {
@@ -60,11 +69,8 @@ impl MinInt for u256 {
const BITS: u32 = 256;
const ZERO: Self = Self { lo: 0, hi: 0 };
const ONE: Self = Self { lo: 1, hi: 0 };
const MIN: Self = Self { lo: 0, hi: 0 };
const MAX: Self = Self {
lo: u128::MAX,
hi: u128::MAX,
};
const MIN: Self = Self::MIN;
const MAX: Self = Self::MAX;
}
impl MinInt for i256 {
@@ -76,14 +82,8 @@ impl MinInt for i256 {
const BITS: u32 = 256;
const ZERO: Self = Self { lo: 0, hi: 0 };
const ONE: Self = Self { lo: 1, hi: 0 };
const MIN: Self = Self {
lo: u128::MIN,
hi: i128::MIN,
};
const MAX: Self = Self {
lo: u128::MAX,
hi: i128::MAX,
};
const MIN: Self = Self::MIN;
const MAX: Self = Self::MAX;
}
macro_rules! impl_common {
@@ -113,13 +113,10 @@ fn shl_u256() {
has_errors = true;
eprintln!(
"\
FAILURE: {} << {b}\n\
expected: {}\n\
actual: {}\
FAILURE: {a:#x} << {b}\n\
expected: {expected:#x}\n\
actual: {actual:#x}\
",
hexu(a),
hexu(expected),
hexu(actual),
);
};