1use 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;
71
72const HALF_ROUND_OFF: f64 = (ROUND_OFF / 2.0).next_down();
73
74#[inline]
79#[must_use]
80pub fn wrap_simd<F, const N: usize>(x: Simd<F, N>) -> Simd<F, N>
81where
82 F: SimdElement + SimdCast,
83 Simd<F, N>: ops::Div<Output = Simd<F, N>>
84 + ops::Add<Output = Simd<F, N>>
85 + ops::Mul<Output = Simd<F, N>>
86 + ops::Sub<Output = Simd<F, N>>
87 + SimdPartialOrd<Mask = Mask<<F as SimdElement>::Mask, N>>
88 + StdFloat,
89{
90 let in_fast_range = x.simd_ge(Simd::splat(-HALF_ROUND_OFF).cast())
91 & x.simd_lt(Simd::splat(HALF_ROUND_OFF).cast());
92 if in_fast_range.all() {
93 return x;
94 }
95
96 let round_off = Simd::splat(ROUND_OFF).cast();
97 x - (x / round_off + Simd::splat(0.5).cast()).floor() * round_off
98}
99
100#[inline]
107#[must_use]
108pub fn wrap(x: f64) -> f64 {
109 if (-HALF_ROUND_OFF..HALF_ROUND_OFF).contains(&x) {
110 return x;
111 }
112
113 x - (x / ROUND_OFF + 0.5).floor() * ROUND_OFF
114}
115
116#[cfg(test)]
117mod wrap_tests {
118 use super::*;
119 use std::simd::f64x4;
120
121 const CASES: &[[f64; 4]] = &[
122 [0.0, -0.0, 100.0, -100.0],
123 [
124 HALF_ROUND_OFF.next_down(),
125 HALF_ROUND_OFF,
126 HALF_ROUND_OFF.next_up(),
127 HALF_ROUND_OFF.next_up().next_up(),
128 ],
129 [
130 (-HALF_ROUND_OFF).next_down(),
131 -HALF_ROUND_OFF,
132 (-HALF_ROUND_OFF).next_up(),
133 (-HALF_ROUND_OFF).next_up().next_up(),
134 ],
135 [ROUND_OFF, -ROUND_OFF, 100_000_000.0, -100_000_000.0],
136 [1.25, HALF_ROUND_OFF, -20.5, -HALF_ROUND_OFF],
137 [0.0, -0.0, HALF_ROUND_OFF, f64::NAN],
138 [f64::INFINITY, f64::NEG_INFINITY, f64::NAN, -f64::NAN],
139 [f64::MAX, -f64::MAX, f64::MIN_POSITIVE, -f64::MIN_POSITIVE],
140 ];
141
142 fn assert_matches_vanilla(input: f64, actual: f64) {
143 let bound = 16_777_216.0_f64.next_down();
145 let expected = if (-bound..bound).contains(&input) {
146 input
147 } else {
148 input - (input / 33_554_432.0 + 0.5).floor() * 33_554_432.0
149 };
150 if expected.is_nan() {
151 assert!(actual.is_nan(), "input {input:?}: {actual:?}");
152 } else {
153 assert_eq!(actual.to_bits(), expected.to_bits(), "input {input:?}");
154 }
155 }
156
157 #[test]
158 fn test_wrap() {
159 assert_eq!(
160 wrap(16_777_216.0_f64.next_down()).to_bits(),
161 (-16_777_216.0_f64).to_bits()
162 );
163 for &input in CASES.iter().flatten() {
164 assert_matches_vanilla(input, wrap(input));
165 }
166 }
167
168 #[test]
169 fn test_wrap_4x_matches_vanilla() {
170 for &case in CASES {
171 let wrapped = wrap_simd(f64x4::from_array(case)).to_array();
172 for (input, actual) in case.into_iter().zip(wrapped) {
173 assert_matches_vanilla(input, actual);
174 }
175 }
176 }
177}