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.
This commit is contained in:
Robert Bendun 2022-08-21 00:43:04 +02:00
parent ab6ed8f45c
commit eb36ad2c93
4 changed files with 154 additions and 12 deletions

View File

@ -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<Number> from(Token token);
};

View File

@ -132,22 +132,24 @@ static Result<Value> comparison_operator(Interpreter&, std::vector<Value> args)
return Value::from(Binary_Predicate{}(args.front(), args.back()));
}
using Operator_Entry = std::tuple<char const*, Intrinsic>;
/// Operators definition table
static constexpr auto Operators = std::array {
std::tuple { "+", plus_minus_operator<std::plus<>> },
std::tuple { "-", plus_minus_operator<std::minus<>> },
std::tuple { "*", binary_operator<std::multiplies<>, '*'> },
std::tuple { "/", binary_operator<std::divides<>, '/'> },
Operator_Entry { "+", plus_minus_operator<std::plus<>> },
Operator_Entry { "-", plus_minus_operator<std::minus<>> },
Operator_Entry { "*", binary_operator<std::multiplies<>, '*'> },
Operator_Entry { "/", binary_operator<std::divides<>, '/'> },
std::tuple { "<", comparison_operator<std::less<>> },
std::tuple { ">", comparison_operator<std::greater<>> },
std::tuple { "<=", comparison_operator<std::less_equal<>> },
std::tuple { ">=", comparison_operator<std::greater_equal<>> },
Operator_Entry { "<", comparison_operator<std::less<>> },
Operator_Entry { ">", comparison_operator<std::greater<>> },
Operator_Entry { "<=", comparison_operator<std::less_equal<>> },
Operator_Entry { ">=", comparison_operator<std::greater_equal<>> },
std::tuple { "==", equality_operator<std::equal_to<>> },
std::tuple { "!=", equality_operator<std::not_equal_to<>> },
Operator_Entry { "==", equality_operator<std::equal_to<>> },
Operator_Entry { "!=", equality_operator<std::not_equal_to<>> },
std::tuple { ".",
Operator_Entry { ".",
+[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
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<Value> args) -> Result<Value> {
using Chord_Chord = Shape<Value::Type::Music, Value::Type::Music>;

View File

@ -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();
}

View File

@ -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))));
};
};
};