Skip to main content

banished_core/
crt.rs

1//! The float functions of the Microsoft C runtime, as the game calls them: [`sinf`], [`cosf`],
2//! [`acosf`] and [`atan2f`].
3//!
4//! These functions are not correctly rounded, so their results can differ from Rust's `f32::sin`
5//! and its relatives in the last bit. That is enough to change the game's results. So this module
6//! ports the C runtime's own algorithms, operation by operation, and simulation code uses them.
7//! `tools/emu/crt_math.py` checks them against the original functions. `powf`, `floorf` and
8//! `sqrtf` are correctly rounded in both, so Rust's own functions give the same results.
9//!
10//! ```
11//! use banished_core::crt;
12//!
13//! let s = crt::sinf(0.5);
14//! let angle = crt::atan2f(1.0, 2.0);
15//! assert!(s > 0.47 && angle > 0.46);
16//! ```
17//!
18//! Original: `FUN_140137160` (`sinf`, the Runtime's copy at `0x18012a620` is the same),
19//! `FUN_140136560` (`cosf`, Runtime `0x1801295a0`), `FUN_140136090` (`acosf`), Runtime
20//! `0x180128f10` (`atan2f`, behind `Vector3::Heading`).
21
22const fn d(bits: u64) -> f64 {
23    f64::from_bits(bits)
24}
25
26// FUN_140137160's constants (`0x140183190..0x1401832a0`).
27const S_ONE: f64 = 1.0;
28const S_HALF: f64 = 0.5;
29const S_SIXTH: f64 = 0.16666666666666666;
30const S_2_PI: f64 = 0.6366197723675814;
31const S_PIO2_1: f64 = 1.5707963267341256;
32const S_PIO2_1T: f64 = 6.077100506506192e-11;
33const S_PIO2_2: f64 = 6.077100506303966e-11;
34const S_PIO2_2T: f64 = 2.0222662487959506e-21;
35const S_BIG: u64 = 0x411e_8480_0000_0000; // 500000.0
36const S_S1: f64 = -0.16666666666666666;
37const S_S2: f64 = 0.008333333333333333;
38const S_S3: f64 = -0.0001984126984126984;
39const S_S4: f64 = 2.7557319223985893e-06;
40const S_C0: f64 = -0.5;
41const S_C1: f64 = 0.041666666666666664;
42const S_C2: f64 = -0.0013888888888888887;
43const S_C3: f64 = 2.4801587301587298e-05;
44const S_C4: f64 = -2.755731922398589e-07;
45
46// FUN_140136560's polynomial (`0x140182610..0x140182648`).
47const C_S1: f64 = -0.16666666666666666;
48const C_S2: f64 = 0.00833333333333095;
49const C_S3: f64 = -0.00019841269836761127;
50const C_S4: f64 = 2.7557316103728802e-06;
51const C_C1: f64 = 0.041666666666666664;
52const C_C2: f64 = -0.0013888888888887398;
53const C_C3: f64 = 2.480158729876704e-05;
54const C_C4: f64 = -2.755731727234419e-07;
55
56/// The `π/2` reduction both use: the region and the reduced argument, and the exponent the
57/// small cases are told by (taken before the extra precision step).
58fn reduce(x: f64, ax: u64) -> (u32, f64, u64) {
59    let region = ((x * S_2_PI) + S_HALF) as i32;
60    let dr = region as f64;
61    let r1 = x - S_PIO2_1 * dr;
62    let mut r = r1 - S_PIO2_1T * dr;
63    let er = (r.to_bits() << 1) >> 53;
64    if 15 < (ax >> 52) as i64 - er as i64 {
65        let t = S_PIO2_2 * dr;
66        let r2 = r1 - t;
67        let tail = S_PIO2_2T * dr - ((r1 - r2) - t);
68        r = r2 - tail;
69    }
70    (region as u32, r, er)
71}
72
73fn sin_poly(r: f64, x2: f64, s1: f64, s2: f64, s3: f64, s4: f64) -> f64 {
74    let x4 = x2 * x2;
75    let p = (s4 * x2 + s3) * x4 + (s2 * x2 + s1);
76    p * (r * x2) + r
77}
78
79/// The sine of `x` (radians), as the C runtime's `sinf` computes it.
80///
81/// Original: `FUN_140137160`.
82pub fn sinf(x: f32) -> f32 {
83    if x.to_bits() & 0x7f80_0000 == 0x7f80_0000 {
84        return f32::NAN;
85    }
86    let xd = x as f64;
87    let ax = xd.to_bits() & 0x7fff_ffff_ffff_ffff;
88    if ax <= 0x3fe9_21fb_5444_2d18 {
89        if ax < 0x3f80_0000_0000_0000 {
90            if ax < 0x3f20_0000_0000_0000 {
91                return x;
92            }
93            return (xd - ((xd * xd) * xd) * S_SIXTH) as f32;
94        }
95        let x2 = xd * xd;
96        return sin_poly(xd, x2, S_S1, S_S2, S_S3, S_S4) as f32;
97    }
98    let neg = xd.to_bits() != ax;
99    let v = if neg { 0.0 - xd } else { xd };
100    let (region, r, er) = if ax < S_BIG {
101        reduce(v, ax)
102    } else {
103        // `__remainder_piby2f_inline`: past 500000, which the game never passes.
104        return (xd.sin()) as f32;
105    };
106    let odd = region & 1 != 0;
107    let mut out = if er >= 0x3f2 {
108        let x2 = r * r;
109        if odd {
110            let x4 = x2 * x2;
111            let p = ((S_C4 * x2 + S_C3) * x4 + (S_C2 * x2 + S_C1)) * x4;
112            (S_C0 * x2 + S_ONE) + p
113        } else {
114            sin_poly(r, x2, S_S1, S_S2, S_S3, S_S4)
115        }
116    } else if er > 0x3de {
117        let x2 = r * r;
118        if odd {
119            S_ONE - x2 * S_HALF
120        } else {
121            r - (S_SIXTH * r) * x2
122        }
123    } else if odd {
124        S_ONE
125    } else {
126        r
127    };
128    let q = (region >> 1) & 1;
129    if q != neg as u32 {
130        out = 0.0 - out;
131    }
132    out as f32
133}
134
135/// The cosine of `x` (radians), as the C runtime's `cosf` computes it.
136///
137/// Original: `FUN_140136560`.
138pub fn cosf(x: f32) -> f32 {
139    if x.to_bits() & 0x7f80_0000 == 0x7f80_0000 {
140        return f32::NAN;
141    }
142    let xd = x as f64;
143    let ax = xd.to_bits() & 0x7fff_ffff_ffff_ffff;
144    let (region, r, er) = if ax <= 0x3fe9_21fb_5444_2d18 {
145        (0, xd, 0x3f2)
146    } else {
147        let v = if xd.to_bits() != ax { 0.0 - xd } else { xd };
148        if ax < S_BIG {
149            reduce(v, ax)
150        } else {
151            // `__remainder_piby2f_inline`: past 500000, which the game never passes.
152            return (xd.cos()) as f32;
153        }
154    };
155    let odd = region & 1 != 0;
156    let x2 = r * r;
157    let mut out = if er >= 0x3f2 {
158        let x4 = x2 * x2;
159        if odd {
160            ((C_S4 * x2 + C_S3) * x4 + (C_S2 * x2 + C_S1)) * (x2 * r) + r
161        } else {
162            let p = ((C_C4 * x2 + C_C3) * x4 + (C_C2 * x2 + C_C1)) * x4;
163            p - (S_HALF * x2 - S_ONE)
164        }
165    } else if er > 0x3de {
166        if odd {
167            r - (S_SIXTH * r) * x2
168        } else {
169            S_ONE - x2 * S_HALF
170        }
171    } else if odd {
172        r
173    } else {
174        S_ONE
175    };
176    if (region.wrapping_add(1)) & 2 != 0 {
177        out = 0.0 - out;
178    }
179    out as f32
180}
181
182/// The arc cosine of `x`, in radians, as the C runtime's `acosf` computes it.
183///
184/// Original: `FUN_140136090`.
185pub fn acosf(x: f32) -> f32 {
186    const P0: f32 = -0.013381929136812687;
187    const P1: f32 = 0.003961374517530203;
188    const P2: f32 = 0.05652986839413643;
189    const P3: f32 = 0.18416160345077515;
190    const Q0: f32 = 1.1049696207046509;
191    const Q1: f32 = 0.8364112973213196;
192    const PIO2_F: f32 = 1.5707963705062866;
193    const PI_F: f32 = 3.1415927410125732;
194    const PIO2_LO: f64 = 6.123233995736766e-17;
195    const PIO2: f64 = 1.5707963267948966;
196    const PI: f64 = 3.141592653589793;
197    let ux = x.to_bits();
198    let negative = ux & 0x8000_0000 != 0;
199    let e = ((ux >> 23) & 0xff) as i32 - 127;
200    if ux & 0x7fff_ffff > 0x7f80_0000 {
201        return f32::from_bits(ux | 0x0040_0000);
202    }
203    if e < -26 {
204        return PIO2_F;
205    }
206    if 0 <= e {
207        return if x == 1.0 {
208            0.0
209        } else if x == -1.0 {
210            PI_F
211        } else {
212            f32::NAN
213        };
214    }
215    let mut ax = if negative { -x } else { x };
216    let transform = -1 <= e;
217    let (r, s) = if transform {
218        let r = (1.0 - ax) * 0.5;
219        let s = r.sqrt();
220        ax = s;
221        (r, s)
222    } else {
223        (ax * ax, 0.0)
224    };
225    let mut p = P0 - r * P1;
226    p *= r;
227    p -= P2;
228    p *= r;
229    p += P3;
230    p *= r;
231    let q = Q0 - r * Q1;
232    let u = p / q;
233    if transform {
234        if negative {
235            let t = (((u * ax) as f64 - PIO2_LO) + s as f64) * 2.0;
236            (PI - t) as f32
237        } else {
238            let c = f32::from_bits(s.to_bits() & 0xffff_0000);
239            let a = (2.0 * ax) * u;
240            let b = ((r - c * c) / (c + s)) * 2.0;
241            b + a + c * 2.0
242        }
243    } else {
244        let t = (x as f64) - (PIO2_LO - (u * x) as f64);
245        (PIO2 - t) as f32
246    }
247}
248
249/// The angle of the point `(x, y)`, in radians from -π to π, as the C runtime's `atan2f`
250/// computes it.
251///
252/// Original: Runtime `0x180128f10`.
253pub fn atan2f(y: f32, x: f32) -> f32 {
254    const SIGN: u64 = 0x8000_0000_0000_0000;
255    const ABS: u64 = 0x7fff_ffff_ffff_ffff;
256    const INF: u64 = 0x7ff0_0000_0000_0000;
257    const PI: f64 = 3.141592653589793;
258    const PIO2: f64 = 1.5707963267948966;
259    const PIO4: f64 = 0.7853981633974483;
260    const PI3O4: f64 = 2.356194490192345;
261    let (yd, xd) = (y as f64, x as f64);
262    let (yb, xb) = (yd.to_bits(), xd.to_bits());
263    let (ysign, xsign) = (yb & SIGN != 0, xb & SIGN != 0);
264    let (ay, ax) = (yb & ABS, xb & ABS);
265    let diff = ((yb >> 52) & 0x7ff) as i32 - ((xb >> 52) & 0x7ff) as i32;
266    let signed = |v: f64, neg: bool| (if neg { -v } else { v }) as f32;
267    if ax > INF {
268        return f32::from_bits(x.to_bits() | 0x0040_0000);
269    }
270    if ay > INF {
271        return f32::from_bits(y.to_bits() | 0x0040_0000);
272    }
273    if ay == 0 {
274        return if xsign { signed(PI, ysign) } else { yd as f32 };
275    }
276    if ax == 0 && ysign {
277        return signed(PIO2, true);
278    }
279    if 26 < diff {
280        return signed(PIO2, ysign);
281    }
282    if diff < -13 && !xsign {
283        if diff < -150 {
284            return if ysign { -0.0 } else { 0.0 };
285        }
286        if diff >= -126 {
287            return (yd / xd) as f32;
288        }
289        // A result too small for a normal float: scaled by 2^100 and unscaled by hand.
290        let f = (d(0x4630_0000_0000_0000) * yd) / xd;
291        let sign = f.to_bits() & SIGN;
292        let a = f.to_bits() & ABS;
293        let e = ((a >> 52) & 0x7ff) as i32 - 100;
294        let bits = if 0 < e {
295            ((e as u64) << 52) | (a & 0x800f_ffff_ffff_ffff)
296        } else {
297            let m = (a & 0x801f_ffff_ffff_ffff) | 0x0010_0000_0000_0000;
298            let n = 1 - e;
299            if n <= 0x36 {
300                let m = m >> (n - 1);
301                (m >> 1) + (m & 1)
302            } else {
303                0
304            }
305        };
306        return f64::from_bits(bits | sign) as f32;
307    }
308    if diff < -26 && xsign {
309        return signed(PI, ysign);
310    }
311    if ay == INF && ax == INF {
312        return signed(if xsign { PI3O4 } else { PIO4 }, ysign);
313    }
314    let (mut small, mut big) = (f64::from_bits(ay), f64::from_bits(ax));
315    let swap = small > big;
316    if swap {
317        std::mem::swap(&mut small, &mut big);
318    }
319    let t = small / big;
320    let mut r = if t > 0.0625 {
321        let u = (t * 256.0 + 0.5) as i32;
322        let j = u as f64;
323        let dd = (small * 256.0 - j * big) / (j * small + big * 256.0);
324        let a = dd + d(ATAN_TABLE[(u - 16) as usize]);
325        a - ((dd * dd) * dd) * 0.33333333333224097
326    } else if 0.0001 > t {
327        t
328    } else {
329        let x2 = t * t;
330        let p = 0.3333333333333317 - (0.19999999999393223 - x2 * 0.1428571356180717) * x2;
331        t - p * (x2 * t)
332    };
333    if swap {
334        r = PIO2 - r;
335    }
336    if xsign {
337        r = PI - r;
338    }
339    if ysign {
340        r = -r;
341    }
342    r as f32
343}
344
345/// `atan(k / 256)` for k = 16..=256, each the double just below the exact value (the
346/// Runtime's table at `0x1801724a0`; computed here, see the test).
347const ATAN_TABLE: [u64; 241] = [
348    0x3faff55bb72cfde9,
349    0x3fb0f99ea71d52a6,
350    0x3fb1f86dbf082d58,
351    0x3fb2f719318a4a9a,
352    0x3fb3f59f0e7c559d,
353    0x3fb4f3fd677292fb,
354    0x3fb5f2324fd2d7b2,
355    0x3fb6f03bdcea4b0c,
356    0x3fb7ee182602f10e,
357    0x3fb8ebc54478fb28,
358    0x3fb9e94153cfdcf1,
359    0x3fbae68a71c722b8,
360    0x3fbbe39ebe6f07c3,
361    0x3fbce07c5c3cca32,
362    0x3fbddd21701eba6e,
363    0x3fbed98c2190043a,
364    0x3fbfd5ba9aac2f6d,
365    0x3fc068d584212b3d,
366    0x3fc0e6adccf40881,
367    0x3fc1646541060850,
368    0x3fc1e1fafb043726,
369    0x3fc25f6e171a535c,
370    0x3fc2dcbdb2fba1ff,
371    0x3fc359e8edeb99a3,
372    0x3fc3d6eee8c6626c,
373    0x3fc453cec6092a9e,
374    0x3fc4d087a9da4f17,
375    0x3fc54d18ba11570a,
376    0x3fc5c9811e3ec269,
377    0x3fc645bfffb3aa73,
378    0x3fc6c1d4898933d8,
379    0x3fc73dbde8a7d201,
380    0x3fc7b97b4bce5b02,
381    0x3fc8350be398ebc7,
382    0x3fc8b06ee2879c28,
383    0x3fc92ba37d050271,
384    0x3fc9a6a8e96c8626,
385    0x3fca217e601081a5,
386    0x3fca9c231b403279,
387    0x3fcb1696574d780b,
388    0x3fcb90d7529260a2,
389    0x3fcc0ae54d768466,
390    0x3fcc84bf8a742e6d,
391    0x3fccfe654e1d5395,
392    0x3fcd77d5df205736,
393    0x3fcdf110864c9d9d,
394    0x3fce6a148e96ec4d,
395    0x3fcee2e1451d980c,
396    0x3fcf5b75f92c80dd,
397    0x3fcfd3d1fc40dbe4,
398    0x3fd025fa510665b5,
399    0x3fd061eea03d6290,
400    0x3fd09dc597d86362,
401    0x3fd0d97ee509acb3,
402    0x3fd1151a362431c9,
403    0x3fd150973a9ce546,
404    0x3fd18bf5a30bf178,
405    0x3fd1c735212dd883,
406    0x3fd2025567e47c95,
407    0x3fd23d562b381041,
408    0x3fd278372057ef45,
409    0x3fd2b2f7fd9b5fe2,
410    0x3fd2ed987a823cfe,
411    0x3fd328184fb58951,
412    0x3fd362773707ebcb,
413    0x3fd39cb4eb76157b,
414    0x3fd3d6d129271134,
415    0x3fd410cbad6c7d32,
416    0x3fd44aa436c2af09,
417    0x3fd4845a84d0c21b,
418    0x3fd4bdee586890e6,
419    0x3fd4f75f73869978,
420    0x3fd530ad9951cd49,
421    0x3fd569d88e1b4cd7,
422    0x3fd5a2e0175e0f4e,
423    0x3fd5dbc3fbbe768d,
424    0x3fd614840309cfe1,
425    0x3fd64d1ff635c1c5,
426    0x3fd685979f5fa6fd,
427    0x3fd6bdeac9cbd76c,
428    0x3fd6f61941e4def0,
429    0x3fd72e22d53aa2a9,
430    0x3fd7660752817501,
431    0x3fd79dc6899118d1,
432    0x3fd7d5604b63b3f7,
433    0x3fd80cd46a14b1d0,
434    0x3fd84422b8df95d7,
435    0x3fd87b4b0c1ebedb,
436    0x3fd8b24d394a1b25,
437    0x3fd8e92916f5cde8,
438    0x3fd91fde7cd0c662,
439    0x3fd9566d43a34907,
440    0x3fd98cd5454d6b18,
441    0x3fd9c3165cc58107,
442    0x3fd9f93066168001,
443    0x3fda2f233e5e530b,
444    0x3fda64eec3cc23fc,
445    0x3fda9a92d59e98cf,
446    0x3fdad00f5422058b,
447    0x3fdb056420ae9343,
448    0x3fdb3a911da65c6c,
449    0x3fdb6f962e737efb,
450    0x3fdba473378624a5,
451    0x3fdbd9281e528191,
452    0x3fdc0db4c94ec9ef,
453    0x3fdc42191ff11eb6,
454    0x3fdc76550aad71f8,
455    0x3fdcaa6872f3631b,
456    0x3fdcde53432c1350,
457    0x3fdd121566b7f2ad,
458    0x3fdd45aec9ec862b,
459    0x3fdd791f5a1226f4,
460    0x3fddac670561bb4f,
461    0x3fdddf85bb026974,
462    0x3fde127b6b0744af,
463    0x3fde4548066cf51a,
464    0x3fde77eb7f175a34,
465    0x3fdeaa65c7cf28c4,
466    0x3fdedcb6d43f8434,
467    0x3fdf0ede98f393cf,
468    0x3fdf40dd0b541417,
469    0x3fdf72b221a4e495,
470    0x3fdfa45dd3029258,
471    0x3fdfd5e0175fdf83,
472    0x3fe0039c73c1a40b,
473    0x3fe01c341e82422d,
474    0x3fe034b709250488,
475    0x3fe04d25314342e5,
476    0x3fe0657e94db30cf,
477    0x3fe07dc3324e9b38,
478    0x3fe095f30861a58f,
479    0x3fe0ae0e1639866c,
480    0x3fe0c6145b5b43da,
481    0x3fe0de05d7aa6f7c,
482    0x3fe0f5e28b67e295,
483    0x3fe10daa77307a0d,
484    0x3fe1255d9bfbd2a8,
485    0x3fe13cfbfb1b056e,
486    0x3fe1548596376469,
487    0x3fe16bfa6f5137e1,
488    0x3fe1835a88be7c13,
489    0x3fe19aa5e5299f99,
490    0x3fe1b1dc87904284,
491    0x3fe1c8fe7341f64f,
492    0x3fe1e00babdefeb3,
493    0x3fe1f7043557138a,
494    0x3fe20de813e823b1,
495    0x3fe224b74c1d192a,
496    0x3fe23b71e2cc9e6a,
497    0x3fe25217dd17e501,
498    0x3fe268a940696da6,
499    0x3fe27f261273d1b3,
500    0x3fe2958e59308e30,
501    0x3fe2abe21aded073,
502    0x3fe2c2215e024465,
503    0x3fe2d84c2961e48b,
504    0x3fe2ee628406cbca,
505    0x3fe30464753b090a,
506    0x3fe31a52048874be,
507    0x3fe3302b39b78856,
508    0x3fe345f01cce37bb,
509    0x3fe35ba0b60eccce,
510    0x3fe3713d0df6c503,
511    0x3fe386c52d3db11e,
512    0x3fe39c391cd41719,
513    0x3fe3b198e5e2564a,
514    0x3fe3c6e491c78dc4,
515    0x3fe3dc1c2a188504,
516    0x3fe3f13fb89e96f4,
517    0x3fe4064f47569f48,
518    0x3fe41b4ae06fea41,
519    0x3fe430328e4b26d5,
520    0x3fe445065b795b55,
521    0x3fe459c652badc7f,
522    0x3fe46e727efe4715,
523    0x3fe4830aeb5f7bfd,
524    0x3fe4978fa3269ee1,
525    0x3fe4ac00b1c71762,
526    0x3fe4c05e22de94e4,
527    0x3fe4d4a8023414e8,
528    0x3fe4e8de5bb6ec04,
529    0x3fe4fd013b7dd17e,
530    0x3fe51110adc5ed81,
531    0x3fe5250cbef1e9fa,
532    0x3fe538f57b89061e,
533    0x3fe54ccaf0362c8f,
534    0x3fe5608d29c70c34,
535    0x3fe5743c352b33b9,
536    0x3fe587d81f732fba,
537    0x3fe59b60f5cfab9d,
538    0x3fe5aed6c5909517,
539    0x3fe5c2399c244260,
540    0x3fe5d58987169b18,
541    0x3fe5e8c6941043cf,
542    0x3fe5fbf0d0d5cc49,
543    0x3fe60f084b46e05e,
544    0x3fe6220d115d7b8d,
545    0x3fe634ff312d1f3b,
546    0x3fe647deb8e20b8f,
547    0x3fe65aabb6c07b02,
548    0x3fe66d663923e086,
549    0x3fe6800e4e7e2857,
550    0x3fe692a40556fb6a,
551    0x3fe6a5276c4b0575,
552    0x3fe6b798920b3d98,
553    0x3fe6c9f7855c3198,
554    0x3fe6dc44551553ae,
555    0x3fe6ee7f10204aef,
556    0x3fe700a7c5784633,
557    0x3fe712be84295198,
558    0x3fe724c35b4fae7b,
559    0x3fe736b65a172dff,
560    0x3fe748978fba8e0f,
561    0x3fe75a670b82d8d8,
562    0x3fe76c24dcc6c6c0,
563    0x3fe77dd112ea22c7,
564    0x3fe78f6bbd5d315e,
565    0x3fe7a0f4eb9c19a2,
566    0x3fe7b26cad2e50fd,
567    0x3fe7c3d311a6092b,
568    0x3fe7d528289fa093,
569    0x3fe7e66c01c114fd,
570    0x3fe7f79eacb97898,
571    0x3fe808c03940694a,
572    0x3fe819d0b7158a4c,
573    0x3fe82ad036000005,
574    0x3fe83bbec5cdee22,
575    0x3fe84c9c7653f7ea,
576    0x3fe85d69576cc2c5,
577    0x3fe86e2578f87ae5,
578    0x3fe87ed0eadc5a2a,
579    0x3fe88f6bbd023118,
580    0x3fe89ff5ff57f1f7,
581    0x3fe8b06fc1cf3dfe,
582    0x3fe8c0d9145cf49d,
583    0x3fe8d13206f8c4ca,
584    0x3fe8e17aa99cc05d,
585    0x3fe8f1b30c44f167,
586    0x3fe901db3eeef187,
587    0x3fe911f35199833b,
588    0x3fe921fb54442d18,
589];
590
591#[cfg(test)]
592mod tests {
593    use super::*;
594
595    /// The table against the Runtime's own, when the binary is at hand.
596    #[test]
597    fn the_atan_table_is_the_runtimes() {
598        let path = concat!(
599            env!("CARGO_MANIFEST_DIR"),
600            "/../../game-data/original/Runtime-steam-x64.dll"
601        );
602        let Ok(img) = std::fs::read(path) else {
603            return;
604        };
605        // .rdata: virtual 0x16f000 at file offset 0x16d400 (the DLL's section table).
606        let va = 0x1801724a0u64 - 0x180000000;
607        let pe = u32::from_le_bytes(img[0x3c..0x40].try_into().unwrap()) as usize;
608        let n = u16::from_le_bytes(img[pe + 6..pe + 8].try_into().unwrap()) as usize;
609        let opt = u16::from_le_bytes(img[pe + 20..pe + 22].try_into().unwrap()) as usize;
610        let mut off = None;
611        for k in 0..n {
612            let o = pe + 24 + opt + 40 * k;
613            let rd =
614                |i: usize| u32::from_le_bytes(img[o + i..o + i + 4].try_into().unwrap()) as u64;
615            let (vsize, rva, raw) = (rd(8), rd(12), rd(20));
616            if rva <= va && va < rva + vsize {
617                off = Some((va - rva + raw) as usize);
618            }
619        }
620        let off = off.unwrap();
621        for (k, &want) in ATAN_TABLE.iter().enumerate() {
622            let got = u64::from_le_bytes(img[off + 8 * k..off + 8 * k + 8].try_into().unwrap());
623            assert_eq!(got, want, "entry {k}");
624        }
625    }
626
627    #[test]
628    fn plain_values() {
629        assert_eq!(sinf(0.0), 0.0);
630        assert_eq!(cosf(0.0), 1.0);
631        assert_eq!(acosf(1.0), 0.0);
632        assert!((atan2f(1.0, 1.0) - std::f32::consts::FRAC_PI_4).abs() < 1e-7);
633    }
634}