| // 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 fraction.n/fraction.d and v is below relative_tolerance. | |
| void TestRoundTrip(double v, double relative_tolerance) { | |
| // Unsigned. | |
| if (v >= 0) { | |
| avifUnsignedFraction fraction; | |
| ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &fraction)) << v; | |
| const double reconstructed = (double)fraction.n / fraction.d; | |
| const double tolerance = v * relative_tolerance; | |
| EXPECT_NEAR(reconstructed, v, tolerance) | |
| << "fraction.n " << (double)fraction.n << " fraction.d " | |
| << (double)fraction.d; | |
| } | |
| // Signed. | |
| if (v <= INT32_MAX) { | |
| for (double multiplier : {1.0, -1.0}) { | |
| double v2 = v * multiplier; | |
| avifSignedFraction fraction; | |
| ASSERT_TRUE(avifDoubleToSignedFraction(v2, &fraction)) << v2; | |
| const double reconstructed = (double)fraction.n / fraction.d; | |
| const double tolerance = v * relative_tolerance; | |
| EXPECT_NEAR(reconstructed, v2, tolerance) | |
| << "fraction.n " << (double)fraction.n << " fraction.d " | |
| << (double)fraction.d; | |
| } | |
| } | |
| } | |
| 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; | |
| avifUnsignedFraction fraction; | |
| ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &fraction)) << v; | |
| const double reconstructed = (double)fraction.n / fraction.d; | |
| 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); | |
| avifUnsignedFraction fraction; | |
| ASSERT_TRUE(avifDoubleToUnsignedFraction(v, &fraction)) << v; | |
| const double reconstructed = (double)fraction.n / fraction.d; | |
| 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) { | |
| avifUnsignedFraction fraction; | |
| // Negative value. | |
| EXPECT_FALSE(avifDoubleToUnsignedFraction(-0.1, &fraction)); | |
| // Too large. | |
| EXPECT_FALSE( | |
| avifDoubleToUnsignedFraction(((double)UINT32_MAX) + 1.0, &fraction)); | |
| } | |
| } // namespace | |
| } // namespace avif |