blob: 5bce9e885ad4a3ae8bf3da4ca248b70ecd6f0078 [file] [log] [blame]
/*
* 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/base/SkFloatBits.h"
#include "include/private/base/SkFloatingPoint.h"
#include "include/private/base/SkTo.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 },
};
static const struct {
const SkPoint* fPts;
int fCount;
bool fIsFinite;
} gSets[] = {
{ gF0, std::size(gF0), true },
{ gF1, std::size(gF1), true },
{ gI0, std::size(gI0), false },
{ gI1, std::size(gI1), false },
{ gI2, std::size(gI2), false },
{ gI3, std::size(gI3), false },
};
for (size_t i = 0; i < std::size(gSets); ++i) {
SkRect r;
r.setBounds(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(sk_float_isfinite(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, !!sk_float_isfinite(value) == (fc == kFinite));
REPORTER_ASSERT(reporter, !!sk_float_isinf(value) == (fc == kInfinite));
REPORTER_ASSERT(reporter, !!sk_float_isnan(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 Rec data[] = {
{ 0, true },
{ 1, true },
{ -1, true },
{ max * 0.75f, true },
{ max, true },
{ -max * 0.75f, true },
{ -max, true },
{ inf, false },
{ -inf, false },
{ nan, false },
};
const IsFiniteProc1 gProc1[] = {
isFinite_int,
isFinite_float,
isFinite_mulzero
};
const IsFiniteProc2 gProc2[] = {
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);
}