pub(crate) fn sin(radians: f64) -> f64 {
libm::sin(radians)
}
pub(crate) fn cos(radians: f64) -> f64 {
libm::cos(radians)
}
pub(crate) fn sin_cos(radians: f64) -> (f64, f64) {
(sin(radians), cos(radians))
}
pub(crate) fn atan(value: f64) -> f64 {
libm::atan(value)
}
pub(crate) fn atan2(y: f64, x: f64) -> f64 {
libm::atan2(y, x)
}
pub(crate) fn powf(base: f64, exponent: f64) -> f64 {
libm::pow(base, exponent)
}
pub(crate) fn exp(value: f64) -> f64 {
libm::exp(value)
}
pub(crate) fn exp2(value: f64) -> f64 {
libm::exp2(value)
}
pub(crate) fn log2(value: f64) -> f64 {
libm::log2(value)
}
pub(crate) fn hypot(x: f64, y: f64) -> f64 {
libm::hypot(x, y)
}
#[cfg(any(feature = "png", feature = "pdf"))]
pub(crate) fn hypotf(x: f32, y: f32) -> f32 {
libm::hypotf(x, y)
}
pub(crate) fn cbrt(value: f64) -> f64 {
libm::cbrt(value)
}
pub(crate) fn square<T: Copy + std::ops::Mul<Output = T>>(value: T) -> T {
value * value
}
pub(crate) fn cube(value: f64) -> f64 {
value * value * value
}
pub(crate) fn cube_rounded_once(value: f64) -> f64 {
if value == 0.0 || !value.is_finite() {
return cube(value);
}
let bits = value.to_bits();
let sign = bits >> 63;
let biased_exponent = ((bits >> 52) & 0x7ff) as i64;
let fraction = bits & ((1u64 << 52) - 1);
let (significand, exponent) = if biased_exponent == 0 {
(fraction, -1074)
} else {
(fraction | (1u64 << 52), biased_exponent - 1075)
};
let squared = u128::from(significand) * u128::from(significand);
let low = (squared & ((1u128 << 64) - 1)) * u128::from(significand);
let high = (squared >> 64) * u128::from(significand) + (low >> 64);
let low_low = low & ((1u128 << 64) - 1);
let total_bits = if high > 0 {
i64::from(128 - high.leading_zeros()) + 64
} else {
i64::from(128 - low_low.leading_zeros())
};
let shift = total_bits - 53;
let (quotient, dropped_versus_half) = if shift <= 0 {
(
((high << 64) | low_low) << (-shift),
core::cmp::Ordering::Less,
)
} else if shift >= 64 {
let high_shift = shift - 64;
let quotient = high >> high_shift;
let ordering = if high_shift == 0 {
low_low.cmp(&(1u128 << 63))
} else {
let dropped_high = high & ((1u128 << high_shift) - 1);
match dropped_high.cmp(&(1u128 << (high_shift - 1))) {
core::cmp::Ordering::Equal if low_low != 0 => core::cmp::Ordering::Greater,
ordering => ordering,
}
};
(quotient, ordering)
} else {
let quotient = (high << (64 - shift)) | (low_low >> shift);
let dropped = low_low & ((1u128 << shift) - 1);
(quotient, dropped.cmp(&(1u128 << (shift - 1))))
};
let mut quotient = quotient as u64;
match dropped_versus_half {
core::cmp::Ordering::Greater => quotient += 1,
core::cmp::Ordering::Equal if quotient & 1 == 1 => quotient += 1,
_ => {}
}
let mut result_exponent = 3 * exponent + shift;
if quotient == 1u64 << 53 {
quotient >>= 1;
result_exponent += 1;
}
let biased = result_exponent + 52 + 1023;
if biased <= 0 || biased >= 0x7ff {
return cube(value);
}
f64::from_bits((sign << 63) | ((biased as u64) << 52) | (quotient & ((1u64 << 52) - 1)))
}
pub(crate) fn ecmascript_hypot(x: f64, y: f64) -> f64 {
let (x, y) = (x.abs(), y.abs());
if x.is_infinite() || y.is_infinite() {
return f64::INFINITY;
}
if x.is_nan() || y.is_nan() {
return f64::NAN;
}
let max = x.max(y);
if max == 0.0 {
return 0.0;
}
let first = x / max;
let second = y / max;
(first * first + second * second).sqrt() * max
}
#[cfg(test)]
mod tests {
use super::{cube, sin_cos, square};
#[allow(clippy::disallowed_methods)]
#[test]
fn square_and_cube_match_the_run_time_powi_expansion_bit_for_bit() {
let samples = [
0.0_f64,
-0.0,
1.0,
-1.0,
0.3,
0.7,
1e-300,
1e300,
123.456,
-987.654,
0.1 + 0.2,
];
for value in samples.map(std::hint::black_box) {
assert_eq!(square(value).to_bits(), value.powi(2).to_bits(), "{value}");
assert_eq!(cube(value).to_bits(), value.powi(3).to_bits(), "{value}");
let single = value as f32;
assert_eq!(
square(single).to_bits(),
single.powi(2).to_bits(),
"{single}"
);
}
}
#[test]
fn sin_cos_is_the_pair_of_sin_and_cos() {
for radians in [0.0, 0.5, 1.0, -2.5, std::f64::consts::PI, 1e6] {
let (sin, cos) = sin_cos(radians);
assert_eq!(sin.to_bits(), super::sin(radians).to_bits());
assert_eq!(cos.to_bits(), super::cos(radians).to_bits());
}
}
#[test]
fn cube_rounded_once_is_the_reference_math_pow_where_two_roundings_miss_it() {
use super::cube_rounded_once;
let differing: [(f64, f64, f64); 6] = [
(
0.616_205_673_688_851_3,
0.233_979_106_528_009_88,
0.233_979_106_528_009_85,
),
(
0.292_803_224_645_716_2,
0.025_103_112_125_565_72,
0.025_103_112_125_565_725,
),
(
0.703_117_954_469_567_3,
0.347_603_838_826_051,
0.347_603_838_826_051_06,
),
(
0.883_273_516_906_646_3,
0.689_105_359_566_399_5,
0.689_105_359_566_399_6,
),
(
0.257_553_106_503_737_53,
0.017_084_425_433_172_436,
0.017_084_425_433_172_44,
),
(
0.712_343_658_038_061_7,
0.361_467_026_445_337_1,
0.361_467_026_445_337_2,
),
];
for (value, pow, twice) in differing {
assert_eq!(cube(value).to_bits(), f64::to_bits(twice), "{value} twice");
assert_eq!(
cube_rounded_once(value).to_bits(),
f64::to_bits(pow),
"{value} once"
);
}
let agreeing: [(f64, f64); 12] = [
(0.3, 0.026_999_999_999_999_996),
(0.7, 0.342_999_999_999_999_9),
(0.5, 0.125),
(-0.152_134_363_308_487_29, -0.003_521_129_224_458_991),
(123.456, 1_881_640.295_202_816_1),
(1e100, 1e300),
(1e103, f64::INFINITY),
(1e-300, 0.0),
(2e-108, 1e-323),
(0.0, 0.0),
(1.0, 1.0),
(-1.0, -1.0),
];
for (value, pow) in agreeing {
assert_eq!(
cube_rounded_once(value).to_bits(),
f64::to_bits(pow),
"{value}"
);
}
assert!(cube_rounded_once(f64::NAN).is_nan());
assert_eq!(cube_rounded_once(f64::NEG_INFINITY), f64::NEG_INFINITY);
}
#[test]
fn ecmascript_hypot_normalizes_like_v8_where_the_naive_root_misses() {
use super::{ecmascript_hypot, hypot};
let pairs: [(f64, f64, f64, f64); 5] = [
(
4.436_023_699_646_386,
-5.331_564_516_784_604,
6.935_696_537_512_369,
6.935_696_537_512_368,
),
(
-7.675_428_604_318_679,
-21.211_718_603_191_01,
22.557_686_281_198_1,
22.557_686_281_198_098,
),
(
-6.635_509_993_519_029,
-1.917_027_456_322_562,
6.906_879_696_533_41,
6.906_879_696_533_410_5,
),
(
-2.754_105_021_405_473,
-2.831_362_128_856_252,
3.949_899_489_056_936,
3.949_899_489_056_936_6,
),
(
19.576_852_310_314_674,
48.882_048_037_118_59,
52.656_507_353_631_45,
52.656_507_353_631_44,
),
];
for (a, b, reference, naive) in pairs {
assert_eq!(
(a * a + b * b).sqrt().to_bits(),
f64::to_bits(naive),
"{a} {b} naive"
);
assert_eq!(
ecmascript_hypot(a, b).to_bits(),
f64::to_bits(reference),
"{a} {b}"
);
}
let (sin, cos) = sin_cos(1.2);
assert_eq!(
ecmascript_hypot(50.0 * cos, 30.0 * sin).to_bits(),
f64::to_bits(33.317_938_525_169_94)
);
assert_eq!(
ecmascript_hypot(30.0 * cos, 50.0 * sin).to_bits(),
f64::to_bits(47.853_056_040_685_594)
);
assert_eq!(
hypot(50.0 * cos, 30.0 * sin).to_bits(),
f64::to_bits(33.317_938_525_169_94)
);
assert_eq!(ecmascript_hypot(0.0, 0.0), 0.0);
assert_eq!(ecmascript_hypot(-3.0, 0.0), 3.0);
assert_eq!(ecmascript_hypot(f64::INFINITY, f64::NAN), f64::INFINITY);
assert!(ecmascript_hypot(1.0, f64::NAN).is_nan());
}
}