| /* | |
| * Copyright 2011 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/private/SkFloatingPoint.h" | |
| #include "include/private/SkTo.h" | |
| #include "src/core/SkFloatBits.h" | |
| #include "tests/Test.h" | |
| #include <array> | |
| #include <cmath> | |
| #include <cstddef> | |
| #include <cstdint> | |
| static void test_roundtoint(skiatest::Reporter* reporter) { | |
| SkScalar x = 0.49999997f; | |
| int ix = SkScalarRoundToInt(x); | |
| 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, 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 { | |
| const SkPoint* fPts; | |
| size_t fCount; | |
| bool fIsFinite; | |
| }; | |
| static void test_isRectFinite(skiatest::Reporter* reporter) { | |
| static const SkPoint gF0[] = { | |
| { 0, 0 }, { 1, 1 } | |
| }; | |
| static const SkPoint gF1[] = { | |
| { 0, 0 }, { 1, 1 }, { 99.234f, -42342 } | |
| }; | |
| static const SkPoint gI0[] = { | |
| { 0, 0 }, { 1, 1 }, { 99.234f, -42342 }, { SK_ScalarNaN, 3 }, { 2, 3 }, | |
| }; | |
| static const SkPoint gI1[] = { | |
| { 0, 0 }, { 1, 1 }, { 99.234f, -42342 }, { 3, SK_ScalarNaN }, { 2, 3 }, | |
| }; | |
| static const SkPoint gI2[] = { | |
| { 0, 0 }, { 1, 1 }, { 99.234f, -42342 }, { SK_ScalarInfinity, 3 }, { 2, 3 }, | |
| }; | |
| static const SkPoint gI3[] = { | |
| { 0, 0 }, { 1, 1 }, { 99.234f, -42342 }, { 3, SK_ScalarInfinity }, { 2, 3 }, | |
| }; | |
| struct GSets { | |
| const SkPoint* fPts; | |
| size_t fCount; | |
| bool fIsFinite; | |
| }; | |
| static const auto gSets = std::to_array<GSets>({ | |
| GSets{gF0, std::size(gF0), true}, | |
| GSets{gF1, std::size(gF1), true}, | |
| GSets{gI0, std::size(gI0), false}, | |
| GSets{gI1, std::size(gI1), false}, | |
| GSets{gI2, std::size(gI2), false}, | |
| GSets{gI3, std::size(gI3), false}, | |
| }); | |
| for (size_t i = 0; i < std::size(gSets); ++i) { | |
| SkRect r = SkRect::BoundsOrEmpty({gSets[i].fPts, gSets[i].fCount}); | |
| bool rectIsFinite = !r.isEmpty(); | |
| REPORTER_ASSERT(reporter, gSets[i].fIsFinite == rectIsFinite); | |
| } | |
| } | |
| static bool isFinite_int(float x) { | |
| uint32_t bits = SkFloat2Bits(x); // need unsigned for our shifts | |
| int exponent = bits << 1 >> 24; | |
| return exponent != 0xFF; | |
| } | |
| static bool isFinite_float(float x) { | |
| return SkToBool(SkIsFinite(x)); | |
| } | |
| static bool isFinite_mulzero(float x) { | |
| float y = x * 0; | |
| return y == y; | |
| } | |
| // return true if the float is finite | |
| typedef bool (*IsFiniteProc1)(float); | |
| static bool isFinite2_and(float x, float y, IsFiniteProc1 proc) { | |
| return proc(x) && proc(y); | |
| } | |
| static bool isFinite2_mulzeroadd(float x, float y, IsFiniteProc1 proc) { | |
| return proc(x * 0 + y * 0); | |
| } | |
| // return true if both floats are finite | |
| typedef bool (*IsFiniteProc2)(float, float, IsFiniteProc1); | |
| enum FloatClass { | |
| kFinite, | |
| kInfinite, | |
| kNaN | |
| }; | |
| static void test_floatclass(skiatest::Reporter* reporter, float value, FloatClass fc) { | |
| // our sk_float_is... function may return int instead of bool, | |
| // hence the double ! to turn it into a bool | |
| REPORTER_ASSERT(reporter, !!SkIsFinite(value) == (fc == kFinite)); | |
| REPORTER_ASSERT(reporter, !!std::isinf(value) == (fc == kInfinite)); | |
| REPORTER_ASSERT(reporter, !!SkIsNaN(value) == (fc == kNaN)); | |
| } | |
| #if defined _WIN32 | |
| #pragma warning(push) | |
| // we are intentionally causing an overflow here | |
| // (warning C4756: overflow in constant arithmetic) | |
| #pragma warning(disable : 4756) | |
| #endif | |
| static void test_isfinite(skiatest::Reporter* reporter) { | |
| struct Rec { | |
| float fValue; | |
| bool fIsFinite; | |
| }; | |
| float max = 3.402823466e+38f; | |
| float inf = max * max; | |
| float nan = inf * 0; | |
| test_floatclass(reporter, 0, kFinite); | |
| test_floatclass(reporter, max, kFinite); | |
| test_floatclass(reporter, -max, kFinite); | |
| test_floatclass(reporter, inf, kInfinite); | |
| test_floatclass(reporter, -inf, kInfinite); | |
| test_floatclass(reporter, nan, kNaN); | |
| test_floatclass(reporter, -nan, kNaN); | |
| const auto data = std::to_array<Rec>({ | |
| Rec{0, true}, | |
| Rec{1, true}, | |
| Rec{-1, true}, | |
| Rec{max * 0.75f, true}, | |
| Rec{max, true}, | |
| Rec{-max * 0.75f, true}, | |
| Rec{-max, true}, | |
| Rec{inf, false}, | |
| Rec{-inf, false}, | |
| Rec{nan, false}, | |
| }); | |
| static constexpr auto gProc1 = | |
| std::to_array<IsFiniteProc1>({isFinite_int, isFinite_float, isFinite_mulzero}); | |
| static constexpr auto gProc2 = | |
| std::to_array<IsFiniteProc2>({isFinite2_and, isFinite2_mulzeroadd}); | |
| size_t i, n = std::size(data); | |
| for (i = 0; i < n; ++i) { | |
| for (size_t k = 0; k < std::size(gProc1); ++k) { | |
| const Rec& rec = data[i]; | |
| bool finite = gProc1[k](rec.fValue); | |
| REPORTER_ASSERT(reporter, rec.fIsFinite == finite); | |
| } | |
| } | |
| for (i = 0; i < n; ++i) { | |
| const Rec& rec0 = data[i]; | |
| for (size_t j = 0; j < n; ++j) { | |
| const Rec& rec1 = data[j]; | |
| for (size_t k = 0; k < std::size(gProc1); ++k) { | |
| IsFiniteProc1 proc1 = gProc1[k]; | |
| for (size_t m = 0; m < std::size(gProc2); ++m) { | |
| bool finite = gProc2[m](rec0.fValue, rec1.fValue, proc1); | |
| bool finite2 = rec0.fIsFinite && rec1.fIsFinite; | |
| REPORTER_ASSERT(reporter, finite2 == finite); | |
| } | |
| } | |
| } | |
| } | |
| test_isRectFinite(reporter); | |
| } | |
| #if defined _WIN32 | |
| #pragma warning ( pop ) | |
| #endif | |
| DEF_TEST(Scalar, reporter) { | |
| test_isfinite(reporter); | |
| test_roundtoint(reporter); | |
| } |