steel_math/noise_math/
coordinate.rs1use std::{
2 ops,
3 simd::{
4 Mask, Select, Simd, SimdCast, SimdElement, StdFloat,
5 cmp::{SimdPartialEq, SimdPartialOrd},
6 num::{SimdFloat, SimdInt},
7 },
8};
9
10#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
19#[inline(always)]
20#[must_use]
21pub fn fast_floor(v: f64) -> i32 {
22 let i = v as i32;
23 if v < f64::from(i) { i - 1 } else { i }
24}
25
26#[expect(clippy::inline_always, reason = "hot-path noise primitive")]
28#[inline(always)]
29#[must_use]
30pub fn fast_floor_simd<F, I, const N: usize>(v: Simd<F, N>) -> Simd<I, N>
31where
32 F: SimdElement + SimdCast,
33 I: SimdElement + SimdCast,
34 Simd<F, N>: SimdFloat<Cast<I> = Simd<I, N>>
35 + SimdPartialOrd
36 + SimdPartialEq<Mask = Mask<<F as SimdElement>::Mask, N>>,
37 Simd<I, N>: SimdInt<Cast<F> = Simd<F, N>> + ops::Sub<Output = Simd<I, N>>,
38{
39 let i = v.cast::<I>();
40 let b = v.simd_lt(i.cast::<F>());
41 b.select(i - Simd::splat(1).cast(), i)
42}
43
44#[inline]
48#[must_use]
49pub fn fast_lfloor(v: f64) -> i64 {
50 let i = v as i64;
51 if v < i as f64 { i - 1 } else { i }
52}
53
54#[cfg(test)]
55mod fast_floor_tests {
56 use super::*;
57 #[test]
58 fn test_floor() {
59 assert_eq!(fast_floor(1.5), 1);
60 assert_eq!(fast_floor(1.0), 1);
61 assert_eq!(fast_floor(0.5), 0);
62 assert_eq!(fast_floor(0.0), 0);
63 assert_eq!(fast_floor(-0.5), -1);
64 assert_eq!(fast_floor(-1.0), -1);
65 assert_eq!(fast_floor(-1.5), -2);
66 }
67}
68const ROUND_OFF: f64 = 33_554_432.0;
71const HALF_ROUND_OFF: f64 = ROUND_OFF / 2.0;
72
73#[inline]
78#[must_use]
79pub fn wrap_simd<F, const N: usize>(x: Simd<F, N>) -> Simd<F, N>
80where
81 F: SimdElement + SimdCast,
82 Simd<F, N>: ops::Div<Output = Simd<F, N>>
83 + ops::Add<Output = Simd<F, N>>
84 + ops::Mul<Output = Simd<F, N>>
85 + ops::Sub<Output = Simd<F, N>>
86 + SimdPartialOrd<Mask = Mask<<F as SimdElement>::Mask, N>>
87 + StdFloat,
88{
89 let in_fast_range = x.simd_ge(Simd::splat(-HALF_ROUND_OFF).cast())
90 & x.simd_lt(Simd::splat(HALF_ROUND_OFF).cast());
91 if in_fast_range.all() {
92 return x;
93 }
94
95 let round_off = Simd::splat(ROUND_OFF).cast();
96 x - (x / round_off + Simd::splat(0.5).cast()).floor() * round_off
97}
98
99#[inline]
106#[must_use]
107pub fn wrap(x: f64) -> f64 {
108 if (-HALF_ROUND_OFF..HALF_ROUND_OFF).contains(&x) {
109 return x;
110 }
111
112 x - (x / ROUND_OFF + 0.5).floor() * ROUND_OFF
113}
114
115#[cfg(test)]
116mod wrap_tests {
117 use super::*;
118 use std::simd::f64x4;
119 #[test]
120 fn test_wrap() {
121 fn wrap_reference(x: f64) -> f64 {
122 x - (x / ROUND_OFF + 0.5).floor() * ROUND_OFF
123 }
124
125 assert!((wrap(100.0) - 100.0).abs() < 1e-10);
127 assert!((wrap(-100.0) - (-100.0)).abs() < 1e-10);
128
129 let large = 100_000_000.0;
131 let wrapped = wrap(large);
132 assert!(wrapped.abs() < ROUND_OFF);
133
134 for x in [
135 -HALF_ROUND_OFF,
136 -HALF_ROUND_OFF + 1.0,
137 0.0,
138 HALF_ROUND_OFF - 1.0,
139 HALF_ROUND_OFF,
140 ROUND_OFF,
141 -ROUND_OFF,
142 100_000_000.0,
143 -100_000_000.0,
144 ] {
145 assert!((wrap(x) - wrap_reference(x)).abs() < 1e-15);
146 }
147 }
148
149 #[test]
150 fn test_wrap_4x_matches_scalar_wrap() {
151 let cases = [
152 [0.0, 1.0, -1.0, HALF_ROUND_OFF - 1.0],
153 [
154 -HALF_ROUND_OFF,
155 -HALF_ROUND_OFF + 1.0,
156 HALF_ROUND_OFF - 1.0,
157 HALF_ROUND_OFF,
158 ],
159 [ROUND_OFF, -ROUND_OFF, 100_000_000.0, -100_000_000.0],
160 [1.25, HALF_ROUND_OFF, -20.5, -HALF_ROUND_OFF],
161 ];
162
163 for case in cases {
164 let wrapped = wrap_simd(f64x4::from_array(case)).to_array();
165 for (input, actual) in case.into_iter().zip(wrapped) {
166 #[expect(
167 clippy::float_cmp,
168 reason = "SIMD wrap must be bit-identical to scalar wrap per lane"
169 )]
170 {
171 assert_eq!(actual, wrap(input));
172 }
173 }
174 }
175 }
176}