mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-12-16 04:09:39 +00:00
Merge 4905916ef4 into ed2fe134aa
This commit is contained in:
commit
03f2dc4036
@ -128,6 +128,7 @@ add_library(common
|
||||
QoSSession.h
|
||||
Random.cpp
|
||||
Random.h
|
||||
Rational.h
|
||||
Result.h
|
||||
ScopeGuard.h
|
||||
SDCardUtil.cpp
|
||||
|
||||
@ -175,4 +175,15 @@ constexpr int IntLog2(u64 val)
|
||||
{
|
||||
return 63 - std::countl_zero(val);
|
||||
}
|
||||
|
||||
// Similar to operator<=> but negative signed values always compare less than unsigned values.
|
||||
constexpr auto Compare3Way(std::integral auto lhs, std::integral auto rhs)
|
||||
{
|
||||
if (std::cmp_less(lhs, rhs))
|
||||
return std::strong_ordering::less;
|
||||
if (std::cmp_less(rhs, lhs))
|
||||
return std::strong_ordering::greater;
|
||||
return std::strong_ordering::equal;
|
||||
}
|
||||
|
||||
} // namespace MathUtil
|
||||
|
||||
312
Source/Core/Common/Rational.h
Normal file
312
Source/Core/Common/Rational.h
Normal file
@ -0,0 +1,312 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
#include "Common/MathUtil.h"
|
||||
|
||||
namespace MathUtil
|
||||
{
|
||||
template <std::integral T>
|
||||
class Rational;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <typename T>
|
||||
concept RationalCompatible = requires(T x) { Rational(x); };
|
||||
|
||||
template <typename T>
|
||||
concept RationalAdjacent = std::is_arithmetic_v<T>;
|
||||
|
||||
// Allows for class template argument deduction within the class body.
|
||||
// Note: CTAD via alias template would be cleaner but was not implemented until Clang 19.
|
||||
template <typename Other>
|
||||
constexpr auto DeducedRational(Other other)
|
||||
{
|
||||
return Rational{other};
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// Rational number class template
|
||||
// Results are not currently automatically reduced.
|
||||
// Watch out for integer overflow after repeated operations.
|
||||
// Use Reduced / Approximated functions as needed to avoid overflow.
|
||||
template <std::integral T>
|
||||
class Rational final
|
||||
{
|
||||
public:
|
||||
using ValueType = T;
|
||||
|
||||
ValueType numerator{0};
|
||||
ValueType denominator{1};
|
||||
|
||||
// Conversion from integers
|
||||
constexpr Rational(T num = 0, T den = 1) : numerator{num}, denominator{den} {}
|
||||
|
||||
// Conversion from floating point
|
||||
// Properly converts nice values like 0.5f -> Rational(1, 2)
|
||||
// Inexact values like 0.3f won't convert particularly nicely.
|
||||
// But e.g. Rational(0.3f).Approximated(10) will produce Rational(3, 10)
|
||||
constexpr explicit Rational(std::floating_point auto float_value)
|
||||
{
|
||||
constexpr int dest_exp = IntLog2(std::numeric_limits<T>::max()) - 1;
|
||||
int exp = 0;
|
||||
const auto norm_frac = std::frexp(float_value, &exp);
|
||||
|
||||
const int num_exp = std::max(exp, dest_exp);
|
||||
numerator = SaturatingCast<T>(std::ldexp(norm_frac, num_exp));
|
||||
const int zeros = std::countr_zero(std::make_unsigned_t<T>(numerator));
|
||||
|
||||
const int den_exp = num_exp - exp;
|
||||
const int shift_right = std::min(den_exp, zeros);
|
||||
denominator = SaturatingCast<T>(std::ldexp(1, den_exp - shift_right));
|
||||
|
||||
numerator >>= shift_right;
|
||||
}
|
||||
|
||||
// Conversion from other Rational.
|
||||
template <std::integral Other>
|
||||
constexpr explicit Rational(Rational<Other> other)
|
||||
{
|
||||
if constexpr (std::is_unsigned_v<T> && std::is_signed_v<Other>)
|
||||
other = other.Normalized();
|
||||
numerator = SaturatingCast<T>(other.numerator);
|
||||
denominator = SaturatingCast<T>(other.denominator);
|
||||
}
|
||||
|
||||
// Potentially lossy conversion to int/float types
|
||||
template <detail::RationalAdjacent Target>
|
||||
constexpr explicit operator Target() const
|
||||
{
|
||||
return Target(numerator) / Target(denominator);
|
||||
}
|
||||
|
||||
constexpr bool IsInteger() const { return (numerator % denominator) == 0; }
|
||||
|
||||
constexpr Rational Inverted() const { return Rational(denominator, numerator); }
|
||||
|
||||
// Returns a copy with a non-negative denominator.
|
||||
constexpr Rational Normalized() const
|
||||
{
|
||||
return (denominator < T{0}) ? Rational(T{} - numerator, T{} - denominator) : *this;
|
||||
}
|
||||
|
||||
// Returns a reduced fraction.
|
||||
constexpr Rational Reduced() const
|
||||
{
|
||||
const auto gcd = std::gcd(numerator, denominator);
|
||||
return {T(numerator / gcd), T(denominator / gcd)};
|
||||
}
|
||||
|
||||
// Returns a reduced approximated fraction with a given maximum numerator/denominator.
|
||||
constexpr Rational Approximated(T max_num_den) const
|
||||
{
|
||||
// This algorithm comes from FFmpeg's av_reduce function (LGPLv2.1+)
|
||||
|
||||
const auto reduced_normalized = Reduced().Normalized();
|
||||
auto [num, den] = reduced_normalized;
|
||||
|
||||
const bool is_negative = num < T{0};
|
||||
if (is_negative)
|
||||
num *= -1;
|
||||
|
||||
if (num <= max_num_den && den <= max_num_den)
|
||||
return reduced_normalized;
|
||||
|
||||
Rational a0{0, 1};
|
||||
Rational a1{1, 0};
|
||||
|
||||
while (den != 0)
|
||||
{
|
||||
auto x = num / den;
|
||||
const auto next_den = num - (den * x);
|
||||
const Rational a2 = (Rational(x, x) * a1) & a0;
|
||||
|
||||
if (a2.numerator > max_num_den || a2.denominator > max_num_den)
|
||||
{
|
||||
if (a1.numerator != 0)
|
||||
x = (max_num_den - a0.numerator) / a1.numerator;
|
||||
|
||||
if (a1.denominator != 0)
|
||||
x = std::min(x, (max_num_den - a0.denominator) / a1.denominator);
|
||||
|
||||
if (den * (2 * x * a1.denominator + a0.denominator) > num * a1.denominator)
|
||||
a1 = {(Rational(x, x) * a1) & a0};
|
||||
break;
|
||||
}
|
||||
|
||||
a0 = a1;
|
||||
a1 = a2;
|
||||
num = den;
|
||||
den = next_den;
|
||||
}
|
||||
|
||||
return is_negative ? -a1 : a1;
|
||||
}
|
||||
|
||||
// Multiplication
|
||||
constexpr auto& operator*=(detail::RationalCompatible auto rhs)
|
||||
{
|
||||
const auto r = CommonRational(rhs);
|
||||
numerator *= r.numerator;
|
||||
denominator *= r.denominator;
|
||||
return *this;
|
||||
}
|
||||
constexpr friend auto operator*(detail::RationalCompatible auto lhs, Rational rhs)
|
||||
{
|
||||
return CommonRational(lhs) *= rhs;
|
||||
}
|
||||
constexpr friend auto operator*(Rational lhs, detail::RationalAdjacent auto rhs)
|
||||
{
|
||||
return lhs * CommonRational(rhs);
|
||||
}
|
||||
|
||||
// Division
|
||||
constexpr auto& operator/=(detail::RationalCompatible auto rhs)
|
||||
{
|
||||
return *this *= CommonRational(rhs).Inverted();
|
||||
}
|
||||
constexpr friend auto operator/(detail::RationalCompatible auto lhs, Rational rhs)
|
||||
{
|
||||
return CommonRational(lhs) *= rhs.Inverted();
|
||||
}
|
||||
constexpr friend auto operator/(Rational lhs, detail::RationalAdjacent auto rhs)
|
||||
{
|
||||
return lhs * CommonRational(rhs).Inverted();
|
||||
}
|
||||
|
||||
// Modulo (behaves like fmod)
|
||||
constexpr auto& operator%=(detail::RationalCompatible auto rhs)
|
||||
{
|
||||
const auto r = CommonRational(rhs);
|
||||
return *this -= (r * T(*this / r));
|
||||
}
|
||||
constexpr friend auto operator%(detail::RationalCompatible auto lhs, Rational rhs)
|
||||
{
|
||||
return CommonRational(lhs) %= rhs;
|
||||
}
|
||||
constexpr friend auto operator%(Rational lhs, detail::RationalAdjacent auto rhs)
|
||||
{
|
||||
return lhs % CommonRational(rhs);
|
||||
}
|
||||
|
||||
// Addition
|
||||
constexpr auto& operator+=(detail::RationalCompatible auto rhs)
|
||||
{
|
||||
const auto r = CommonRational(rhs);
|
||||
numerator *= r.denominator;
|
||||
numerator += r.numerator * denominator;
|
||||
denominator *= r.denominator;
|
||||
return *this;
|
||||
}
|
||||
constexpr friend auto operator+(detail::RationalCompatible auto lhs, Rational rhs)
|
||||
{
|
||||
return CommonRational(lhs) += rhs;
|
||||
}
|
||||
constexpr friend auto operator+(Rational lhs, detail::RationalAdjacent auto rhs)
|
||||
{
|
||||
return lhs + CommonRational(rhs);
|
||||
}
|
||||
|
||||
// Subtraction
|
||||
constexpr auto& operator-=(detail::RationalCompatible auto rhs)
|
||||
{
|
||||
const auto r = CommonRational(rhs);
|
||||
numerator *= r.denominator;
|
||||
numerator -= r.numerator * denominator;
|
||||
denominator *= r.denominator;
|
||||
return *this;
|
||||
}
|
||||
constexpr friend auto operator-(detail::RationalCompatible auto lhs, Rational rhs)
|
||||
{
|
||||
return CommonRational(lhs) -= rhs;
|
||||
}
|
||||
constexpr friend auto operator-(Rational lhs, detail::RationalAdjacent auto rhs)
|
||||
{
|
||||
return lhs - CommonRational(rhs);
|
||||
}
|
||||
|
||||
// Mediant (n1+n2)/(d1+d2)
|
||||
constexpr auto& operator&=(detail::RationalCompatible auto rhs)
|
||||
{
|
||||
const auto r = CommonRational(rhs);
|
||||
numerator += r.numerator;
|
||||
denominator += r.denominator;
|
||||
return *this;
|
||||
}
|
||||
constexpr friend auto operator&(detail::RationalCompatible auto lhs, Rational rhs)
|
||||
{
|
||||
return CommonRational(lhs) &= rhs;
|
||||
}
|
||||
constexpr friend auto operator&(Rational lhs, detail::RationalAdjacent auto rhs)
|
||||
{
|
||||
return lhs & CommonRational(rhs);
|
||||
}
|
||||
|
||||
// Comparison
|
||||
constexpr friend auto operator<=>(detail::RationalCompatible auto lhs, Rational rhs)
|
||||
{
|
||||
const auto left = detail::DeducedRational(lhs).Normalized();
|
||||
const auto lhs_q = left.numerator / left.denominator;
|
||||
const auto lhs_r = left.numerator % left.denominator;
|
||||
|
||||
rhs = rhs.Normalized();
|
||||
const auto rhs_q = rhs.numerator / rhs.denominator;
|
||||
const auto rhs_r = rhs.numerator % rhs.denominator;
|
||||
|
||||
// If integer division results differ we have a result.
|
||||
if (const auto cmp = Compare3Way(lhs_q, rhs_q); std::is_neq(cmp))
|
||||
return cmp;
|
||||
|
||||
// If at least one side has no remainder we have a result.
|
||||
if (lhs_r == 0 || rhs_r == 0)
|
||||
return Compare3Way(lhs_r, rhs_r);
|
||||
|
||||
// Recurse with inverted remainders.
|
||||
return Rational(rhs.denominator, rhs_r) <=> decltype(left)(left.denominator, lhs_r);
|
||||
}
|
||||
constexpr friend auto operator<=>(Rational lhs, detail::RationalAdjacent auto rhs)
|
||||
{
|
||||
return lhs <=> detail::DeducedRational(rhs);
|
||||
}
|
||||
|
||||
// Equality
|
||||
constexpr friend bool operator==(detail::RationalCompatible auto lhs, Rational rhs)
|
||||
{
|
||||
return std::is_eq(lhs <=> rhs);
|
||||
}
|
||||
constexpr friend bool operator==(Rational lhs, detail::RationalAdjacent auto rhs)
|
||||
{
|
||||
return std::is_eq(lhs <=> rhs);
|
||||
}
|
||||
|
||||
// Unary operators
|
||||
constexpr auto operator+() const { return *this; }
|
||||
constexpr auto operator-() const { return Rational(T{} - numerator, denominator); }
|
||||
constexpr auto& operator++() { return *this += 1; }
|
||||
constexpr auto& operator--() { return *this -= 1; }
|
||||
constexpr auto operator++(int) { return std::exchange(*this, *this + 1); }
|
||||
constexpr auto operator--(int) { return std::exchange(*this, *this - 1); }
|
||||
|
||||
constexpr explicit operator bool() const { return numerator != 0; }
|
||||
|
||||
private:
|
||||
// Constructs a common_type'd Rational<T> from an existing value.
|
||||
static constexpr auto CommonRational(auto val)
|
||||
{
|
||||
return Rational<
|
||||
std::common_type_t<T, typename decltype(detail::DeducedRational(val))::ValueType>>(val);
|
||||
}
|
||||
};
|
||||
|
||||
// Floating point deduction guides.
|
||||
Rational(float) -> Rational<s32>;
|
||||
Rational(double) -> Rational<s64>;
|
||||
|
||||
} // namespace MathUtil
|
||||
@ -348,8 +348,8 @@ bool AchievementManager::CanPause()
|
||||
OSD::AddMessage(
|
||||
fmt::format("RetroAchievements Hardcore Mode:\n"
|
||||
"Cannot pause until another {:.2f} seconds have passed.",
|
||||
static_cast<float>(frames_to_next_pause) /
|
||||
Core::System::GetInstance().GetVideoInterface().GetTargetRefreshRate()),
|
||||
float(frames_to_next_pause /
|
||||
Core::System::GetInstance().GetVideoInterface().GetTargetRefreshRate())),
|
||||
OSD::Duration::VERY_LONG, OSD::Color::RED);
|
||||
}
|
||||
return can_pause;
|
||||
|
||||
@ -397,8 +397,8 @@ void FifoPlayer::WriteFrame(const FifoFrameInfo& frame, const AnalyzedFrameInfo&
|
||||
{
|
||||
// Core timing information
|
||||
auto& vi = m_system.GetVideoInterface();
|
||||
m_CyclesPerFrame = static_cast<u64>(m_system.GetSystemTimers().GetTicksPerSecond()) *
|
||||
vi.GetTargetRefreshRateDenominator() / vi.GetTargetRefreshRateNumerator();
|
||||
m_CyclesPerFrame =
|
||||
u64(u64(m_system.GetSystemTimers().GetTicksPerSecond()) / vi.GetTargetRefreshRate());
|
||||
m_ElapsedCycles = 0;
|
||||
m_FrameFifoSize = static_cast<u32>(frame.fifoData.size());
|
||||
|
||||
|
||||
@ -81,8 +81,6 @@ void VideoInterfaceManager::DoState(PointerWrap& p)
|
||||
p.Do(m_fb_width);
|
||||
p.Do(m_border_hblank);
|
||||
p.Do(m_target_refresh_rate);
|
||||
p.Do(m_target_refresh_rate_numerator);
|
||||
p.Do(m_target_refresh_rate_denominator);
|
||||
p.Do(m_ticks_last_line_start);
|
||||
p.Do(m_half_line_count);
|
||||
p.Do(m_half_line_of_next_si_poll);
|
||||
@ -734,27 +732,15 @@ void VideoInterfaceManager::UpdateParameters()
|
||||
|
||||
void VideoInterfaceManager::UpdateRefreshRate()
|
||||
{
|
||||
m_target_refresh_rate_numerator = m_system.GetSystemTimers().GetTicksPerSecond() * 2;
|
||||
m_target_refresh_rate_denominator = GetTicksPerEvenField() + GetTicksPerOddField();
|
||||
m_target_refresh_rate =
|
||||
static_cast<double>(m_target_refresh_rate_numerator) / m_target_refresh_rate_denominator;
|
||||
m_target_refresh_rate = {m_system.GetSystemTimers().GetTicksPerSecond() * 2,
|
||||
GetTicksPerEvenField() + GetTicksPerOddField()};
|
||||
}
|
||||
|
||||
double VideoInterfaceManager::GetTargetRefreshRate() const
|
||||
MathUtil::Rational<u32> VideoInterfaceManager::GetTargetRefreshRate() const
|
||||
{
|
||||
return m_target_refresh_rate;
|
||||
}
|
||||
|
||||
u32 VideoInterfaceManager::GetTargetRefreshRateNumerator() const
|
||||
{
|
||||
return m_target_refresh_rate_numerator;
|
||||
}
|
||||
|
||||
u32 VideoInterfaceManager::GetTargetRefreshRateDenominator() const
|
||||
{
|
||||
return m_target_refresh_rate_denominator;
|
||||
}
|
||||
|
||||
u32 VideoInterfaceManager::GetTicksPerSample() const
|
||||
{
|
||||
return 2 * m_system.GetSystemTimers().GetTicksPerSecond() / CLOCK_FREQUENCIES[m_clock & 1];
|
||||
|
||||
@ -4,10 +4,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/Rational.h"
|
||||
|
||||
enum class FieldType;
|
||||
class PointerWrap;
|
||||
@ -381,9 +381,7 @@ public:
|
||||
// Change values pertaining to video mode
|
||||
void UpdateParameters();
|
||||
|
||||
double GetTargetRefreshRate() const;
|
||||
u32 GetTargetRefreshRateNumerator() const;
|
||||
u32 GetTargetRefreshRateDenominator() const;
|
||||
MathUtil::Rational<u32> GetTargetRefreshRate() const;
|
||||
|
||||
u32 GetTicksPerSample() const;
|
||||
u32 GetTicksPerHalfLine() const;
|
||||
@ -440,9 +438,7 @@ private:
|
||||
// 0xcc002076 - 0xcc00207f is full of 0x00FF: unknown
|
||||
// 0xcc002080 - 0xcc002100 even more unknown
|
||||
|
||||
double m_target_refresh_rate = 0;
|
||||
u32 m_target_refresh_rate_numerator = 0;
|
||||
u32 m_target_refresh_rate_denominator = 1;
|
||||
MathUtil::Rational<u32> m_target_refresh_rate{0};
|
||||
|
||||
u64 m_ticks_last_line_start = 0; // number of ticks when the current full scanline started
|
||||
u32 m_half_line_count = 0; // number of halflines that have occurred for this full frame
|
||||
|
||||
@ -95,7 +95,7 @@ static size_t s_state_writes_in_queue;
|
||||
static std::condition_variable s_state_write_queue_is_empty;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
constexpr u32 STATE_VERSION = 175; // Last changed in PR 13751
|
||||
constexpr u32 STATE_VERSION = 176; // Last changed in PR 14014
|
||||
|
||||
// Increase this if the StateExtendedHeader definition changes
|
||||
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217
|
||||
|
||||
@ -155,6 +155,7 @@
|
||||
<ClInclude Include="Common\Projection.h" />
|
||||
<ClInclude Include="Common\QoSSession.h" />
|
||||
<ClInclude Include="Common\Random.h" />
|
||||
<ClInclude Include="Common\Rational.h" />
|
||||
<ClInclude Include="Common\Result.h" />
|
||||
<ClInclude Include="Common\scmrev.h" />
|
||||
<ClInclude Include="Common\ScopeGuard.h" />
|
||||
|
||||
@ -67,11 +67,8 @@ AVRational GetTimeBaseForCurrentRefreshRate(s64 max_denominator)
|
||||
{
|
||||
// TODO: GetTargetRefreshRate* are not safe from GPU thread.
|
||||
auto& vi = Core::System::GetInstance().GetVideoInterface();
|
||||
int num;
|
||||
int den;
|
||||
av_reduce(&num, &den, int(vi.GetTargetRefreshRateDenominator()),
|
||||
int(vi.GetTargetRefreshRateNumerator()), max_denominator);
|
||||
return AVRational{num, den};
|
||||
const auto time_base = vi.GetTargetRefreshRate().Inverted().Approximated(max_denominator);
|
||||
return AVRational(int(time_base.numerator), int(time_base.denominator));
|
||||
}
|
||||
|
||||
void InitAVCodec()
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/Rational.h"
|
||||
|
||||
TEST(MathUtil, IntLog2)
|
||||
{
|
||||
@ -18,6 +19,14 @@ TEST(MathUtil, IntLog2)
|
||||
EXPECT_EQ(63, MathUtil::IntLog2(0xFFFFFFFFFFFFFFFFull));
|
||||
}
|
||||
|
||||
TEST(MathUtil, Compare3Way)
|
||||
{
|
||||
EXPECT_TRUE(std::is_lt(MathUtil::Compare3Way(-1, 1u)));
|
||||
EXPECT_TRUE(std::is_gteq(MathUtil::Compare3Way(5u, -17ll)));
|
||||
EXPECT_TRUE(std::is_eq(MathUtil::Compare3Way(42ull, 42)));
|
||||
EXPECT_TRUE(std::is_neq(MathUtil::Compare3Way(s32(-1), u32(-1))));
|
||||
}
|
||||
|
||||
TEST(MathUtil, NextPowerOf2)
|
||||
{
|
||||
EXPECT_EQ(4U, MathUtil::NextPowerOf2(3));
|
||||
@ -194,5 +203,144 @@ TEST(MathUtil, RectangleGetHeightUnsigned)
|
||||
EXPECT_EQ(rect_e.GetHeight(), u32{0xFFFFFFF8});
|
||||
}
|
||||
|
||||
TEST(MathUtil, Rational)
|
||||
{
|
||||
using MathUtil::Rational;
|
||||
|
||||
// Integer
|
||||
const auto r5 = 65 * Rational{8, 13} / 8;
|
||||
EXPECT_TRUE(r5.IsInteger());
|
||||
EXPECT_EQ(r5, 5);
|
||||
EXPECT_EQ(int(r5), 5);
|
||||
EXPECT_EQ(r5, Rational{10} * 0.5f);
|
||||
EXPECT_NE(r5, Rational(6));
|
||||
|
||||
// Non-Integer
|
||||
const auto r5_2 = Rational(5, 2);
|
||||
EXPECT_FALSE(r5_2.IsInteger());
|
||||
EXPECT_EQ(int(r5_2), 2);
|
||||
EXPECT_EQ(r5_2, 12.5f / r5);
|
||||
|
||||
// True/False
|
||||
EXPECT_TRUE(r5_2);
|
||||
EXPECT_FALSE(r5_2 * 0);
|
||||
EXPECT_FALSE(!r5_2);
|
||||
|
||||
// Negative values
|
||||
EXPECT_EQ(Rational(-4, -3), Rational(4, 3));
|
||||
EXPECT_EQ(Rational(-1, 10), Rational(1, -10));
|
||||
EXPECT_TRUE(Rational(-5, 1).IsInteger());
|
||||
EXPECT_TRUE(Rational(5, -1).IsInteger());
|
||||
EXPECT_NE(r5, -r5);
|
||||
|
||||
// Conversion to/from float
|
||||
const Rational r3p5(3.5);
|
||||
EXPECT_EQ(r3p5.numerator, 7);
|
||||
EXPECT_EQ(r3p5.denominator, 2);
|
||||
const Rational neg3p5(-3.5);
|
||||
EXPECT_EQ(neg3p5.numerator, -7);
|
||||
EXPECT_EQ(neg3p5.denominator, 2);
|
||||
EXPECT_EQ(float(r5_2), 2.5);
|
||||
EXPECT_EQ(float(-r5_2), -2.5f);
|
||||
EXPECT_EQ(r5_2, Rational(2.5f));
|
||||
EXPECT_EQ(-r5_2, Rational(-2.5));
|
||||
|
||||
EXPECT_NE(r5_2, Rational(2.500001f));
|
||||
|
||||
// Fraction reduction
|
||||
const Rational r15_6{15, 6};
|
||||
EXPECT_EQ(r15_6, r5_2);
|
||||
const auto f15_6_reduced = r15_6.Reduced();
|
||||
EXPECT_EQ(f15_6_reduced.numerator, 5);
|
||||
EXPECT_EQ(f15_6_reduced.denominator, 2);
|
||||
|
||||
// Approximations
|
||||
EXPECT_EQ(Rational(0.3).Approximated(1000'000), Rational(3, 10));
|
||||
EXPECT_EQ(Rational(3, 10).Approximated(9), Rational(2, 7));
|
||||
EXPECT_EQ(Rational(-33, 100).Approximated(20), Rational(-1, 3));
|
||||
EXPECT_EQ(Rational(0.33).Approximated(10), Rational(1, 3));
|
||||
EXPECT_EQ(Rational(1, -100).Approximated(10), Rational(0, 1));
|
||||
EXPECT_EQ(Rational(6, -100).Approximated(10), Rational(-1, 10));
|
||||
EXPECT_EQ(Rational(101).Approximated(20), Rational(20, 1));
|
||||
EXPECT_EQ(Rational<s16>(std::numeric_limits<s16>::max()).Approximated(18), Rational(18, 1));
|
||||
|
||||
constexpr auto s64_max = std::numeric_limits<s64>::max();
|
||||
EXPECT_EQ(Rational<s64>(-s64_max).Approximated(13), Rational(-13, 1));
|
||||
EXPECT_EQ(Rational<s64>(1, s64_max).Approximated(s64_max), Rational<s64>(1, s64_max));
|
||||
|
||||
// Addition/Subtraction
|
||||
EXPECT_EQ(-r5_2 + -r15_6, Rational(-5));
|
||||
EXPECT_EQ(2.5f - -r15_6, Rational(5));
|
||||
EXPECT_EQ(+r3p5 - 2, 1.5f);
|
||||
EXPECT_EQ(r3p5 - 7, -3.5);
|
||||
EXPECT_EQ(r3p5 - u8(2), 1.5);
|
||||
EXPECT_EQ(r3p5 - 3ull, 0.5);
|
||||
EXPECT_EQ(r3p5 - Rational<u64>(1), 2.5);
|
||||
EXPECT_EQ(Rational<u8>(6) - 5.5f, 0.5);
|
||||
EXPECT_EQ(Rational<u8>(6) + Rational(-5, -5), 7);
|
||||
EXPECT_EQ(Rational<u8>(6) - Rational<s64>(3, -3), 7);
|
||||
|
||||
// Inc/Dec
|
||||
Rational f7_3_inc{7, 3};
|
||||
EXPECT_EQ(f7_3_inc++, 2 + Rational(1, 3));
|
||||
EXPECT_EQ(++f7_3_inc, 4 + Rational(1, 3));
|
||||
EXPECT_EQ(f7_3_inc--, Rational(1, 3) + 4);
|
||||
EXPECT_EQ(--f7_3_inc, Rational(1, 3) + 2);
|
||||
|
||||
// Multiplication/Division
|
||||
EXPECT_EQ(r5_2 * 3, Rational(7.5));
|
||||
EXPECT_EQ(r5_2 * r5_2, 6.25);
|
||||
EXPECT_EQ(7 / r15_6, 2 + Rational(8, 10));
|
||||
EXPECT_EQ(r3p5 / r15_6, 12 - Rational(106, 10));
|
||||
EXPECT_EQ(r3p5 / 2, 1.75);
|
||||
EXPECT_EQ(int(r3p5 / 2), 1);
|
||||
EXPECT_EQ(Rational(-1, -3) * 2ull, Rational(2, 3));
|
||||
auto ru77 = Rational(77u);
|
||||
ru77 /= Rational(-7, -1);
|
||||
EXPECT_EQ(ru77, 11);
|
||||
|
||||
// Modulo
|
||||
EXPECT_EQ(r3p5 % 2, 1.5f);
|
||||
EXPECT_EQ(r3p5 % -2, Rational(3, 2));
|
||||
EXPECT_EQ(-r3p5 % 2, Rational(3, -2));
|
||||
EXPECT_EQ(-r3p5 % -2, -1.5f);
|
||||
|
||||
// Mediant
|
||||
EXPECT_EQ(r5_2 & Rational(2, 1), Rational(7, 3));
|
||||
EXPECT_EQ(Rational(11, 5) & Rational(3, -1), Rational(14, 4));
|
||||
|
||||
// Comparison
|
||||
EXPECT_TRUE(Rational(-5, 101) < 0);
|
||||
EXPECT_TRUE(Rational(5, -101) < 0u);
|
||||
EXPECT_TRUE(Rational(-5, -101) > 0);
|
||||
EXPECT_TRUE(Rational(7, 5) > Rational(7, 6));
|
||||
EXPECT_TRUE(Rational(1, 3) < Rational(-2, -3));
|
||||
EXPECT_TRUE(Rational(0.5) != Rational(2, 2));
|
||||
EXPECT_TRUE(Rational(10, 3) == 3 + Rational(2, 6));
|
||||
EXPECT_TRUE(3 >= Rational(6, 2));
|
||||
EXPECT_TRUE(Rational(6, 2) < 4.0);
|
||||
|
||||
// Conversions use SaturatingCast
|
||||
EXPECT_EQ(Rational<u32>(Rational(-5)), 0);
|
||||
EXPECT_EQ(Rational<u8>(Rational(1, 1000)), Rational(1, 255));
|
||||
EXPECT_EQ(Rational<u32>(-9.f), 0);
|
||||
EXPECT_EQ(Rational<s16>(std::pow(2.1, 18.0)), std::numeric_limits<s16>::max());
|
||||
EXPECT_EQ(Rational<s16>(-std::pow(2.1, 18.0)), std::numeric_limits<s16>::min());
|
||||
// Smallest positive non-zero float produces the smallest positive non-zero Rational.
|
||||
EXPECT_EQ(Rational<s16>(std::numeric_limits<double>::denorm_min()),
|
||||
Rational<s16>(1, std::numeric_limits<s16>::max()));
|
||||
|
||||
// Mixing types uses common_type
|
||||
const auto big_result = s64(-1000) * Rational<u16>{3000, 3};
|
||||
static_assert(std::is_same_v<decltype(big_result), const Rational<s64>>);
|
||||
EXPECT_TRUE(big_result == -1000'000);
|
||||
EXPECT_EQ(Rational<u32>(Rational(-6, -2)), 3);
|
||||
EXPECT_EQ(Rational(5u) * Rational(-1), 0);
|
||||
EXPECT_EQ(Rational(5u) * Rational(-1ll), -5);
|
||||
|
||||
// Works at compile time
|
||||
static_assert(Rational(8, 2) + Rational(-6, 123) + 4 == Rational(326, 41));
|
||||
}
|
||||
|
||||
// TODO: Add unit test coverage for `Rectangle::ClampUL`. (And consider removing
|
||||
// `Rectangle::ClampLL`, which does not have any callers.)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user