mediapipe/mediapipe/framework/deps/mathutil_unittest.cc
MediaPipe Team b544a314b3 Project import generated by Copybara.
GitOrigin-RevId: ec25bf2e416c3689477e82946fb69de2e53b9161
2021-06-10 01:38:18 -04:00

880 lines
32 KiB
C++

// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Test functions in MathUtil.
#include "mediapipe/framework/deps/mathutil.h"
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <ostream>
#include "mediapipe/framework/port/benchmark.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h"
namespace {
TEST(MathUtil, Round) {
// test float rounding
EXPECT_EQ(mediapipe::MathUtil::FastIntRound(0.7f), 1);
EXPECT_EQ(mediapipe::MathUtil::FastIntRound(5.7f), 6);
EXPECT_EQ(mediapipe::MathUtil::FastIntRound(6.3f), 6);
EXPECT_EQ(mediapipe::MathUtil::FastIntRound(1000000.7f), 1000001);
// test that largest representable number below 0.5 rounds to zero.
// this is important because naive implementation of round:
// static_cast<int>(r + 0.5f) is 1 due to implicit rounding in operator+
float rf = std::nextafter(0.5f, .0f);
EXPECT_LT(rf, 0.5f);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(rf), 0);
// same test for double
double rd = std::nextafter(0.5, 0.0);
EXPECT_LT(rd, 0.5);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(rd), 0);
// same test for long double
long double rl = std::nextafter(0.5l, 0.0l);
EXPECT_LT(rl, 0.5l);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(rl), 0);
}
static void BM_IntCast(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += static_cast<int>(x);
x += 0.1;
sum += static_cast<int>(x);
x += 0.1;
sum += static_cast<int>(x);
x += 0.1;
sum += static_cast<int>(x);
x += 0.1;
sum += static_cast<int>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_IntCast);
static void BM_Int64Cast(benchmark::State& state) {
double x = 0.1;
int64 sum = 0;
for (auto _ : state) {
sum += static_cast<int64>(x);
x += 0.1;
sum += static_cast<int64>(x);
x += 0.1;
sum += static_cast<int64>(x);
x += 0.1;
sum += static_cast<int64>(x);
x += 0.1;
sum += static_cast<int64>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_Int64Cast);
static void BM_IntRound(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_IntRound);
static void BM_FastIntRound(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
sum += mediapipe::MathUtil::FastIntRound(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_FastIntRound);
static void BM_Int64Round(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<int64>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_Int64Round);
static void BM_UintRound(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
sum += mediapipe::MathUtil::Round<uint32>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_UintRound);
static void BM_SafeIntCast(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_SafeIntCast);
static void BM_SafeInt64Cast(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeCast<int64>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_SafeInt64Cast);
static void BM_SafeIntRound(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_SafeIntRound);
static void BM_SafeInt64Round(benchmark::State& state) {
double x = 0.1;
int sum = 0;
for (auto _ : state) {
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
sum += mediapipe::MathUtil::SafeRound<int64>(x);
x += 0.1;
}
EXPECT_NE(sum, 0); // Don't let 'sum' get optimized away.
}
BENCHMARK(BM_SafeInt64Round);
TEST(MathUtil, IntRound) {
EXPECT_EQ(mediapipe::MathUtil::Round<int>(0.0), 0);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(0.49), 0);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(1.49), 1);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(-0.49), 0);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(-1.49), -1);
// Either adjacent integer is an acceptable result.
EXPECT_EQ(fabs(mediapipe::MathUtil::Round<int>(0.5) - 0.5), 0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::Round<int>(1.5) - 1.5), 0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::Round<int>(-0.5) + 0.5), 0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::Round<int>(-1.5) + 1.5), 0.5);
EXPECT_EQ(mediapipe::MathUtil::Round<int>(static_cast<double>(0x76543210)),
0x76543210);
// A double-precision number has a 53-bit mantissa (52 fraction bits),
// so the following value can be represented exactly.
int64 value64 = static_cast<int64_t>(0x1234567890abcd00);
EXPECT_EQ(mediapipe::MathUtil::Round<int64>(static_cast<double>(value64)),
value64);
}
template <class F>
F NextAfter(F x, F y);
template <>
float NextAfter(float x, float y) {
return nextafterf(x, y);
}
template <>
double NextAfter(double x, double y) {
return nextafter(x, y);
}
template <class FloatIn, class IntOut>
class SafeCastTester {
public:
static void Run() {
const IntOut imax = std::numeric_limits<IntOut>::max();
EXPECT_GT(imax, 0);
const IntOut imin = std::numeric_limits<IntOut>::min();
const bool s = std::numeric_limits<IntOut>::is_signed;
if (s) {
EXPECT_LT(imin, 0);
} else {
EXPECT_EQ(0, imin);
}
// Some basic tests.
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(0.0)),
0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-0.0)),
0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(0.99)),
0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(1.0)),
1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(1.01)),
1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(1.99)),
1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(2.0)),
2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(2.01)),
2);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-0.99)), 0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-1.0)),
s ? -1 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-1.01)),
s ? -1 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-1.99)),
s ? -1 : 0);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-2.0)),
s ? -2 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-2.01)),
s ? -2 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(117.9)),
117);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(118.0)),
118);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(118.1)),
118);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-117.9)),
s ? -117 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-118.0)),
s ? -118 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(-118.1)),
s ? -118 : 0);
// Some edge cases.
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
std::numeric_limits<FloatIn>::max()),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
-std::numeric_limits<FloatIn>::max()),
imin);
const FloatIn inf_val = std::numeric_limits<FloatIn>::infinity();
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(inf_val), imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(-inf_val), imin);
const FloatIn nan_val = inf_val - inf_val;
EXPECT_TRUE(std::isnan(nan_val));
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(nan_val), 0);
// Some larger numbers.
if (sizeof(IntOut) >= 32) {
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(0x76543210)),
0x76543210);
}
if (sizeof(FloatIn) >= 64) {
// A double-precision number has a 53-bit mantissa (52 fraction bits),
// so the following value can be represented exactly by a double.
int64 value64 = static_cast<int64_t>(0x1234567890abcd00);
const IntOut expected =
(sizeof(IntOut) >= 64) ? static_cast<IntOut>(value64) : imax;
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(value64)),
expected);
}
// Check values near imin and imax
static const int kLoopCount = 10;
{
// Values greater than or equal to imax should convert to imax
FloatIn v = static_cast<FloatIn>(imax);
for (int i = 0; i < kLoopCount; i++) {
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(v), imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(v + 10000.)),
imax);
v = NextAfter(v, std::numeric_limits<FloatIn>::max());
}
}
{
// Values less than or equal to imin should convert to imin
FloatIn v = static_cast<FloatIn>(imin);
for (int i = 0; i < kLoopCount; i++) {
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(v), imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(v - 10000.)),
imin);
v = NextAfter(v, -std::numeric_limits<FloatIn>::max());
}
}
{
// Values slightly less than imax which can be exactly represented as a
// FloatIn should convert exactly to themselves.
IntOut v = imax;
for (int i = 0; i < kLoopCount; i++) {
v = std::min<IntOut>(v - 1,
NextAfter(static_cast<FloatIn>(v),
-std::numeric_limits<FloatIn>::max()));
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(v)), v);
}
}
{
// Values slightly greater than imin which can be exactly represented as a
// FloatIn should convert exactly to themselves.
IntOut v = imin;
for (int i = 0; i < kLoopCount; i++) {
v = std::max<IntOut>(v + 1,
NextAfter(static_cast<FloatIn>(v),
std::numeric_limits<FloatIn>::max()));
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(v)), v);
}
}
// When FloatIn is wider than IntOut, we can test that fractional conversion
// near imax works as expected.
if (sizeof(FloatIn) > sizeof(IntOut)) {
{
// Values slightly less than imax should convert to imax - 1
FloatIn v = static_cast<FloatIn>(imax);
for (int i = 0; i < kLoopCount; i++) {
v = NextAfter(static_cast<FloatIn>(v),
-std::numeric_limits<FloatIn>::max());
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(v)),
imax - 1);
}
}
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.1)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.99)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 1.0)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 1.99)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 2.0)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.1)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.99)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.0)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.01)),
imax - 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.99)),
imax - 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 2.0)),
imax - 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 2.01)),
imax - 3);
}
// When FloatIn is wider than IntOut, and IntOut is signed, we can test
// that fractional conversion near imin works as expected.
if (s && (sizeof(FloatIn) > sizeof(IntOut))) {
{
// Values just over imin should convert to imin + 1
FloatIn v = static_cast<FloatIn>(imin);
for (int i = 0; i < kLoopCount; i++) {
v = NextAfter(static_cast<FloatIn>(v),
std::numeric_limits<FloatIn>::max());
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<IntOut>(static_cast<FloatIn>(v)),
imin + 1);
}
}
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.1)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.99)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 1.0)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.99)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 2.0)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.1)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.99)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.0)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.01)),
imin + 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.99)),
imin + 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 2.0)),
imin + 2);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 2.01)),
imin + 3);
}
}
};
TEST(MathUtil, SafeCast) {
SafeCastTester<float, int8>::Run();
SafeCastTester<double, int8>::Run();
SafeCastTester<float, int16>::Run();
SafeCastTester<double, int16>::Run();
SafeCastTester<float, int32>::Run();
SafeCastTester<double, int32>::Run();
SafeCastTester<float, int64>::Run();
SafeCastTester<double, int64>::Run();
SafeCastTester<float, uint8>::Run();
SafeCastTester<double, uint8>::Run();
SafeCastTester<float, uint16>::Run();
SafeCastTester<double, uint16>::Run();
SafeCastTester<float, uint32>::Run();
SafeCastTester<double, uint32>::Run();
SafeCastTester<float, uint64>::Run();
SafeCastTester<double, uint64>::Run();
// Spot-check SafeCast<int>
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(static_cast<float>(12345.678)),
12345);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(static_cast<float>(12345.4321)),
12345);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(static_cast<double>(-12345.678)),
-12345);
EXPECT_EQ(
mediapipe::MathUtil::SafeCast<int>(static_cast<double>(-12345.4321)),
-12345);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(1E47), 2147483647);
EXPECT_EQ(mediapipe::MathUtil::SafeCast<int>(-1E47),
static_cast<int64_t>(-2147483648));
}
template <class FloatIn, class IntOut>
class SafeRoundTester {
public:
static void Run() {
const IntOut imax = std::numeric_limits<IntOut>::max();
EXPECT_GT(imax, 0);
const IntOut imin = std::numeric_limits<IntOut>::min();
const bool s = std::numeric_limits<IntOut>::is_signed;
if (s) {
EXPECT_LT(imin, 0);
} else {
EXPECT_EQ(0, imin);
}
// Some basic tests.
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(0.0)),
0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-0.0)), 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(0.49)), 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(0.51)), 1);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(1.49)), 1);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(1.51)), 2);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-0.49)), 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-0.51)),
s ? -1 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-1.49)),
s ? -1 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-1.51)),
s ? -2 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(117.4)),
117);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(117.6)),
118);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-117.4)),
s ? -117 : 0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-117.6)),
s ? -118 : 0);
// At the midpoint between ints, either adjacent int is an acceptable
// result.
EXPECT_EQ(
fabs(mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(0.5)) -
0.5),
0.5);
EXPECT_EQ(
fabs(mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(1.5)) -
1.5),
0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(117.5)) -
117.5),
0.5);
if (s) {
EXPECT_EQ(fabs(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(-0.5)) +
0.5),
0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(-1.5)) +
1.5),
0.5);
EXPECT_EQ(fabs(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(-117.5)) +
117.5),
0.5);
} else {
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-0.5)),
0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-1.5)),
0);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(-117.5)),
0);
}
// Some edge cases.
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
std::numeric_limits<FloatIn>::max()),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
-std::numeric_limits<FloatIn>::max()),
imin);
const FloatIn inf_val = std::numeric_limits<FloatIn>::infinity();
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(inf_val), imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(-inf_val), imin);
const FloatIn nan_val = inf_val - inf_val;
EXPECT_TRUE(std::isnan(nan_val));
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(nan_val), 0);
// Some larger numbers.
if (sizeof(IntOut) >= 32) {
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(0x76543210)),
0x76543210);
}
if (sizeof(FloatIn) >= 64) {
// A double-precision number has a 53-bit mantissa (52 fraction bits),
// so the following value can be represented exactly by a double.
int64 value64 = static_cast<int64_t>(0x1234567890abcd00);
const IntOut expected =
(sizeof(IntOut) >= 64) ? static_cast<IntOut>(value64) : imax;
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(value64)),
expected);
}
// Check values near imin and imax
static const int kLoopCount = 10;
{
// Values greater than or equal to imax should round to imax
FloatIn v = static_cast<FloatIn>(imax);
for (int i = 0; i < kLoopCount; i++) {
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(v), imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(v + 10000.)),
imax);
v = NextAfter(v, std::numeric_limits<FloatIn>::max());
}
}
{
// Values less than or equal to imin should round to imin
FloatIn v = static_cast<FloatIn>(imin);
for (int i = 0; i < kLoopCount; i++) {
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(v), imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(v - 10000.)),
imin);
v = NextAfter(v, -std::numeric_limits<FloatIn>::max());
}
}
{
// Values slightly less than imax which can be exactly represented as a
// FloatIn should round exactly to themselves.
IntOut v = imax;
for (int i = 0; i < kLoopCount; i++) {
v = std::min<IntOut>(v - 1,
NextAfter(static_cast<FloatIn>(v),
-std::numeric_limits<FloatIn>::max()));
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(v)), v);
}
}
{
// Values slightly greater than imin which can be exactly represented as a
// FloatIn should round exactly to themselves.
IntOut v = imin;
for (int i = 0; i < kLoopCount; i++) {
v = std::max<IntOut>(v + 1,
NextAfter(static_cast<FloatIn>(v),
std::numeric_limits<FloatIn>::max()));
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(v)), v);
}
}
// When FloatIn is wider than IntOut, we can test that fractional rounding
// near imax works as expected.
if (sizeof(FloatIn) > sizeof(IntOut)) {
{
// Values slightly less than imax should round to imax
FloatIn v = static_cast<FloatIn>(imax);
for (int i = 0; i < kLoopCount; i++) {
v = NextAfter(static_cast<FloatIn>(v),
-std::numeric_limits<FloatIn>::max());
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(v)),
imax);
}
}
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.1)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.49)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.5)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.51)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) + 0.99)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.1)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.49)),
imax);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.51)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 0.99)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.49)),
imax - 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imax) - 1.51)),
imax - 2);
}
// When FloatIn is wider than IntOut, or if IntOut is unsigned, we can test
// that fractional rounding near imin works as expected.
if (!s || (sizeof(FloatIn) > sizeof(IntOut))) {
{
// Values slightly greater than imin should round to imin
FloatIn v = static_cast<FloatIn>(imin);
for (int i = 0; i < kLoopCount; i++) {
v = NextAfter(static_cast<FloatIn>(v),
std::numeric_limits<FloatIn>::max());
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<IntOut>(static_cast<FloatIn>(v)),
imin);
}
}
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.1)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.49)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.5)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.51)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) - 0.99)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.1)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.49)),
imin);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.51)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 0.99)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.49)),
imin + 1);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<IntOut>(
static_cast<FloatIn>(static_cast<FloatIn>(imin) + 1.51)),
imin + 2);
}
}
};
TEST(MathUtil, SafeRound) {
SafeRoundTester<float, int8>::Run();
SafeRoundTester<double, int8>::Run();
SafeRoundTester<float, int16>::Run();
SafeRoundTester<double, int16>::Run();
SafeRoundTester<float, int32>::Run();
SafeRoundTester<double, int32>::Run();
SafeRoundTester<float, int64>::Run();
SafeRoundTester<double, int64>::Run();
SafeRoundTester<float, uint8>::Run();
SafeRoundTester<double, uint8>::Run();
SafeRoundTester<float, uint16>::Run();
SafeRoundTester<double, uint16>::Run();
SafeRoundTester<float, uint32>::Run();
SafeRoundTester<double, uint32>::Run();
SafeRoundTester<float, uint64>::Run();
SafeRoundTester<double, uint64>::Run();
// Spot-check SafeRound<int>
EXPECT_EQ(mediapipe::MathUtil::SafeRound<int>(static_cast<float>(12345.678)),
12346);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<int>(static_cast<float>(12345.4321)),
12345);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<int>(static_cast<double>(-12345.678)),
-12346);
EXPECT_EQ(
mediapipe::MathUtil::SafeRound<int>(static_cast<double>(-12345.4321)),
-12345);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<int>(1E47), 2147483647);
EXPECT_EQ(mediapipe::MathUtil::SafeRound<int>(-1E47),
static_cast<int64_t>(-2147483648));
}
} // namespace