From eb36ad2c93adcada30dadb0322257ec84b392d9f Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Sun, 21 Aug 2022 00:43:04 +0200 Subject: [PATCH] Implemented Number::{ceil, floor, round, pow, inverse} Except that pow only accepts integer exponents, since nth root is not that quick to implement and simple blind implementation may have performance issues. --- include/musique.hh | 7 ++++ src/builtin_operators.cc | 26 ++++++------ src/number.cc | 90 ++++++++++++++++++++++++++++++++++++++++ src/tests/number.cc | 43 +++++++++++++++++++ 4 files changed, 154 insertions(+), 12 deletions(-) diff --git a/include/musique.hh b/include/musique.hh index 51166ef..df85dca 100644 --- a/include/musique.hh +++ b/include/musique.hh @@ -667,6 +667,13 @@ struct Number Number operator/(Number const& rhs) const; Number& operator/=(Number const& rhs); + Number floor() const; ///< Return number rounded down to nearest integer + Number ceil() const; ///< Return number rounded up to nearest integer + Number round() const; ///< Return number rounded to nearest integer + + Number inverse() const; ///< Return number raised to power -1 + Number pow(Number n) const; ///< Return number raised to power `n`. + /// Parses source contained by token into a Number instance static Result from(Token token); }; diff --git a/src/builtin_operators.cc b/src/builtin_operators.cc index 609297f..8560ca7 100644 --- a/src/builtin_operators.cc +++ b/src/builtin_operators.cc @@ -132,22 +132,24 @@ static Result comparison_operator(Interpreter&, std::vector args) return Value::from(Binary_Predicate{}(args.front(), args.back())); } +using Operator_Entry = std::tuple; + /// Operators definition table static constexpr auto Operators = std::array { - std::tuple { "+", plus_minus_operator> }, - std::tuple { "-", plus_minus_operator> }, - std::tuple { "*", binary_operator, '*'> }, - std::tuple { "/", binary_operator, '/'> }, + Operator_Entry { "+", plus_minus_operator> }, + Operator_Entry { "-", plus_minus_operator> }, + Operator_Entry { "*", binary_operator, '*'> }, + Operator_Entry { "/", binary_operator, '/'> }, - std::tuple { "<", comparison_operator> }, - std::tuple { ">", comparison_operator> }, - std::tuple { "<=", comparison_operator> }, - std::tuple { ">=", comparison_operator> }, + Operator_Entry { "<", comparison_operator> }, + Operator_Entry { ">", comparison_operator> }, + Operator_Entry { "<=", comparison_operator> }, + Operator_Entry { ">=", comparison_operator> }, - std::tuple { "==", equality_operator> }, - std::tuple { "!=", equality_operator> }, + Operator_Entry { "==", equality_operator> }, + Operator_Entry { "!=", equality_operator> }, - std::tuple { ".", + Operator_Entry { ".", +[](Interpreter &i, std::vector args) -> Result { assert(args.size() == 2, "Operator . requires two arguments"); // TODO(assert) assert(args.back().type == Value::Type::Number, "Only numbers can be used for indexing"); // TODO(assert) @@ -155,7 +157,7 @@ static constexpr auto Operators = std::array { } }, - std::tuple { "&", + Operator_Entry { "&", +[](Interpreter&, std::vector args) -> Result { using Chord_Chord = Shape; diff --git a/src/number.cc b/src/number.cc index fdcaad1..e080e0b 100644 --- a/src/number.cc +++ b/src/number.cc @@ -190,3 +190,93 @@ parse_fractional: return result.simplify(); } + +Number Number::floor() const +{ + auto result = *this; + if (den <= -1 || den >= 1) { + if (auto const r = result.num % result.den; r != 0) { + result.num += ((num < 0) xor (den < 0) ? 1 : -1) * r; + result.num /= result.den; + result.den = 1; + } else { + result.simplify_inplace(); + } + } + return result; +} + +Number Number::ceil() const +{ + auto result = *this; + if (den <= -1 || den >= 1) { + if (auto const r = result.num % result.den; r != 0) { + result.num += ((num < 0) xor (den < 0) ? -1 : 1) * r; + result.num /= result.den; + result.den = 1; + } else { + result.simplify_inplace(); + } + } + return result; +} + +Number Number::round() const +{ + auto result = *this; + if (den <= -1 || den >= 1) { + if (auto const r = result.num % result.den; r != 0) { + auto const dir = r * 2 >= result.den ? -1 : 1; + result.num += ((num < 0) xor (den < 0) ? dir : -dir) * r; + result.num /= result.den; + result.den = 1; + } else { + result.simplify_inplace(); + } + } + return result; +} + +Number Number::inverse() const +{ + assert(num != 0, "Cannot take inverse of fraction with 0 in denominator"); + return { den, num }; +} + +namespace impl +{ + // Raise Number to integer power helper + static inline Number pow(Number const& x, decltype(Number::num) n) + { + // We simply raise numerator and denominator to required power + // and if n is negative we take inverse. + if (n == 0) return Number(1); + auto result = Number { 1, 1 }; + + auto flip = false; + if (n < 0) { flip = true; n = -n; } + + for (auto i = n; i != 0; --i) result.num *= x.num; + for (auto i = n; i != 0; --i) result.den *= x.den; + return flip ? result.inverse() : result; + } +} + +Number Number::pow(Number n) const +{ + n.simplify_inplace(); + + // Simple case, we raise this to integer power. + if (n.den == 1) { + return impl::pow(*this, n.num); + } + + // Hard case, we raise this to fractional power. + // Essentialy finding n.den root of (x to n.num power). + // We need to protect ourselfs against even roots of negative numbers. + // TODO Implement this case properly. + // + // TODO(assert) <- taking power is not always doable so this operation + // should produce error not crash with assertion failure in the future. + unimplemented(); +} diff --git a/src/tests/number.cc b/src/tests/number.cc index 01a58ca..af17c56 100644 --- a/src/tests/number.cc +++ b/src/tests/number.cc @@ -52,4 +52,47 @@ suite number_test = [] { test_number_from(".75", Number(3, 4)); test_number_from("120.", Number(120, 1)); }; + + "Rounding"_test = [] { + should("Support floor operation") = [] { + expect(eq(Number(0), Number(1, 2).floor())); + expect(eq(Number(1), Number(3, 2).floor())); + expect(eq(Number(-1), Number(-1, 2).floor())); + expect(eq(Number(0), Number(0).floor())); + expect(eq(Number(1), Number(1).floor())); + expect(eq(Number(-1), Number(-1).floor())); + }; + + should("Support ceil operation") = [] { + expect(eq(Number(1), Number(1, 2).ceil())); + expect(eq(Number(2), Number(3, 2).ceil())); + expect(eq(Number(0), Number(-1, 2).ceil())); + expect(eq(Number(-1), Number(-3, 2).ceil())); + expect(eq(Number(0), Number(0).ceil())); + expect(eq(Number(1), Number(1).ceil())); + expect(eq(Number(-1), Number(-1).ceil())); + }; + + should("Support round operation") = [] { + expect(eq(Number(1), Number(3, 4).round())); + expect(eq(Number(0), Number(1, 4).round())); + expect(eq(Number(1), Number(5, 4).round())); + expect(eq(Number(2), Number(7, 4).round())); + + expect(eq(Number(-1), Number(-3, 4).round())); + expect(eq(Number(0), Number(-1, 4).round())); + expect(eq(Number(-1), Number(-5, 4).round())); + expect(eq(Number(-2), Number(-7, 4).round())); + }; + }; + + "Number exponantiation"_test = [] { + should("Support integer powers") = [] { + expect(eq(Number(1, 4), Number(1, 2).pow(Number(2)))); + expect(eq(Number(4, 1), Number(1, 2).pow(Number(-2)))); + + expect(eq(Number(4, 1), Number(2).pow(Number(2)))); + expect(eq(Number(1, 4), Number(2).pow(Number(-2)))); + }; + }; };