blob: b5da9cd065eeade16182c4d582a44cf5db11e502 [file] [log] [blame]
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include <cmath>
#include "avif/internal.h"
#include "gtest/gtest.h"
namespace avif {
namespace {
// Converts a double value to a fraction, and checks that the difference
// between numerator/denominator and v is below relative_tolerance.
void TestRoundTrip(double v, double relative_tolerance) {
// Unsigned.
if (v >= 0) {
uint32_t numerator, denominator;
ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &numerator, &denominator)) << v;
const double reconstructed = (double)numerator / denominator;
const double tolerance = v * relative_tolerance;
EXPECT_NEAR(reconstructed, v, tolerance)
<< "numerator " << (double)numerator << " denominator "
<< (double)denominator;
}
// Signed.
if (v <= INT32_MAX) {
for (double multiplier : {1.0, -1.0}) {
double v2 = v * multiplier;
int32_t numerator;
uint32_t denominator;
ASSERT_TRUE(avifDoubleToSignedFraction(v2, &numerator, &denominator))
<< v2;
const double reconstructed = (double)numerator / denominator;
const double tolerance = v * relative_tolerance;
EXPECT_NEAR(reconstructed, v2, tolerance)
<< "numerator " << (double)numerator << " denominator "
<< (double)denominator;
}
}
}
constexpr double kLotsOfDecimals = 0.14159265358979323846;
TEST(ToFractionTest, RoundTrip) {
// Whole numbers and simple fractions should match perfectly.
constexpr double kPerfectTolerance = 0.0;
TestRoundTrip(0.0, kPerfectTolerance);
TestRoundTrip(1.0, kPerfectTolerance);
TestRoundTrip(42.0, kPerfectTolerance);
TestRoundTrip(102356.0, kPerfectTolerance);
TestRoundTrip(102356456.0f, kPerfectTolerance);
TestRoundTrip(UINT32_MAX / 2.0, kPerfectTolerance);
TestRoundTrip((double)UINT32_MAX - 1.0, kPerfectTolerance);
TestRoundTrip((double)UINT32_MAX, kPerfectTolerance);
TestRoundTrip(0.123, kPerfectTolerance);
TestRoundTrip(1.0 / 3.0, kPerfectTolerance);
TestRoundTrip(1.0 / 4.0, kPerfectTolerance);
TestRoundTrip(3.0 / 23.0, kPerfectTolerance);
TestRoundTrip(1253456.456, kPerfectTolerance);
TestRoundTrip(8598533.9, kPerfectTolerance);
// Numbers with a lot of decimals or very large/small can show a small
// error.
constexpr double kSmallTolerance = 1e-9;
TestRoundTrip(0.0123456, kSmallTolerance);
TestRoundTrip(3 + kLotsOfDecimals, kSmallTolerance);
TestRoundTrip(sqrt(2.0), kSmallTolerance);
TestRoundTrip(exp(1.0), kSmallTolerance);
TestRoundTrip(exp(10.0), kSmallTolerance);
TestRoundTrip(exp(15.0), kSmallTolerance);
// The golden ratio, the irrational number that is the "most difficult" to
// approximate rationally according to Wikipedia.
const double kGoldenRatio = (1.0 + std::sqrt(5.0)) / 2.0;
TestRoundTrip(kGoldenRatio, kSmallTolerance); // Golden ratio.
TestRoundTrip(((double)UINT32_MAX) - 0.5, kSmallTolerance);
// Note that values smaller than this might have a larger relative error
// (e.g. 1.0e-10).
TestRoundTrip(4.2e-10, kSmallTolerance);
}
// Tests the max difference between the fraction-ified value and the original
// value, for a subset of values between 0.0 and UINT32_MAX.
TEST(ToFractionTest, MaxDifference) {
double max_error = 0;
double max_error_v = 0;
double max_relative_error = 0;
double max_relative_error_v = 0;
for (uint64_t i = 0; i < UINT32_MAX; i += 1000) {
const double v = i + kLotsOfDecimals;
uint32_t numerator, denominator;
ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &numerator, &denominator)) << v;
const double reconstructed = (double)numerator / denominator;
const double error = abs(reconstructed - v);
const double relative_error = error / v;
if (error > max_error) {
max_error = error;
max_error_v = v;
}
if (relative_error > max_relative_error) {
max_relative_error = relative_error;
max_relative_error_v = v;
}
}
EXPECT_LE(max_error, 0.5f) << max_error_v;
EXPECT_LT(max_relative_error, 1e-9) << max_relative_error_v;
}
// Tests the max difference between the fraction-ified value and the original
// value, for a subset of values between 0 and 1.0/UINT32_MAX.
TEST(ToFractionTest, MaxDifferenceSmall) {
double max_error = 0;
double max_error_v = 0;
double max_relative_error = 0;
double max_relative_error_v = 0;
for (uint64_t i = 1; i < UINT32_MAX; i += 1000) {
const double v = 1.0 / (i + kLotsOfDecimals);
uint32_t numerator, denominator;
ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &numerator, &denominator)) << v;
const double reconstructed = (double)numerator / denominator;
const double error = abs(reconstructed - v);
const double relative_error = error / v;
if (error > max_error) {
max_error = error;
max_error_v = v;
}
if (relative_error > max_relative_error) {
max_relative_error = relative_error;
max_relative_error_v = v;
}
}
EXPECT_LE(max_error, 1e-10) << max_error_v;
EXPECT_LT(max_relative_error, 1e-5) << max_relative_error_v;
}
TEST(ToFractionTest, BadValues) {
uint32_t numerator, denominator;
// Negative value.
EXPECT_FALSE(avifDoubleToUnsignedFraction(-0.1, &numerator, &denominator));
// Too large.
EXPECT_FALSE(avifDoubleToUnsignedFraction(((double)UINT32_MAX) + 1.0,
&numerator, &denominator));
}
} // namespace
} // namespace avif