Ported Number class from pi/demo-2022-03-09
This commit is contained in:
parent
926a68cb84
commit
2385e0e28c
1
Makefile
1
Makefile
@ -7,6 +7,7 @@ Obj=bin/errors.o \
|
||||
bin/location.o \
|
||||
bin/parser.o \
|
||||
bin/unicode.o \
|
||||
bin/number.o \
|
||||
bin/unicode_tables.o
|
||||
|
||||
all: bin/musique bin/unit-tests
|
||||
|
@ -340,6 +340,32 @@ struct Parser
|
||||
Result<void> ensure(Token::Type type) const;
|
||||
};
|
||||
|
||||
// Number type supporting integer and fractional constants
|
||||
// Invariant: gcd(num, den) == 1, after any operation
|
||||
struct Number
|
||||
{
|
||||
i64 num = 0, den = 1;
|
||||
|
||||
auto as_int() const -> i64; // Returns self as int
|
||||
auto simplify() const -> Number; // Returns self, but with gcd(num, den) == 1
|
||||
void simplify_inplace(); // Update self, to have gcd(num, den) == 1
|
||||
|
||||
bool operator==(Number const&) const;
|
||||
bool operator!=(Number const&) const;
|
||||
std::strong_ordering operator<=>(Number const&) const;
|
||||
|
||||
Number operator+(Number const& rhs) const;
|
||||
Number& operator+=(Number const& rhs);
|
||||
Number operator-(Number const& rhs) const;
|
||||
Number& operator-=(Number const& rhs);
|
||||
Number operator*(Number const& rhs) const;
|
||||
Number& operator*=(Number const& rhs);
|
||||
Number operator/(Number const& rhs) const;
|
||||
Number& operator/=(Number const& rhs);
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Number const& num);
|
||||
|
||||
namespace errors
|
||||
{
|
||||
Error unrecognized_character(u32 invalid_character);
|
||||
|
116
src/number.cc
Normal file
116
src/number.cc
Normal file
@ -0,0 +1,116 @@
|
||||
#include <musique.hh>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
|
||||
auto Number::as_int() const -> i64
|
||||
{
|
||||
// We don't perform GCD simplification in place due to constness
|
||||
auto self = simplify();
|
||||
assert(self.den == 1 || self.num == 0, "Implicit coarce to integer while holding fractional number is not allowed");
|
||||
return self.num;
|
||||
}
|
||||
|
||||
auto Number::simplify() const -> Number
|
||||
{
|
||||
auto copy = *this;
|
||||
copy.simplify_inplace();
|
||||
return copy;
|
||||
}
|
||||
|
||||
// TODO This function may behave weirdly for maximum & minimum values of i64
|
||||
void Number::simplify_inplace()
|
||||
{
|
||||
for (;;) {
|
||||
if (auto d = std::gcd(num, den); d != 1) {
|
||||
num /= d;
|
||||
den /= d;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (den < 0) {
|
||||
// When num < 0 yields num > 0
|
||||
// When num > 0 yields num < 0
|
||||
den = -den;
|
||||
num = -num;
|
||||
}
|
||||
}
|
||||
|
||||
bool Number::operator==(Number const& rhs) const
|
||||
{
|
||||
return (*this <=> rhs) == 0;
|
||||
}
|
||||
|
||||
bool Number::operator!=(Number const& rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
std::strong_ordering Number::operator<=>(Number const& rhs) const
|
||||
{
|
||||
return (num * rhs.den - den * rhs.num) <=> 0;
|
||||
}
|
||||
|
||||
Number Number::operator+(Number const& rhs) const
|
||||
{
|
||||
int dens_lcm = std::lcm(den, rhs.den);
|
||||
return Number{(num * (dens_lcm / den)) + (rhs.num * (dens_lcm / rhs.den)), dens_lcm}.simplify();
|
||||
}
|
||||
|
||||
Number& Number::operator+=(Number const& rhs)
|
||||
{
|
||||
int dens_lcm = std::lcm(den, rhs.den);
|
||||
num = (num * (dens_lcm / den)) + (rhs.num * (dens_lcm / rhs.den));
|
||||
den = dens_lcm;
|
||||
simplify_inplace();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number Number::operator-(Number const& rhs) const
|
||||
{
|
||||
int dens_lcm = std::lcm(den, rhs.den);
|
||||
return Number{(num * (dens_lcm / den)) - (rhs.num * (dens_lcm / rhs.den)), dens_lcm}.simplify();
|
||||
}
|
||||
|
||||
Number& Number::operator-=(Number const& rhs)
|
||||
{
|
||||
int dens_lcm = std::lcm(den, rhs.den);
|
||||
num = (num * (dens_lcm / den)) - (rhs.num * (dens_lcm / rhs.den));
|
||||
den = dens_lcm;
|
||||
simplify_inplace();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number Number::operator*(Number const& rhs) const
|
||||
{
|
||||
return Number{num * rhs.num, den * rhs.den}.simplify();
|
||||
}
|
||||
|
||||
Number& Number::operator*=(Number const& rhs)
|
||||
{
|
||||
num *= rhs.num;
|
||||
den *= rhs.den;
|
||||
simplify_inplace();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number Number::operator/(Number const& rhs) const
|
||||
{
|
||||
return Number{num * rhs.den, den * rhs.num}.simplify();
|
||||
}
|
||||
|
||||
Number& Number::operator/=(Number const& rhs)
|
||||
{
|
||||
num *= rhs.den;
|
||||
den *= rhs.num;
|
||||
simplify_inplace();
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Number const& n)
|
||||
{
|
||||
return n.den == 1
|
||||
? os << n.num
|
||||
: os << n.num << '/' << n.den;
|
||||
}
|
35
src/tests/number.cc
Normal file
35
src/tests/number.cc
Normal file
@ -0,0 +1,35 @@
|
||||
#include <boost/ut.hpp>
|
||||
#include <musique.hh>
|
||||
|
||||
using namespace boost::ut;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
suite number_test = [] {
|
||||
"Number"_test = [] {
|
||||
should("provide arithmetic operators") = [] {
|
||||
expect(eq(Number{1, 8} + Number{3, 4}, Number{ 7, 8})) << "for expr: (1/8) + (3/4)";
|
||||
expect(eq(Number{1, 8} - Number{3, 4}, Number{-5, 8})) << "for expr: (1/8) - (3/4)";
|
||||
expect(eq(Number{1, 8} * Number{3, 4}, Number{ 3, 32})) << "for expr: (1/8) * (3/4)";
|
||||
expect(eq(Number{1, 8} / Number{3, 4}, Number{ 1, 6})) << "for expr: (1/8) / (3/4)";
|
||||
};
|
||||
|
||||
should("provide assignment operators") = [] {
|
||||
Number n;
|
||||
n={1,8}; n += {3,4}; expect(eq(n, Number{ 7, 8})) << "for expr: (1/8) += (3/4)";
|
||||
n={1,8}; n -= {3,4}; expect(eq(n, Number{-5, 8})) << "for expr: (1/8) -= (3/4)";
|
||||
n={1,8}; n *= {3,4}; expect(eq(n, Number{ 3, 32})) << "for expr: (1/8) *= (3/4)";
|
||||
n={1,8}; n /= {3,4}; expect(eq(n, Number{ 1, 6})) << "for expr: (1/8) /= (3/4)";
|
||||
};
|
||||
|
||||
should("be comperable") = [] {
|
||||
expect(gt(Number{1, 4}, Number{1, 8}));
|
||||
expect(eq(Number{2, 4}, Number{1, 2}));
|
||||
};
|
||||
|
||||
should("be convertable to int") = [] {
|
||||
expect(eq(Number{4, 2}.as_int(), 2)) << "for fraction 4/2";
|
||||
expect(eq(Number{2, 1}.as_int(), 2)) << "for fraction 2/1";
|
||||
expect(eq(Number{0, 1000}.as_int(), 0)) << "for fraction 0/1000";
|
||||
};
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user