use super::core::{Dec, ONE_U};
const CHUNK: u128 = 1_000_000_000;
const SIMPLE_MAX: u128 = u128::MAX / ONE_U;
impl Dec {
#[inline(always)]
pub const fn sqrt(self) -> Self {
match self.checked_sqrt() {
Some(value) => value,
None => Self::NAN,
}
}
#[inline(always)]
pub const fn checked_sqrt(self) -> Option<Self> {
if self.0 < 0 {
return None;
}
Some(Self(sqrt_raw(self.0 as u128)))
}
#[inline]
pub fn sqrt_approx(self) -> Self {
if self.0 < 0 {
return Self::NAN;
}
match Self::from_f64(self.to_f64().sqrt()) {
Some(value) => value,
None => Self::NAN,
}
}
}
#[inline]
const fn sqrt_raw(raw: u128) -> i128 {
match raw <= SIMPLE_MAX {
true => simple(raw),
false => wide(raw),
}
}
#[inline(always)]
const fn simple(raw: u128) -> i128 {
let scaled = raw * ONE_U;
let root = scaled.isqrt();
match scaled - root * root > root {
true => root as i128 + 1,
false => root as i128,
}
}
const fn wide(raw: u128) -> i128 {
let whole = raw.isqrt();
let excess = raw - whole * whole;
let mut root = whole * CHUNK + excess * CHUNK / (2 * whole + 1);
while !square_at_most(root, 0, raw) {
root -= 1;
}
while square_at_most(root + 1, 0, raw) {
root += 1;
}
match square_at_most(root, root + 1, raw) {
true => root as i128 + 1,
false => root as i128,
}
}
#[inline(always)]
const fn square_at_most(root: u128, bias: u128, raw: u128) -> bool {
let (high, low) = (root / CHUNK, root % CHUNK);
match raw.checked_sub(high * high) {
None => false,
Some(slack) => match slack.checked_mul(ONE_U) {
Some(limit) => 2 * high * low * CHUNK + low * low + bias <= limit,
None => true,
},
}
}
#[cfg(test)]
mod root {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::dec;
use std::str::FromStr;
fn parse(text: &str) -> Dec {
Dec::from_str(text).unwrap()
}
#[test]
fn test_the_threshold_splits_the_paths_where_the_product_stops_fitting() {
assert!(SIMPLE_MAX.checked_mul(ONE_U).is_some());
assert_eq!((SIMPLE_MAX + 1).checked_mul(ONE_U), None);
assert!(dec!(100).into_raw() as u128 <= SIMPLE_MAX);
assert!(dec!(104_237.25).into_raw() as u128 > SIMPLE_MAX);
}
#[test]
fn test_the_two_paths_agree_where_both_are_valid() {
let mut state = 0x2545_F491_4F6C_DD1D_u64;
for _ in 0..20_000 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let raw = ONE_U + (state as u128) % (SIMPLE_MAX - ONE_U);
assert_eq!(simple(raw), wide(raw), "{raw}");
}
for raw in [ONE_U, ONE_U + 1, SIMPLE_MAX - 1, SIMPLE_MAX] {
assert_eq!(simple(raw), wide(raw), "{raw}");
}
}
#[test]
fn test_a_perfect_square_is_exact() {
for text in ["0", "1", "4", "16", "100", "2.25", "0.25", "0.000000000001"] {
let value = parse(text);
assert_eq!(value.sqrt() * value.sqrt(), value, "{text}");
}
assert_eq!(dec!(16).sqrt(), dec!(4));
assert_eq!(dec!(2.25).sqrt(), dec!(1.5));
assert_eq!(Dec::ZERO.sqrt(), Dec::ZERO);
assert_eq!(Dec::ONE.sqrt(), Dec::ONE);
}
#[test]
fn test_an_irrational_root_rounds_half_away_from_zero() {
assert_eq!(dec!(2).sqrt(), parse("1.414213562373095049"));
assert_eq!(dec!(3).sqrt(), parse("1.732050807568877294"));
assert_eq!(dec!(10).sqrt(), parse("3.162277660168379332"));
}
#[test]
fn test_the_root_brackets_the_value() {
let mut state = 0x1234_5678_9abc_def1_u64;
for _ in 0..20_000 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let raw = (state as u128) * (state as u128 | 1) % (Dec::MAX.into_raw() as u128);
let root = sqrt_raw(raw) as u128;
let floor = match square_at_most(root, 0, raw) {
true => root,
false => root - 1,
};
assert!(square_at_most(floor, 0, raw), "{raw}");
assert!(!square_at_most(floor + 1, 0, raw), "{raw}");
}
}
#[test]
fn test_the_root_is_monotonic() {
let mut previous = Dec::ZERO;
for raw in (0..2_000).map(|step: u32| step as i128 * (Dec::MAX.into_raw() / 2_000)) {
let root = Dec::from_raw(raw).sqrt();
assert!(root >= previous, "{raw}");
previous = root;
}
}
#[test]
fn test_the_largest_value_has_a_root_far_inside_the_range() {
let root = Dec::MAX.sqrt();
assert!(root.is_finite());
assert_eq!(root, parse("13043817825.332782212349571806"));
assert!(root * root <= Dec::MAX);
}
#[test]
fn test_the_smallest_value_has_a_root() {
assert_eq!(Dec::EPSILON.sqrt(), parse("0.000000001"));
assert_eq!(Dec::from_raw(2).sqrt(), parse("0.000000001414213562"));
}
#[test]
fn test_the_approximate_root_keeps_the_double_s_digits() {
let mut state = 0x5eed_1234_abcd_0f0f_u64;
for _ in 0..50_000 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let wide = (state as u128) << 64 | state.rotate_left(29) as u128;
let value = Dec::from_raw((wide % (Dec::MAX.into_raw() as u128)) as i128);
let (exact, approx) = (value.sqrt(), value.sqrt_approx());
let drift = (approx - exact).abs();
let allowed = exact * parse("0.000000000000001") + parse("0.000000000000001");
assert!(
drift <= allowed,
"sqrt({value}) drifted {drift}, allowed {allowed}"
);
}
}
#[test]
fn test_the_approximate_root_is_exact_where_the_double_is() {
for text in ["0", "1", "4", "16", "100", "2.25", "0.25", "0.0625"] {
let value = parse(text);
assert_eq!(value.sqrt_approx(), value.sqrt(), "{text}");
}
}
#[test]
fn test_both_roots_answer_nan_on_the_same_inputs() {
for value in [dec!(-1), Dec::MIN, Dec::NAN, dec!(-0.000000000000000001)] {
assert!(value.sqrt().is_nan(), "{value}");
assert!(value.sqrt_approx().is_nan(), "{value}");
}
}
#[test]
fn test_a_negative_value_is_nan_rather_than_a_complex_number() {
assert!(dec!(-1).sqrt().is_nan());
assert!(dec!(-0.000000000000000001).sqrt().is_nan());
assert!(Dec::MIN.sqrt().is_nan());
assert_eq!(dec!(-1).checked_sqrt(), None);
assert_eq!(Dec::MIN.checked_sqrt(), None);
}
#[test]
fn test_nan_propagates_through_the_root() {
assert!(Dec::NAN.sqrt().is_nan());
assert_eq!(Dec::NAN.checked_sqrt(), None);
}
#[test]
fn test_the_root_is_available_in_a_const() {
const ROOT: Dec = dec!(6.25).sqrt();
assert_eq!(ROOT, dec!(2.5));
}
}