b544a314b3
GitOrigin-RevId: ec25bf2e416c3689477e82946fb69de2e53b9161
880 lines
32 KiB
C++
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
|