]> git.nega.tv - josh/narcissus/commitdiff
Fix some edge case handling in `sin_cos_pi`
authorJoshua Simmons <josh@nega.tv>
Sat, 15 Oct 2022 21:34:23 +0000 (23:34 +0200)
committerJoshua Simmons <josh@nega.tv>
Sat, 15 Oct 2022 21:34:23 +0000 (23:34 +0200)
Update tests as well.

narcissus-maths/src/sin_cos_pi.rs
narcissus-maths/tests/exhaustive_f32.rs

index 9d6f5fde77a7b732c85dff345e39ca68ca8bdea6..ce6de9f8605ad2b9df75300fac881323870e3a58 100644 (file)
@@ -42,6 +42,9 @@ pub fn sin_cos_pi_f32(a: f32) -> (f32, f32) {
     const S: [f32; 4] = F32_SIN_PI_7_K;
     const C: [f32; 4] = F32_COS_PI_8_K;
 
+    // cos_pi(a) = 1.0f for |a| > 2^24, but cos_pi(Inf) = NaN
+    let a = if a.abs() < 16777216.0 { a } else { a * 0.0 };
+
     // Range reduction.
     let r = (a + a).round();
     let i: u32 = unsafe { r.to_int_unchecked() };
@@ -67,11 +70,12 @@ pub fn sin_cos_pi_f32(a: f32) -> (f32, f32) {
     let s = s.mul_add(r2, S[1]);
     let s = r.mul_add(S[0], r3 * s);
 
-    if i & 1 != 0 {
-        (c, s)
-    } else {
-        (s, c)
-    }
+    let (s, c) = if i & 1 != 0 { (c, s) } else { (s, c) };
+
+    // IEEE-754: sin_pi(+n) is +0 and sin_pi(-n) is -0 for positive integers n
+    let s = if a == a.floor() { a * 0.0 } else { s };
+
+    (s, c)
 }
 
 #[cfg(test)]
@@ -80,6 +84,8 @@ mod tests {
 
     #[test]
     fn basics() {
+        assert_eq!(sin_cos_pi_f32(f32::from_bits(0x7f4135c6)), (0.0, 1.0));
+
         assert_eq!(sin_cos_pi_f32(-1.5), (1.0, 0.0));
         assert_eq!(sin_cos_pi_f32(-1.0), (0.0, -1.0));
         assert_eq!(sin_cos_pi_f32(-0.5), (-1.0, 0.0));
index 9d9f85cb7e32b80436295934d0d27c7a60e07e0e..02295f658bd782f5d4b6913b673a75afbe0aa3de 100644 (file)
@@ -77,6 +77,7 @@ struct FloatErrors {
 }
 
 const PREC: u32 = 280;
+const COUNT: u32 = 2_139_095_040_u32;
 
 struct FloatCheck {
     reference: fn(&mut rug::Float),
@@ -124,8 +125,6 @@ impl FloatCheck {
         let ref_val = self.tmp.to_f32_round(rug::float::Round::Nearest);
 
         if our_val != ref_val && !(our_val.is_nan() && ref_val.is_nan()) {
-            println!("u: {u:#08x}, our_val: {our_val}, ref_val: {ref_val}");
-
             self.errors.num_errors += 1;
             let e = ulp_error_f32(our_val, ref_val, x, self.reference);
             if e > self.errors.max_error_ulp {
@@ -136,9 +135,12 @@ impl FloatCheck {
     }
 }
 
-fn check_exhaustive_f32(reference: fn(&mut rug::Float), ours: fn(f32) -> f32, tan_mode: bool) {
-    const COUNT: u32 = 2_139_095_040_u32;
-    const SPLIT: u32 = 256;
+fn check_exhaustive_f32(
+    reference: fn(&mut rug::Float),
+    ours: fn(f32) -> f32,
+    tan_mode: bool,
+) -> FloatErrors {
+    const SPLIT: u32 = 512;
 
     // Generate ranges.
     assert_eq!(COUNT % SPLIT, 0);
@@ -205,14 +207,7 @@ fn check_exhaustive_f32(reference: fn(&mut rug::Float), ours: fn(f32) -> f32, ta
         }
     });
 
-    println!(
-        "errors: {} ({:.2}%)",
-        errors.num_errors,
-        (errors.num_errors as f64 / (COUNT * 2) as f64) * 100.0
-    );
-    println!("max error ulps: {}", errors.max_error_ulp);
-    println!("max error error at: {:#08x}", errors.max_error_val);
-    assert_eq!(errors.num_errors, 0);
+    errors
 }
 
 fn ref_sin_pi_f32(x: &mut rug::Float) {
@@ -230,17 +225,44 @@ fn ref_tan_pi_f32(x: &mut rug::Float) {
 #[test]
 #[ignore]
 pub fn exhaustive_sin_pi() {
-    check_exhaustive_f32(ref_sin_pi_f32, |a| sin_cos_pi_f32(a).0, false)
+    let errors = check_exhaustive_f32(ref_sin_pi_f32, |a| sin_cos_pi_f32(a).0, false);
+    println!(
+        "errors: {} ({:.2}%)",
+        errors.num_errors,
+        (errors.num_errors as f64 / (COUNT * 2) as f64) * 100.0
+    );
+    println!("max error ulps: {}", errors.max_error_ulp);
+    println!("max error error at: {:#08x}", errors.max_error_val);
+    assert_eq!(errors.max_error_ulp, 1);
+    assert_eq!(errors.num_errors, 715_786_430);
 }
 
 #[test]
 #[ignore]
 pub fn exhaustive_cos_pi() {
-    check_exhaustive_f32(ref_cos_pi_f32, |a| sin_cos_pi_f32(a).1, false)
+    let errors = check_exhaustive_f32(ref_cos_pi_f32, |a| sin_cos_pi_f32(a).1, false);
+    println!(
+        "errors: {} ({:.2}%)",
+        errors.num_errors,
+        (errors.num_errors as f64 / (COUNT * 2) as f64) * 100.0
+    );
+    println!("max error ulps: {}", errors.max_error_ulp);
+    println!("max error error at: {:#08x}", errors.max_error_val);
+    assert_eq!(errors.num_errors, 33_455_772);
+    assert_eq!(errors.max_error_ulp, 1);
 }
 
 #[test]
 #[ignore]
 pub fn exhaustive_tan_pi() {
-    check_exhaustive_f32(ref_tan_pi_f32, tan_pi_f32, true)
+    let errors = check_exhaustive_f32(ref_tan_pi_f32, tan_pi_f32, true);
+    println!(
+        "errors: {} ({:.2}%)",
+        errors.num_errors,
+        (errors.num_errors as f64 / (COUNT * 2) as f64) * 100.0
+    );
+    println!("max error ulps: {}", errors.max_error_ulp);
+    println!("max error error at: {:#08x}", errors.max_error_val);
+    assert_eq!(errors.num_errors, 68_630_324);
+    assert_eq!(errors.max_error_ulp, 2);
 }