Update fallback rsqrt implementation to use optimal constants.
authorjvanverth <jvanverth@google.com>
Thu, 23 Jul 2015 18:14:29 +0000 (11:14 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 23 Jul 2015 18:14:29 +0000 (11:14 -0700)
Improves max relative error from 0.00175126 to 0.000650197.
Also add unit tests to check error bounds.

BUG=chromium:511458

Review URL: https://codereview.chromium.org/1251423002

include/core/SkFloatingPoint.h
tests/MathTest.cpp

index 7c34706f7a05093007aebbb131c0e6cbf26ba298..13e8963c140f68d995c7f8dd8485e0db26dddea2 100644 (file)
@@ -151,6 +151,8 @@ static inline float sk_float_rsqrt(const float x) {
 //
 // We do one step of Newton's method to refine the estimates in the NEON and null paths.  No
 // refinement is faster, but very innacurate.  Two steps is more accurate, but slower than 1/sqrt.
+//
+// Optimized constants in the null path courtesy of http://rrrola.wz.cz/inv_sqrt.html
 #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1
     return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x)));
 #elif defined(SK_ARM_HAS_NEON)
@@ -165,12 +167,12 @@ static inline float sk_float_rsqrt(const float x) {
 #else
     // Get initial estimate.
     int i = *SkTCast<int*>(&x);
-    i = 0x5f3759df - (i>>1);
+    i = 0x5F1FFFF9 - (i>>1);
     float estimate = *SkTCast<float*>(&i);
 
     // One step of Newton's method to refine.
     const float estimate_sq = estimate*estimate;
-    estimate *= (1.5f-0.5f*x*estimate_sq);
+    estimate *= 0.703952253f*(2.38924456f-x*estimate_sq);
     return estimate;
 #endif
 }
index 772bade1856cbe8b70bbc32eb8125419047372c9..3c2bc644073ddcd211e732c645a28fd894a4b00d 100644 (file)
@@ -382,6 +382,40 @@ static void unittest_half(skiatest::Reporter* reporter) {
 
 }
 
+static void test_rsqrt(skiatest::Reporter* reporter) {
+    const float maxRelativeError = 6.50196699e-4f;
+
+    // test close to 0 up to 1
+    float input = 0.000001f;
+    for (int i = 0; i < 1000; ++i) {
+        float exact = 1.0f/sk_float_sqrt(input);
+        float estimate = sk_float_rsqrt(input);
+        float relativeError = sk_float_abs(exact - estimate)/exact;
+        REPORTER_ASSERT(reporter, relativeError <= maxRelativeError);
+        input += 0.001f;
+    }
+
+    // test 1 to ~100
+    input = 1.0f;
+    for (int i = 0; i < 1000; ++i) {
+        float exact = 1.0f/sk_float_sqrt(input);
+        float estimate = sk_float_rsqrt(input);
+        float relativeError = sk_float_abs(exact - estimate)/exact;
+        REPORTER_ASSERT(reporter, relativeError <= maxRelativeError);
+        input += 0.01f;
+    }
+
+    // test some big numbers
+    input = 1000000.0f;
+    for (int i = 0; i < 100; ++i) {
+        float exact = 1.0f/sk_float_sqrt(input);
+        float estimate = sk_float_rsqrt(input);
+        float relativeError = sk_float_abs(exact - estimate)/exact;
+        REPORTER_ASSERT(reporter, relativeError <= maxRelativeError);
+        input += 754326.f;
+    }
+}
+
 static void test_muldiv255(skiatest::Reporter* reporter) {
     for (int a = 0; a <= 255; a++) {
         for (int b = 0; b <= 255; b++) {
@@ -521,6 +555,7 @@ DEF_TEST(Math, reporter) {
     unittest_fastfloat(reporter);
     unittest_isfinite(reporter);
     unittest_half(reporter);
+    test_rsqrt(reporter);
 
     for (i = 0; i < 10000; i++) {
         SkFixed numer = rand.nextS();