#![cfg(all(
not(feature = "fast"),
not(any(
feature = "rounding-half-away-from-zero",
feature = "rounding-half-toward-zero",
feature = "rounding-trunc",
feature = "rounding-floor",
feature = "rounding-ceiling",
)),
))]
use decimal_scaled::{D38, DecimalConstants};
use proptest::prelude::*;
type D = D38<19>;
type Bits = i128;
const CASES: u32 = 100;
const RELATIVE_TOL_INV: i128 = 10i128.pow(16);
const ROUND_TRIP_FLOOR_LSB: i128 = 8;
const SYMMETRY_LSB_TOL: i128 = 1;
fn proptest_config(seed_label: &'static str) -> ProptestConfig {
ProptestConfig {
cases: CASES,
max_shrink_iters: 256,
failure_persistence: None,
source_file: Some(seed_label),
..ProptestConfig::default()
}
}
fn round_trip_tol(reference: i128) -> i128 {
let rel = reference.unsigned_abs() / (RELATIVE_TOL_INV as u128);
ROUND_TRIP_FLOOR_LSB + (rel as i128)
}
macro_rules! prop_assert_within {
($label:expr, $lhs:expr, $rhs:expr, $diff:expr, $tol:expr) => {{
if $diff > $tol {
return Err(proptest::test_runner::TestCaseError::fail(format!(
"{label}: lhs={lhs} rhs={rhs} diff={diff} > tol={tol} LSB",
label = $label,
lhs = $lhs,
rhs = $rhs,
diff = $diff,
tol = $tol,
)));
}
}};
}
const ONE: Bits = 10i128.pow(19);
fn positive_x() -> impl Strategy<Value = Bits> {
1i128..=(ONE * 1_000_000)
}
fn nonneg_x() -> impl Strategy<Value = Bits> {
0i128..=(ONE * 1_000_000)
}
fn moderate_real() -> impl Strategy<Value = Bits> {
(-30 * ONE)..=(30 * ONE)
}
fn ln_exp_domain() -> impl Strategy<Value = Bits> {
0i128..=(30 * ONE)
}
fn real_x() -> impl Strategy<Value = Bits> {
(-(ONE * 1_000_000))..=(ONE * 1_000_000)
}
fn open_interval_neg1_to_1() -> impl Strategy<Value = Bits> {
(-(ONE - ONE / 1000))..=(ONE - ONE / 1000)
}
fn open_quarter_pi() -> impl Strategy<Value = Bits> {
let half = ONE / 1000;
let quarter_pi_bits = D::quarter_pi().to_bits();
(-quarter_pi_bits + half)..=(quarter_pi_bits - half)
}
proptest! {
#![proptest_config(proptest_config("exp_of_ln_roundtrip"))]
#[test]
fn exp_of_ln_roundtrip(raw in positive_x()) {
let x = D::from_bits(raw);
let y = x.ln_strict().exp_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
prop_assert_within!("exp(ln(x))", xb, yb, (xb - yb).abs(), tol);
}
}
proptest! {
#![proptest_config(proptest_config("ln_of_exp_roundtrip"))]
#[test]
fn ln_of_exp_roundtrip(raw in ln_exp_domain()) {
let x = D::from_bits(raw);
let y = x.exp_strict().ln_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
prop_assert_within!("ln(exp(x))", xb, yb, (xb - yb).abs(), tol);
}
}
proptest! {
#![proptest_config(proptest_config("sin2_plus_cos2_is_one"))]
#[test]
fn sin2_plus_cos2_is_one(raw in real_x()) {
let x = D::from_bits(raw);
let s = x.sin_strict();
let c = x.cos_strict();
let sum = s * s + c * c;
let one = D::from_bits(ONE);
let tol = round_trip_tol(ONE);
prop_assert_within!("sin²+cos²",
sum.to_bits(), one.to_bits(),
(sum.to_bits() - one.to_bits()).abs(), tol);
}
}
proptest! {
#![proptest_config(proptest_config("sqrt_squared_roundtrip"))]
#[test]
fn sqrt_squared_roundtrip(raw in nonneg_x()) {
let x = D::from_bits(raw);
let r = x.sqrt_strict();
let back = r * r;
let xb = x.to_bits();
let bb = back.to_bits();
let tol = round_trip_tol(xb);
prop_assert_within!("sqrt(x)²", xb, bb, (xb - bb).abs(), tol);
}
}
proptest! {
#![proptest_config(proptest_config("cbrt_cubed_roundtrip"))]
#[test]
fn cbrt_cubed_roundtrip(raw in real_x()) {
let x = D::from_bits(raw);
let r = x.cbrt_strict();
let back = r * r * r;
let xb = x.to_bits();
let bb = back.to_bits();
let tol = round_trip_tol(xb);
prop_assert_within!("cbrt(x)³", xb, bb, (xb - bb).abs(), tol);
}
}
proptest! {
#![proptest_config(proptest_config("atan_of_tan_roundtrip"))]
#[test]
fn atan_of_tan_roundtrip(raw in open_quarter_pi()) {
let x = D::from_bits(raw);
let y = x.tan_strict().atan_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
prop_assert_within!("atan(tan(x))", xb, yb, (xb - yb).abs(), tol);
}
}
proptest! {
#![proptest_config(proptest_config("tanh_atanh_roundtrip"))]
#[test]
fn tanh_atanh_roundtrip(raw in open_interval_neg1_to_1()) {
let x = D::from_bits(raw);
let y = x.atanh_strict().tanh_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
prop_assert_within!("tanh(atanh(x))", xb, yb, (xb - yb).abs(), tol);
}
}
proptest! {
#![proptest_config(proptest_config("sin_odd_symmetry"))]
#[test]
fn sin_odd_symmetry(raw in real_x()) {
let x = D::from_bits(raw);
let lhs = x.sin_strict();
let rhs = -((-x).sin_strict());
prop_assert_within!("sin(-x)=-sin(x)", lhs.to_bits(), rhs.to_bits(),
(lhs.to_bits() - rhs.to_bits()).abs(),
SYMMETRY_LSB_TOL);
}
}
proptest! {
#![proptest_config(proptest_config("cos_even_symmetry"))]
#[test]
fn cos_even_symmetry(raw in real_x()) {
let x = D::from_bits(raw);
let lhs = x.cos_strict();
let rhs = (-x).cos_strict();
prop_assert_within!("cos(-x)=cos(x)", lhs.to_bits(), rhs.to_bits(),
(lhs.to_bits() - rhs.to_bits()).abs(),
SYMMETRY_LSB_TOL);
}
}
proptest! {
#![proptest_config(proptest_config("atan_odd_symmetry"))]
#[test]
fn atan_odd_symmetry(raw in real_x()) {
let x = D::from_bits(raw);
let lhs = x.atan_strict();
let rhs = -((-x).atan_strict());
prop_assert_within!("atan(-x)=-atan(x)", lhs.to_bits(), rhs.to_bits(),
(lhs.to_bits() - rhs.to_bits()).abs(),
SYMMETRY_LSB_TOL);
}
}
proptest! {
#![proptest_config(proptest_config("cbrt_odd_symmetry"))]
#[test]
fn cbrt_odd_symmetry(raw in real_x()) {
let x = D::from_bits(raw);
let lhs = x.cbrt_strict();
let rhs = -((-x).cbrt_strict());
prop_assert_within!("cbrt(-x)=-cbrt(x)", lhs.to_bits(), rhs.to_bits(),
(lhs.to_bits() - rhs.to_bits()).abs(),
SYMMETRY_LSB_TOL);
}
}
#[cfg(any(feature = "d76", feature = "wide"))]
mod wide_witness {
use super::CASES;
use decimal_scaled::{D38, D76};
use proptest::prelude::*;
type N = D38<19>;
type W = D76<19>;
const ONE: i128 = 10i128.pow(19);
const CROSS_TIER_LSB_TOL: i128 = 1;
fn nonneg_x() -> impl Strategy<Value = i128> {
0i128..=(ONE * 1_000_000)
}
fn moderate_real() -> impl Strategy<Value = i128> {
(-30 * ONE)..=(30 * ONE)
}
fn config(label: &'static str) -> ProptestConfig {
ProptestConfig {
cases: CASES,
max_shrink_iters: 256,
failure_persistence: None,
source_file: Some(label),
..ProptestConfig::default()
}
}
proptest! {
#![proptest_config(config("d76_sqrt_witness"))]
#[test]
fn d76_sqrt_agrees_with_d38(raw in nonneg_x()) {
let n = N::from_bits(raw);
let w: W = n.into();
let nb = n.sqrt_strict().to_bits();
let wb = w.sqrt_strict().to_bits().to_i128_checked()
.expect("D76<19>::sqrt fits i128");
let diff = (wb - nb).abs();
prop_assert!(diff <= CROSS_TIER_LSB_TOL,
"D76<19>::sqrt vs D38<19>::sqrt diff={diff} (raw={raw})");
}
}
proptest! {
#![proptest_config(config("d76_exp_witness"))]
#[test]
fn d76_exp_agrees_with_d38(raw in moderate_real()) {
let n = N::from_bits(raw);
let w: W = n.into();
let nb = n.exp_strict().to_bits();
let wb = w.exp_strict().to_bits().to_i128_checked()
.expect("D76<19>::exp fits i128 at moderate x");
let diff = (wb - nb).abs();
prop_assert!(diff <= CROSS_TIER_LSB_TOL,
"D76<19>::exp vs D38<19>::exp diff={diff} (raw={raw})");
}
}
}
mod hard_inputs {
use super::{
CASES, ROUND_TRIP_FLOOR_LSB, RELATIVE_TOL_INV, SYMMETRY_LSB_TOL,
};
use decimal_scaled::{D38, DecimalConstants};
use proptest::prelude::*;
type D = D38<19>;
type Bits = i128;
const ONE: Bits = 10i128.pow(19);
fn config(label: &'static str) -> ProptestConfig {
ProptestConfig {
cases: CASES,
max_shrink_iters: 256,
failure_persistence: None,
source_file: Some(label),
..ProptestConfig::default()
}
}
fn round_trip_tol(reference: i128) -> i128 {
let rel = reference.unsigned_abs() / (RELATIVE_TOL_INV as u128);
ROUND_TRIP_FLOOR_LSB + (rel as i128)
}
fn near_unit() -> impl Strategy<Value = Bits> {
let delta = ONE / 1_000_000;
(ONE - delta)..=(ONE + delta)
}
proptest! {
#![proptest_config(config("hard_tie_sqrt_roundtrip"))]
#[test]
fn hard_tie_sqrt_roundtrip(raw in near_unit()) {
let x = D::from_bits(raw);
let r = x.sqrt_strict();
let back = r * r;
let xb = x.to_bits();
let bb = back.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - bb).abs();
prop_assert!(diff <= tol,
"hard_tie sqrt(x)²: x={xb} back={bb} diff={diff} > tol={tol}");
}
}
fn tiny_around_zero() -> impl Strategy<Value = Bits> {
let bound = ONE / 1_000_000;
(-bound)..=bound
}
fn ln_just_above_one() -> impl Strategy<Value = Bits> {
let bound = ONE / 1_000_000;
(ONE - bound)..=(ONE + bound)
}
proptest! {
#![proptest_config(config("hard_canc_exp_of_ln"))]
#[test]
fn hard_canc_exp_of_ln(raw in ln_just_above_one()) {
let x = D::from_bits(raw);
let y = x.ln_strict().exp_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - yb).abs();
prop_assert!(diff <= tol,
"hard_canc exp(ln(x)) near 1: x={xb} y={yb} diff={diff} > tol={tol}");
}
}
proptest! {
#![proptest_config(config("hard_canc_pythag_tiny"))]
#[test]
fn hard_canc_pythag_tiny(raw in tiny_around_zero()) {
let x = D::from_bits(raw);
let s = x.sin_strict();
let c = x.cos_strict();
let sum = s * s + c * c;
let one = D::from_bits(ONE);
let tol = round_trip_tol(ONE);
let diff = (sum.to_bits() - one.to_bits()).abs();
prop_assert!(diff <= tol,
"hard_canc sin²+cos² tiny: sum={} one={} diff={diff} > tol={tol}",
sum.to_bits(), one.to_bits());
}
}
fn near_half_pi_multiples() -> impl Strategy<Value = Bits> {
let half_pi = D::half_pi().to_bits();
(-4i64..=4i64).prop_flat_map(move |k| {
let center = (k as i128) * half_pi;
(-100i128..=100i128).prop_map(move |d| center + d)
})
}
fn near_quarter_pi_odd_multiples() -> impl Strategy<Value = Bits> {
let quarter_pi = D::quarter_pi().to_bits();
(prop::sample::select(vec![-7i64, -5, -3, -1, 1, 3, 5, 7]))
.prop_flat_map(move |k| {
let center = (k as i128) * quarter_pi;
(-50i128..=50i128).prop_map(move |d| center + d)
})
}
proptest! {
#![proptest_config(config("hard_rred_sin_symmetry"))]
#[test]
fn hard_rred_sin_symmetry(raw in near_half_pi_multiples()) {
let x = D::from_bits(raw);
let lhs = x.sin_strict();
let rhs = -((-x).sin_strict());
let diff = (lhs.to_bits() - rhs.to_bits()).abs();
prop_assert!(diff <= SYMMETRY_LSB_TOL,
"hard_rred sin(-x)=-sin(x): lhs={} rhs={} diff={diff}",
lhs.to_bits(), rhs.to_bits());
}
}
proptest! {
#![proptest_config(config("hard_rred_pythag"))]
#[test]
fn hard_rred_pythag(raw in near_quarter_pi_odd_multiples()) {
let x = D::from_bits(raw);
let s = x.sin_strict();
let c = x.cos_strict();
let sum = s * s + c * c;
let one = D::from_bits(ONE);
let tol = round_trip_tol(ONE);
let diff = (sum.to_bits() - one.to_bits()).abs();
prop_assert!(diff <= tol,
"hard_rred sin²+cos² near k·π/4: sum={} one={} diff={diff} > tol={tol}",
sum.to_bits(), one.to_bits());
}
}
fn ln_near_zero() -> impl Strategy<Value = Bits> {
1i128..=(ONE / 1000)
}
fn huge_real() -> impl Strategy<Value = Bits> {
let bound = ONE * 1_000_000;
(-bound)..=bound
}
proptest! {
#![proptest_config(config("hard_asym_exp_of_ln_small"))]
#[test]
fn hard_asym_exp_of_ln_small(raw in ln_near_zero()) {
let x = D::from_bits(raw);
let y = x.ln_strict().exp_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - yb).abs();
prop_assert!(diff <= tol,
"hard_asym exp(ln(small)): x={xb} y={yb} diff={diff} > tol={tol}");
}
}
proptest! {
#![proptest_config(config("hard_asym_atan_odd_huge"))]
#[test]
fn hard_asym_atan_odd_huge(raw in huge_real()) {
let x = D::from_bits(raw);
let lhs = x.atan_strict();
let rhs = -((-x).atan_strict());
let diff = (lhs.to_bits() - rhs.to_bits()).abs();
prop_assert!(diff <= SYMMETRY_LSB_TOL,
"hard_asym atan(-x)=-atan(x) huge: lhs={} rhs={} diff={diff}",
lhs.to_bits(), rhs.to_bits());
}
}
fn quarter_pi_inner() -> impl Strategy<Value = Bits> {
let quarter_pi = D::quarter_pi().to_bits();
let margin = ONE / 1000;
let band = ONE / 100;
let lo = quarter_pi - band;
let hi = quarter_pi - margin;
prop::sample::select(vec![1i64, -1])
.prop_flat_map(move |s| (lo..=hi).prop_map(move |b| (s as i128) * b))
}
fn squares_of_inputs() -> impl Strategy<Value = Bits> {
(1i128..=(ONE * 100)).prop_map(|raw| {
let safe = if raw > ONE / 10 { raw } else { raw };
(safe.saturating_mul(safe)) / ONE
})
}
proptest! {
#![proptest_config(config("hard_inv_atan_of_tan"))]
#[test]
fn hard_inv_atan_of_tan(raw in quarter_pi_inner()) {
let x = D::from_bits(raw);
let y = x.tan_strict().atan_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - yb).abs();
prop_assert!(diff <= tol,
"hard_inv atan(tan(x)): x={xb} y={yb} diff={diff} > tol={tol}");
}
}
proptest! {
#![proptest_config(config("hard_inv_sqrt_of_square"))]
#[test]
fn hard_inv_sqrt_of_square(raw in squares_of_inputs()) {
let x = D::from_bits(raw);
let r = x.sqrt_strict();
let back = r * r;
let xb = x.to_bits();
let bb = back.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - bb).abs();
prop_assert!(diff <= tol,
"hard_inv sqrt(x)² at square: x={xb} back={bb} diff={diff} > tol={tol}");
}
}
fn perfect_squares_jittered() -> impl Strategy<Value = Bits> {
(1i64..=1000i64).prop_flat_map(|n| {
let center = (n as i128) * (n as i128) * ONE;
(-3i128..=3i128).prop_map(move |d| center + d)
})
}
fn perfect_cubes_jittered() -> impl Strategy<Value = Bits> {
prop::sample::select(
(-100i64..=100i64).filter(|&n| n != 0).collect::<Vec<_>>(),
)
.prop_flat_map(|n| {
let center = (n as i128) * (n as i128) * (n as i128) * ONE;
(-3i128..=3i128).prop_map(move |d| center + d)
})
}
proptest! {
#![proptest_config(config("hard_pp_sqrt_roundtrip"))]
#[test]
fn hard_pp_sqrt_roundtrip(raw in perfect_squares_jittered()) {
let x = D::from_bits(raw);
let r = x.sqrt_strict();
let back = r * r;
let xb = x.to_bits();
let bb = back.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - bb).abs();
prop_assert!(diff <= tol,
"hard_pp sqrt(n² ± δ): x={xb} back={bb} diff={diff} > tol={tol}");
}
}
proptest! {
#![proptest_config(config("hard_pp_cbrt_symmetry"))]
#[test]
fn hard_pp_cbrt_symmetry(raw in perfect_cubes_jittered()) {
let x = D::from_bits(raw);
let lhs = x.cbrt_strict();
let rhs = -((-x).cbrt_strict());
let diff = (lhs.to_bits() - rhs.to_bits()).abs();
prop_assert!(diff <= SYMMETRY_LSB_TOL,
"hard_pp cbrt(-x)=-cbrt(x) at cube: lhs={} rhs={} diff={diff}",
lhs.to_bits(), rhs.to_bits());
}
}
fn near_pi_constants() -> impl Strategy<Value = Bits> {
let pi = D::pi().to_bits();
let half_pi = D::half_pi().to_bits();
let quarter_pi = D::quarter_pi().to_bits();
prop::sample::select(vec![pi, half_pi, quarter_pi, -pi, -half_pi, -quarter_pi])
.prop_flat_map(|c| (-5i128..=5i128).prop_map(move |d| c + d))
}
fn near_e_unit() -> impl Strategy<Value = Bits> {
let e = D::e().to_bits();
prop::sample::select(vec![e, -e, ONE, -ONE, 2 * ONE])
.prop_flat_map(|c| (-5i128..=5i128).prop_map(move |d| c + d))
}
proptest! {
#![proptest_config(config("hard_const_pythag"))]
#[test]
fn hard_const_pythag(raw in near_pi_constants()) {
let x = D::from_bits(raw);
let s = x.sin_strict();
let c = x.cos_strict();
let sum = s * s + c * c;
let one = D::from_bits(ONE);
let tol = round_trip_tol(ONE);
let diff = (sum.to_bits() - one.to_bits()).abs();
prop_assert!(diff <= tol,
"hard_const sin²+cos² near π const: sum={} one={} diff={diff} > tol={tol}",
sum.to_bits(), one.to_bits());
}
}
proptest! {
#![proptest_config(config("hard_const_exp_of_ln"))]
#[test]
fn hard_const_exp_of_ln(raw in near_e_unit()) {
prop_assume!(raw > 0);
let x = D::from_bits(raw);
let y = x.ln_strict().exp_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - yb).abs();
prop_assert!(diff <= tol,
"hard_const exp(ln(x)) near e: x={xb} y={yb} diff={diff} > tol={tol}");
}
}
fn atan_halving_anchors() -> impl Strategy<Value = Bits> {
let anchors_pos: Vec<Bits> = vec![
3_650_817_511_434_127_092, 1_771_239_555_181_148_572, 877_022_257_637_478_437, 437_733_213_829_148_988, 218_770_525_637_316_891, 109_376_991_958_419_141, ];
let anchors: Vec<Bits> = anchors_pos.iter().flat_map(|&v| [v, -v]).collect();
prop::sample::select(anchors)
.prop_flat_map(|c| (-5i128..=5i128).prop_map(move |d| c + d))
}
proptest! {
#![proptest_config(config("hard_halv_atan_odd"))]
#[test]
fn hard_halv_atan_odd(raw in atan_halving_anchors()) {
let x = D::from_bits(raw);
let lhs = x.atan_strict();
let rhs = -((-x).atan_strict());
let diff = (lhs.to_bits() - rhs.to_bits()).abs();
prop_assert!(diff <= SYMMETRY_LSB_TOL,
"hard_halv atan(-x)=-atan(x): lhs={} rhs={} diff={diff}",
lhs.to_bits(), rhs.to_bits());
}
}
fn near_k_ln2() -> impl Strategy<Value = Bits> {
const LN2_S19: i128 = 6_931_471_805_599_453_094;
(0i64..=30i64).prop_flat_map(|k| {
let center = (k as i128) * LN2_S19;
(-5i128..=5i128).prop_map(move |d| center + d)
})
}
proptest! {
#![proptest_config(config("hard_exp_stage2_roundtrip"))]
#[test]
fn hard_exp_stage2_roundtrip(raw in near_k_ln2()) {
let x = D::from_bits(raw);
let y = x.exp_strict().ln_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - yb).abs();
prop_assert!(diff <= tol,
"hard_exp ln(exp(k·ln2)): x={xb} y={yb} diff={diff} > tol={tol}");
}
}
fn tang_lookup_anchors() -> impl Strategy<Value = Bits> {
(0i64..128).prop_flat_map(|i| {
let center = ONE + (i as i128) * (ONE / 128);
(-3i128..=3i128).prop_map(move |d| center + d)
})
}
proptest! {
#![proptest_config(config("hard_tang_exp_of_ln"))]
#[test]
fn hard_tang_exp_of_ln(raw in tang_lookup_anchors()) {
let x = D::from_bits(raw);
let y = x.ln_strict().exp_strict();
let xb = x.to_bits();
let yb = y.to_bits();
let tol = round_trip_tol(xb);
let diff = (xb - yb).abs();
prop_assert!(diff <= tol,
"hard_tang exp(ln(T_i + δ)): x={xb} y={yb} diff={diff} > tol={tol}");
}
}
}