Ported Number class from pi/demo-2022-03-09

This commit is contained in:
Mateusz Piątkowski 2022-05-15 23:03:09 +02:00 committed by Robert Bendun
parent 926a68cb84
commit 2385e0e28c
4 changed files with 178 additions and 0 deletions

View File

@ -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

View File

@ -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
View 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
View 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";
};
};
};