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:
parent
ab6ed8f45c
commit
eb36ad2c93
@ -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);
|
||||
};
|
||||
|
@ -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>;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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))));
|
||||
};
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user