use core::ops::{Add, AddAssign, Mul, Neg, Sub};
#[inline]
pub fn sqrt(x: f32) -> f32 {
#[cfg(feature = "std")]
{
x.sqrt()
}
#[cfg(not(feature = "std"))]
{
sqrt_soft(x)
}
}
#[inline]
#[allow(dead_code)]
pub(crate) fn sqrt_soft(x: f32) -> f32 {
if x.is_nan() || x < 0.0 {
return f32::NAN;
}
if x == 0.0 || x == f32::INFINITY {
return x;
}
if x < f32::MIN_POSITIVE {
return sqrt_soft(x * 16_777_216.0) * (1.0 / 4096.0);
}
let bits = x.to_bits();
let mut y = f32::from_bits((bits >> 1) + 0x1fc0_0000);
for _ in 0..4 {
y = 0.5 * (y + x / y);
}
y
}
#[inline]
pub fn floor_i32(x: f32) -> i32 {
let t = x as i32;
if x < t as f32 {
t - 1
} else {
t
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Vec2 {
pub const ZERO: Vec2 = Vec2 { x: 0.0, y: 0.0 };
#[inline]
pub const fn new(x: f32, y: f32) -> Self {
Vec2 { x, y }
}
#[inline]
pub fn dot(self, other: Vec2) -> f32 {
self.x * other.x + self.y * other.y
}
#[inline]
pub fn cross(self, other: Vec2) -> f32 {
self.x * other.y - self.y * other.x
}
#[inline]
pub fn length(self) -> f32 {
sqrt(self.dot(self))
}
#[inline]
pub fn length_squared(self) -> f32 {
self.dot(self)
}
#[inline]
pub fn normalize(self) -> Option<Vec2> {
let len = self.length();
if len > 0.0 && len.is_finite() {
Some(Vec2::new(self.x / len, self.y / len))
} else {
None
}
}
#[inline]
pub fn perp(self) -> Vec2 {
Vec2::new(-self.y, self.x)
}
#[inline]
#[allow(dead_code)]
pub fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite()
}
}
impl Add for Vec2 {
type Output = Vec2;
#[inline]
fn add(self, rhs: Vec2) -> Vec2 {
Vec2::new(self.x + rhs.x, self.y + rhs.y)
}
}
impl AddAssign for Vec2 {
#[inline]
fn add_assign(&mut self, rhs: Vec2) {
*self = *self + rhs;
}
}
impl Sub for Vec2 {
type Output = Vec2;
#[inline]
fn sub(self, rhs: Vec2) -> Vec2 {
Vec2::new(self.x - rhs.x, self.y - rhs.y)
}
}
impl Mul<f32> for Vec2 {
type Output = Vec2;
#[inline]
fn mul(self, rhs: f32) -> Vec2 {
Vec2::new(self.x * rhs, self.y * rhs)
}
}
impl Neg for Vec2 {
type Output = Vec2;
#[inline]
fn neg(self) -> Vec2 {
Vec2::new(-self.x, -self.y)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn soft_sqrt_matches_std_within_one_ulp() {
let cases = [
1.0,
2.0,
4.0,
1e-30,
1e30,
0.5,
32767.0,
32767.0 * 32767.0 * 2.0,
f32::MIN_POSITIVE,
f32::MAX,
3.0000001,
];
for x in cases {
let soft = sqrt_soft(x);
let hard = x.sqrt();
let ulps = (soft.to_bits() as i64 - hard.to_bits() as i64).abs();
assert!(ulps <= 1, "sqrt({x}): soft={soft} hard={hard} ulps={ulps}");
}
}
#[test]
fn soft_sqrt_matches_std_over_a_sweep() {
let mut x = 1e-12f32;
while x < 1e12 {
let ulps = (sqrt_soft(x).to_bits() as i64 - x.sqrt().to_bits() as i64).abs();
assert!(ulps <= 1, "sqrt({x}) differed by {ulps} ulps");
x *= 1.000_137;
}
}
#[test]
fn soft_sqrt_handles_denormals() {
for x in [1e-40f32, 1e-44, f32::MIN_POSITIVE / 2.0, f32::from_bits(1)] {
let ulps = (sqrt_soft(x).to_bits() as i64 - x.sqrt().to_bits() as i64).abs();
assert!(ulps <= 1, "sqrt({x:e}) differed by {ulps} ulps");
}
}
#[test]
fn soft_sqrt_edge_values() {
assert_eq!(sqrt_soft(0.0), 0.0);
assert!(sqrt_soft(0.0).is_sign_positive());
assert!(sqrt_soft(-0.0).is_sign_negative(), "must preserve -0.0");
assert!(sqrt_soft(-1.0).is_nan());
assert!(sqrt_soft(f32::NAN).is_nan());
assert_eq!(sqrt_soft(f32::INFINITY), f32::INFINITY);
}
#[test]
fn floor_i32_matches_std_floor() {
for x in [
0.0f32,
1.0,
1.5,
-1.0,
-1.5,
-0.5,
0.5,
1e6,
-1e6,
16384.0,
-16384.0,
1_638_400.0,
-1_638_400.0,
-0.0,
] {
assert_eq!(floor_i32(x), x.floor() as i32, "floor_i32({x})");
}
}
#[test]
fn floor_i32_over_a_sweep() {
let mut x = -3000.0f32;
while x < 3000.0 {
assert_eq!(floor_i32(x), x.floor() as i32, "floor_i32({x})");
x += 0.37;
}
}
#[test]
fn vec2_perp_faces_left() {
assert_eq!(Vec2::new(1.0, 0.0).perp(), Vec2::new(0.0, 1.0));
}
#[test]
fn vec2_cross_sign_is_ccw_positive() {
assert!(Vec2::new(1.0, 0.0).cross(Vec2::new(0.0, 1.0)) > 0.0);
assert!(Vec2::new(0.0, 1.0).cross(Vec2::new(1.0, 0.0)) < 0.0);
}
#[test]
fn vec2_normalize_rejects_degenerate() {
assert!(Vec2::ZERO.normalize().is_none());
let n = Vec2::new(3.0, 4.0).normalize().unwrap();
assert!((n.length() - 1.0).abs() < 1e-6);
}
}