Fix rounding for values near 1/2 and > 2^24
Bug: chromium:1366345, oss-fuzz:51634, oss-fuzz:51629
Change-Id: I721d3512b28b8963ec6224cabf28bc647b6ad956
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/583583
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/include/core/SkScalar.h b/include/core/SkScalar.h
index e1203ef..2a6bb3e 100644
--- a/include/core/SkScalar.h
+++ b/include/core/SkScalar.h
@@ -22,13 +22,14 @@
#define SK_ScalarTanPIOver8 0.414213562f
#define SK_ScalarRoot2Over2 0.707106781f
#define SK_ScalarMax 3.402823466e+38f
+#define SK_ScalarMin (-SK_ScalarMax)
#define SK_ScalarInfinity SK_FloatInfinity
#define SK_ScalarNegativeInfinity SK_FloatNegativeInfinity
#define SK_ScalarNaN SK_FloatNaN
#define SkScalarFloorToScalar(x) sk_float_floor(x)
#define SkScalarCeilToScalar(x) sk_float_ceil(x)
-#define SkScalarRoundToScalar(x) sk_float_floor((x) + 0.5f)
+#define SkScalarRoundToScalar(x) sk_float_round(x)
#define SkScalarTruncToScalar(x) sk_float_trunc(x)
#define SkScalarFloorToInt(x) sk_float_floor2int(x)
@@ -62,8 +63,6 @@
#define SkScalarToDouble(x) static_cast<double>(x)
#define SkDoubleToScalar(x) sk_double_to_float(x)
-#define SK_ScalarMin (-SK_ScalarMax)
-
static inline bool SkScalarIsNaN(SkScalar x) { return x != x; }
/** Returns true if x is not NaN and not infinite
@@ -78,26 +77,6 @@
return sk_floats_are_finite(array, count);
}
-/**
- * Variant of SkScalarRoundToInt, that performs the rounding step (adding 0.5) explicitly using
- * double, to avoid possibly losing the low bit(s) of the answer before calling floor().
- *
- * This routine will likely be slower than SkScalarRoundToInt(), and should only be used when the
- * extra precision is known to be valuable.
- *
- * In particular, this catches the following case:
- * SkScalar x = 0.49999997;
- * int ix = SkScalarRoundToInt(x);
- * SkASSERT(0 == ix); // <--- fails
- * ix = SkDScalarRoundToInt(x);
- * SkASSERT(0 == ix); // <--- succeeds
- */
-static inline int SkDScalarRoundToInt(SkScalar x) {
- double xx = x;
- xx += 0.5;
- return (int)floor(xx);
-}
-
/** Returns the fractional part of the scalar. */
static inline SkScalar SkScalarFraction(SkScalar x) {
return x - SkScalarTruncToScalar(x);
diff --git a/include/private/SkFloatingPoint.h b/include/private/SkFloatingPoint.h
index 0801162..8135abb 100644
--- a/include/private/SkFloatingPoint.h
+++ b/include/private/SkFloatingPoint.h
@@ -57,7 +57,10 @@
return radians * (180 / SK_FloatPI);
}
-#define sk_float_round(x) sk_float_floor((x) + 0.5f)
+// floor(double+0.5) vs. floorf(float+0.5f) give comparable performance, but upcasting to double
+// means tricky values like 0.49999997 and 2^24 get rounded correctly. If these were rounded
+// as floatf(x + .5f), they would be 1 higher than expected.
+#define sk_float_round(x) (float)sk_double_round((double)(x))
// can't find log2f on android, but maybe that just a tool bug?
#ifdef SK_BUILD_FOR_ANDROID
@@ -130,19 +133,19 @@
}
#define sk_float_floor2int(x) sk_float_saturate2int(sk_float_floor(x))
-#define sk_float_round2int(x) sk_float_saturate2int(sk_float_floor((x) + 0.5f))
+#define sk_float_round2int(x) sk_float_saturate2int(sk_float_round(x))
#define sk_float_ceil2int(x) sk_float_saturate2int(sk_float_ceil(x))
#define sk_float_floor2int_no_saturate(x) (int)sk_float_floor(x)
-#define sk_float_round2int_no_saturate(x) (int)sk_float_floor((x) + 0.5f)
+#define sk_float_round2int_no_saturate(x) (int)sk_float_round(x)
#define sk_float_ceil2int_no_saturate(x) (int)sk_float_ceil(x)
#define sk_double_floor(x) floor(x)
#define sk_double_round(x) floor((x) + 0.5)
#define sk_double_ceil(x) ceil(x)
-#define sk_double_floor2int(x) (int)floor(x)
-#define sk_double_round2int(x) (int)floor((x) + 0.5)
-#define sk_double_ceil2int(x) (int)ceil(x)
+#define sk_double_floor2int(x) (int)sk_double_floor(x)
+#define sk_double_round2int(x) (int)sk_double_round(x)
+#define sk_double_ceil2int(x) (int)sk_double_ceil(x)
// Cast double to float, ignoring any warning about too-large finite values being cast to float.
// Clang thinks this is undefined, but it's actually implementation defined to return either
diff --git a/tests/ScalarTest.cpp b/tests/ScalarTest.cpp
index dea4548..044eb2a 100644
--- a/tests/ScalarTest.cpp
+++ b/tests/ScalarTest.cpp
@@ -15,14 +15,23 @@
static void test_roundtoint(skiatest::Reporter* reporter) {
SkScalar x = 0.49999997f;
int ix = SkScalarRoundToInt(x);
- // We "should" get 0, since x < 0.5, but we don't due to float addition rounding up the low
+ int badIx = (int) floorf(x + 0.5f);
+ // We should get 0, since x < 0.5, but we wouldn't if SkScalarRoundToInt uses the commonly
+ // recommended approach shown in 'badIx' due to float addition rounding up the low
// bit after adding 0.5.
- REPORTER_ASSERT(reporter, 1 == ix);
-
- // This version explicitly performs the +0.5 step using double, which should avoid losing the
- // low bits.
- ix = SkDScalarRoundToInt(x);
REPORTER_ASSERT(reporter, 0 == ix);
+ REPORTER_ASSERT(reporter, 1 == badIx);
+
+ // Additionally, when the float value is between (2^23,2^24], it's precision is equal to
+ // 1 integral value. Adding 0.5f rounds up automatically *before* the floor, so naive
+ // rounding is also incorrect. Float values <= 2^23 and > 2^24 don't have this problem
+ // because either the sum can be represented sufficiently for floor() to do the right thing,
+ // or the sum will always round down to the integer multiple.
+ x = 8388609.f;
+ ix = SkScalarRoundToInt(x);
+ badIx = (int) floorf(x + 0.5f);
+ REPORTER_ASSERT(reporter, 8388609 == ix);
+ REPORTER_ASSERT(reporter, 8388610 == badIx);
}
struct PointSet {