1#![allow(clippy::excessive_precision, clippy::approx_constant)]
7const M_1_PI_F: f32 = 0.318_309_886_183_790_671_537_767_526_745_028_724_f32;
37const PI_A2_F: f32 = 3.141_479_492_187_5;
39const PI_B2_F: f32 = 0.000_113_159_418_106_079_101_56;
40const PI_C2_F: f32 = 1.984_187_258_941_005_893_6e-9;
41pub const TRIGRANGEMAX2_F: f32 = 125.0;
43
44const R_LN2_F: f32 = 1.442_695_040_888_963_407_359_924_681_001_892_137_4_f32;
46const L2U_F: f32 = 0.693_145_751_953_125;
48const L2L_F: f32 = 1.428_606_765_330_187_045e-6;
49
50#[derive(Clone, Copy, Debug)]
56struct Df {
57 high: f32,
58 low: f32,
59}
60
61fn df_two_sum(x: f32, y: f32) -> Df {
63 let high = x + y;
64 let v = high - x;
65 let low = (x - (high - v)) + (y - v);
66 Df { high, low }
67}
68
69fn df_fast_two_sum(x: f32, y: f32) -> Df {
71 let high = x + y;
72 Df {
73 high,
74 low: (x - high) + y,
75 }
76}
77
78fn df_add_f32(x: Df, y: f32) -> Df {
80 let high = x.high + y;
81 Df {
82 high,
83 low: ((x.high - high) + y) + x.low,
84 }
85}
86
87fn df_add_to_f32(x: f32, y: Df) -> Df {
89 let high = x + y.high;
90 Df {
91 high,
92 low: ((x - high) + y.high) + y.low,
93 }
94}
95
96fn df_square(x: Df) -> Df {
98 let high = x.high * x.high;
99 Df {
100 high,
101 low: (x.high + x.high).mul_add(x.low, x.high.mul_add(x.high, -high)),
102 }
103}
104
105fn df_mul(x: Df, y: Df) -> Df {
107 let high = x.high * y.high;
108 let mut low = x.high.mul_add(y.high, -high);
109 low = x.low.mul_add(y.high, low);
110 low = x.high.mul_add(y.low, low);
111 Df { high, low }
112}
113
114fn df_mul_to_f32(x: Df, y: Df) -> f32 {
116 x.high
117 .mul_add(y.high, x.low.mul_add(y.high, x.high * y.low))
118}
119
120#[must_use]
122pub fn sinf_u10_in_fast_range(x: f32) -> bool {
123 x.abs() < TRIGRANGEMAX2_F
124}
125
126#[must_use]
131pub fn sinf_u10(d: f32) -> f32 {
132 if !sinf_u10_in_fast_range(d) {
133 return f64::from(d).sin() as f32;
134 }
135
136 let scaled = (d * M_1_PI_F).round_ties_even();
137 let quadrant = scaled as i32;
138
139 let reduced = scaled.mul_add(-PI_A2_F, d);
142 let mut reduced = df_two_sum(reduced, scaled * -PI_B2_F);
143 reduced = df_add_f32(reduced, scaled * -PI_C2_F);
144
145 let argument = reduced;
146 let square = df_square(reduced);
147
148 let mut poly = 2.608_315_980_978_659_354_150_3e-6_f32;
149 poly = poly.mul_add(square.high, -0.000_198_106_907_191_686_332_225_8);
150 poly = poly.mul_add(square.high, 0.008_333_078_585_565_090_179_443_36);
151
152 let series = df_add_to_f32(
153 1.0,
154 df_mul(
155 df_fast_two_sum(-0.166_666_597_127_914_428_710_938, poly * square.high),
156 square,
157 ),
158 );
159 let result = df_mul_to_f32(argument, series);
160
161 if d == 0.0 {
162 return d;
164 }
165 if quadrant & 1 == 0 { result } else { -result }
166}
167
168#[must_use]
170pub fn expf_u10(d: f32) -> f32 {
171 let exponent = (d * R_LN2_F).round_ties_even() as i32;
172 let scaled = exponent as f32;
173
174 let mut reduced = scaled.mul_add(-L2U_F, d);
175 reduced = scaled.mul_add(-L2L_F, reduced);
176
177 let mut poly = 0.000_198_527_617_612_853_646_278_381_f32;
178 poly = poly.mul_add(reduced, 0.001_393_043_552_525_341_510_772_71);
179 poly = poly.mul_add(reduced, 0.008_333_360_776_305_198_669_433_59);
180 poly = poly.mul_add(reduced, 0.041_666_485_369_205_474_853_515_6);
181 poly = poly.mul_add(reduced, 0.166_666_671_633_720_397_949_219);
182 poly = poly.mul_add(reduced, 0.5);
183
184 let mantissa = 1.0 + (reduced * reduced).mul_add(poly, reduced);
185 let result = ldexp2(mantissa, exponent);
186
187 if d < -104.0 {
188 return 0.0;
189 }
190 if d > 100.0 {
191 return f32::INFINITY;
192 }
193 result
194}
195
196fn ldexp2(x: f32, exponent: i32) -> f32 {
198 let half = exponent >> 1;
199 x * pow2i(half) * pow2i(exponent - half)
200}
201
202fn pow2i(exponent: i32) -> f32 {
204 f32::from_bits(((exponent + 0x7f) << 23) as u32)
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 fn ulp_distance(candidate: f32, exact: f64) -> i64 {
218 let rounded = exact as f32;
219 assert!(
220 candidate.is_finite() && rounded.is_finite(),
221 "finite inputs"
222 );
223 let ordered = |value: f32| -> i64 {
224 let bits = i64::from(value.to_bits() as i32);
225 if bits < 0 {
226 i64::from(i32::MIN) - bits
227 } else {
228 bits
229 }
230 };
231 (ordered(candidate) - ordered(rounded)).abs()
232 }
233
234 fn sweep(limit: f32, count: u32) -> impl Iterator<Item = f32> {
236 (0..count).map(move |step| {
237 let unit = f64::from(step) / f64::from(count - 1);
238 ((unit * 2.0 - 1.0) * f64::from(limit)) as f32
239 })
240 }
241
242 #[test]
243 fn sinf_u10_is_within_one_ulp_over_the_cody_waite_range() {
244 let mut worst = 0;
245 for x in sweep(TRIGRANGEMAX2_F * 0.999, 40_001) {
246 worst = worst.max(ulp_distance(sinf_u10(x), f64::from(x).sin()));
247 }
248 assert!(worst <= 1, "sinf_u10 drifted {worst} ulps from correct");
249 }
250
251 #[test]
252 fn sinf_u10_is_within_one_ulp_near_zero_where_the_seam_lives() {
253 let mut worst = 0;
254 for x in sweep(8.0, 60_001) {
255 worst = worst.max(ulp_distance(sinf_u10(x), f64::from(x).sin()));
256 }
257 assert!(worst <= 1, "sinf_u10 drifted {worst} ulps near zero");
258 }
259
260 #[test]
261 fn expf_u10_is_within_one_ulp() {
262 let mut worst = 0;
263 for x in sweep(80.0, 60_001) {
264 worst = worst.max(ulp_distance(expf_u10(x), f64::from(x).exp()));
265 }
266 assert!(worst <= 1, "expf_u10 drifted {worst} ulps from correct");
267 }
268
269 #[test]
270 fn the_exact_cases_stay_exact() {
271 assert_eq!(sinf_u10(0.0), 0.0);
272 assert!(sinf_u10(-0.0).is_sign_negative());
273 assert_eq!(expf_u10(0.0), 1.0);
274 assert_eq!(expf_u10(-200.0), 0.0);
275 assert_eq!(expf_u10(200.0), f32::INFINITY);
276 }
277
278 #[test]
279 fn the_payne_hanek_range_is_flagged_rather_than_claimed() {
280 assert!(sinf_u10_in_fast_range(124.9));
281 assert!(!sinf_u10_in_fast_range(125.0));
282 assert!(!sinf_u10_in_fast_range(f32::NAN));
283 }
284}