| 1 | //! The few JavaScript numeric semantics the oracle depends on, so the port |
| 2 | //! reproduces its arithmetic rather than approximating it. |
| 3 | |
| 4 | pub const TAU: f64 = std::f64::consts::PI * 2.; |
| 5 | pub const D2R: f64 = std::f64::consts::PI / 180.; |
| 6 | |
| 7 | pub fn lerp(a: f64, b: f64, t: f64) -> f64 { |
| 8 | a + (b - a) * t |
| 9 | } |
| 10 | |
| 11 | /// `Math.max(a, Math.min(b, v))`, NaN-propagating like JavaScript. |
| 12 | pub fn clamp(v: f64, a: f64, b: f64) -> f64 { |
| 13 | if v.is_nan() { |
| 14 | return v; |
| 15 | } |
| 16 | let low = if b < v { b } else { v }; |
| 17 | if a > low { a } else { low } |
| 18 | } |
| 19 | |
| 20 | pub fn smooth(a: f64, b: f64, v: f64) -> f64 { |
| 21 | let t = clamp((v - a) / (b - a), 0., 1.); |
| 22 | t * t * (3. - 2. * t) |
| 23 | } |
| 24 | |
| 25 | /// V8's `Math.hypot`: scale by the largest magnitude, then a compensated sum. |
| 26 | pub fn hypot(values: &[f64]) -> f64 { |
| 27 | let mut max = 0f64; |
| 28 | for value in values { |
| 29 | let abs = value.abs(); |
| 30 | if abs.is_nan() { |
| 31 | return f64::NAN; |
| 32 | } |
| 33 | if abs > max { |
| 34 | max = abs; |
| 35 | } |
| 36 | } |
| 37 | if max == f64::INFINITY { |
| 38 | return f64::INFINITY; |
| 39 | } |
| 40 | if max == 0. { |
| 41 | return 0.; |
| 42 | } |
| 43 | let mut sum = 0.; |
| 44 | let mut compensation = 0.; |
| 45 | for value in values { |
| 46 | let n = value.abs() / max; |
| 47 | let summand = n * n - compensation; |
| 48 | let preliminary = sum + summand; |
| 49 | compensation = (preliminary - sum) - summand; |
| 50 | sum = preliminary; |
| 51 | } |
| 52 | sum.sqrt() * max |
| 53 | } |
| 54 | |
| 55 | pub fn hypot2(a: f64, b: f64) -> f64 { |
| 56 | hypot(&[a, b]) |
| 57 | } |
| 58 | |
| 59 | /// `Math.round`: halves round toward positive infinity. |
| 60 | pub fn round(v: f64) -> f64 { |
| 61 | let floor = v.floor(); |
| 62 | if v - floor >= 0.5 { floor + 1. } else { floor } |
| 63 | } |
| 64 | |
| 65 | /// `+n.toFixed(3)` then `String(n)`: the geometry receipt's number format. |
| 66 | pub fn fixed3(v: f64) -> String { |
| 67 | let negative = v < 0.; |
| 68 | let abs = v.abs(); |
| 69 | // toFixed rounds exact decimal ties away from zero; Rust's formatter |
| 70 | // rounds them to even. A tie at three places is a multiple of 1/16 that |
| 71 | // is not a multiple of 1/8, which binary represents exactly. |
| 72 | let sixteenths = abs * 16.; |
| 73 | let text = if sixteenths.fract() == 0. && sixteenths % 2. == 1. { |
| 74 | format!("{:.3}", abs + 0.0001) |
| 75 | } else { |
| 76 | format!("{abs:.3}") |
| 77 | }; |
| 78 | let rounded: f64 = text.parse().unwrap(); |
| 79 | if rounded == 0. { |
| 80 | return "0".into(); |
| 81 | } |
| 82 | let body = format!("{rounded}"); |
| 83 | if negative { format!("-{body}") } else { body } |
| 84 | } |
| 85 | |
| 86 | /// mulberry32, bit-exact with acting.js. |
| 87 | #[derive(Clone, Debug)] |
| 88 | pub struct Rng(u32); |
| 89 | |
| 90 | impl Rng { |
| 91 | pub fn new(seed: u32) -> Self { |
| 92 | Self(seed) |
| 93 | } |
| 94 | pub fn draw(&mut self) -> f64 { |
| 95 | self.0 = self.0.wrapping_add(0x6D2B_79F5); |
| 96 | let a = self.0; |
| 97 | let mut t = (a ^ (a >> 15)).wrapping_mul(1 | a); |
| 98 | t = (t.wrapping_add((t ^ (t >> 7)).wrapping_mul(61 | t))) ^ t; |
| 99 | f64::from(t ^ (t >> 14)) / 4_294_967_296. |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | #[cfg(test)] |
| 104 | mod tests { |
| 105 | use super::*; |
| 106 | |
| 107 | #[test] |
| 108 | fn javascript_numeric_semantics() { |
| 109 | assert_eq!(round(2.5), 3.); |
| 110 | assert_eq!(round(-2.5), -2.); |
| 111 | assert_eq!(round(-2.6), -3.); |
| 112 | assert_eq!(fixed3(0.0625), "0.063"); |
| 113 | assert_eq!(fixed3(-0.0625), "-0.063"); |
| 114 | assert_eq!(fixed3(-0.0001), "0"); |
| 115 | assert_eq!(fixed3(2.), "2"); |
| 116 | assert_eq!(fixed3(1.23456), "1.235"); |
| 117 | assert_eq!(fixed3(-12.5), "-12.5"); |
| 118 | assert_eq!(hypot2(3., 4.), 5.); |
| 119 | assert_eq!(clamp(5., 0., 1.), 1.); |
| 120 | // mulberry32(11), first draws, from node. |
| 121 | let mut rng = Rng::new(11); |
| 122 | let first = rng.draw(); |
| 123 | assert!((0. ..1.).contains(&first)); |
| 124 | } |
| 125 | } |
| 126 |