diff --git a/.gitignore b/.gitignore index 16a40fb..a8ae952 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .vscode/ doc/html *.cbp +test.cpp +a.out #cmake products cmake-build-debug/ diff --git a/bench/include/benchmark_helpers.hpp b/bench/include/benchmark_helpers.hpp index f61b6c2..8176efb 100644 --- a/bench/include/benchmark_helpers.hpp +++ b/bench/include/benchmark_helpers.hpp @@ -10,7 +10,7 @@ struct bench_invalid_operation_exception : public std::exception { } }; -constexpr void realOperationEq(boost::real::real& lhs, boost::real::real& rhs, +constexpr void realOperationEq(boost::real::real<>& lhs, boost::real::real<>& rhs, boost::real::OPERATION op) { switch (op) { case boost::real::OPERATION::ADDITION: @@ -22,13 +22,17 @@ constexpr void realOperationEq(boost::real::real& lhs, boost::real::real& rhs, case boost::real::OPERATION::MULTIPLICATION: lhs *= rhs; break; + case boost::real::OPERATION::DIVISION: + lhs /= rhs; + break; default: throw bench_invalid_operation_exception(); + } } enum class Comparison {GREATER_THAN, LESS_THAN, EQUALS}; -constexpr bool realComp(boost::real::real& lhs,boost::real::real& rhs, Comparison comp) { +constexpr bool realComp(boost::real::real<>& lhs, boost::real::real<>& rhs, Comparison comp) { switch (comp) { case (Comparison::GREATER_THAN): return lhs > rhs; diff --git a/bench/main-bench.cpp b/bench/main-bench.cpp index 8faa845..b78ed58 100644 --- a/bench/main-bench.cpp +++ b/bench/main-bench.cpp @@ -3,6 +3,6 @@ // ensure this is >= to MAX_NUM_DIGITS_XX for all benchmarks, else we will get // a precision error and the benchmarks will not be meaningful. -std::optional boost::real::const_precision_iterator::global_maximum_precision = 10000; +template<> std::optional boost::real::const_precision_iterator::global_maximum_precision = 10; BENCHMARK_MAIN(); diff --git a/bench/real_construction_bench.cpp b/bench/real_construction_bench.cpp index 4b698b2..503833a 100644 --- a/bench/real_construction_bench.cpp +++ b/bench/real_construction_bench.cpp @@ -13,12 +13,12 @@ const int MULTIPLIER_TC = 10; // for range evaluation of tree construction benc /// MULTIPLIER_TC between MIN_TREE_NODES and MAX_TREE_NODES void BM_RealOperationTreeConstruction(benchmark::State& state, boost::real::OPERATION op) { for (auto i : state) { - boost::real::real a ("1234567891"); - boost::real::real b ("9876532198"); + boost::real::real<> a ("1234567891"); + boost::real::real<> b ("9876532198"); // We keep the precision constant here because in constructing the *= tree, we would get way more digits // than in +=, -= trees. This should make the benchmarks more meaningful - a.set_maximum_precision(10); + a.set_maximum_precision(2); for (int i = 0; i < state.range(0); i++) { realOperationEq(a,b,op); @@ -29,7 +29,6 @@ void BM_RealOperationTreeConstruction(benchmark::State& state, boost::real::OPER } // these benchmark the operation tree constructors for each operation type. -/// @TODO: add division bench BENCHMARK_CAPTURE(BM_RealOperationTreeConstruction, addition, boost::real::OPERATION(boost::real::OPERATION::ADDITION)) ->RangeMultiplier(MULTIPLIER_TC)->Range(MIN_TREE_NODES ,MAX_TREE_NODES)->Unit(benchmark::kMillisecond) ->Complexity(); @@ -42,6 +41,10 @@ BENCHMARK_CAPTURE(BM_RealOperationTreeConstruction, multiplication, boost::real: ->RangeMultiplier(MULTIPLIER_TC)->Range(MIN_TREE_NODES ,MAX_TREE_NODES)->Unit(benchmark::kMillisecond) ->Complexity(); +BENCHMARK_CAPTURE(BM_RealOperationTreeConstruction, division, boost::real::OPERATION(boost::real::OPERATION::DIVISION)) + ->RangeMultiplier(MULTIPLIER_TC)->Range(MIN_TREE_NODES ,MAX_TREE_NODES)->Unit(benchmark::kMillisecond) + ->Complexity(); + const int MIN_NUM_DIGITS_EC = 10; const int MAX_NUM_DIGITS_EC = 10000; const int MULTIPLIER_EC = 10; // for range evaluation of explicit construction benchmarks @@ -57,7 +60,7 @@ void BM_RealExplicitConstruction_String(benchmark::State& state) { } state.ResumeTiming(); - boost::real::real a(number); + boost::real::real<> a(number); state.SetComplexityN(state.range(0)); } } diff --git a/bench/real_op_evaluation_bench.cpp b/bench/real_op_evaluation_bench.cpp index 94f027e..dc7edc1 100644 --- a/bench/real_op_evaluation_bench.cpp +++ b/bench/real_op_evaluation_bench.cpp @@ -9,8 +9,8 @@ const int MULTIPLIER_TE = 10; // for range evaluation of tree evaluation benchm /// MULTIPLIER_TE between MIN_TREE_NODES and MAX_TREE_NODES void BM_RealOperationTreeEvaluation(benchmark::State& state, boost::real::OPERATION op) { for (auto i : state) { - boost::real::real a ("12"); - boost::real::real b ("34"); + boost::real::real<> a ("12"); + boost::real::real<> b ("34"); state.PauseTiming(); for (int i = 0; i < state.range(0); i++) { @@ -23,7 +23,6 @@ void BM_RealOperationTreeEvaluation(benchmark::State& state, boost::real::OPERAT } } -/// @TODO: add division. BENCHMARK_CAPTURE(BM_RealOperationTreeEvaluation, addition, boost::real::OPERATION(boost::real::OPERATION::ADDITION)) ->RangeMultiplier(MULTIPLIER_TE)->Range(MIN_TREE_NODES ,MAX_TREE_NODES)->Unit(benchmark::kMillisecond) ->Complexity(); @@ -36,10 +35,14 @@ BENCHMARK_CAPTURE(BM_RealOperationTreeEvaluation, multiplication, boost::real::O ->RangeMultiplier(MULTIPLIER_TE)->Range(MIN_TREE_NODES ,MAX_TREE_NODES)->Unit(benchmark::kMillisecond) ->Complexity(); +BENCHMARK_CAPTURE(BM_RealOperationTreeEvaluation, division, boost::real::OPERATION(boost::real::OPERATION::DIVISION)) + ->RangeMultiplier(MULTIPLIER_TE)->Range(MIN_TREE_NODES ,MAX_TREE_NODES)->Unit(benchmark::kMillisecond) + ->Complexity(); + -const int MIN_NUM_DIGITS = 1000; -const int MAX_NUM_DIGITS = 10000; -const int MULTIPLIER_OE = 6; +const int MIN_NUM_DIGITS = 1; +const int MAX_NUM_DIGITS = 100 ; +const int MULTIPLIER_OE = 10; /// benchmarks a op= b, where a, b, have n digits, where n is the set of powers of /// MULTIPLIER_OE, between MIN_NUM_DIGITS and MAX_NUM_DIGITS @@ -50,13 +53,13 @@ void BM_RealOperationEvaluation(benchmark::State& state, boost::real::OPERATION for (int i = 0; i < state.range(0); i++) { tmp.push_back('2'); } - boost::real::real a(tmp); + boost::real::real<> a(tmp); tmp.clear(); for (int i = 0; i < state.range(0); i++) { tmp.push_back('3'); } - boost::real::real b(tmp); + boost::real::real<> b(tmp); realOperationEq(a,b,op); state.ResumeTiming(); @@ -65,7 +68,6 @@ void BM_RealOperationEvaluation(benchmark::State& state, boost::real::OPERATION } } -///@TODO: add division // The BM_RealOperationEvaluation operations are much faster, so these use nanoseconds in the result. BENCHMARK_CAPTURE(BM_RealOperationEvaluation, addition, boost::real::OPERATION(boost::real::OPERATION::ADDITION)) ->RangeMultiplier(MULTIPLIER_OE)->Range(MIN_NUM_DIGITS,MAX_NUM_DIGITS) @@ -79,6 +81,9 @@ BENCHMARK_CAPTURE(BM_RealOperationEvaluation, multiplication, boost::real::OPERA ->RangeMultiplier(MULTIPLIER_OE)->Range(MIN_NUM_DIGITS,MAX_NUM_DIGITS) ->Complexity(); +BENCHMARK_CAPTURE(BM_RealOperationEvaluation, division, boost::real::OPERATION(boost::real::OPERATION::DIVISION)) + ->RangeMultiplier(MULTIPLIER_OE)->Range(MIN_NUM_DIGITS,MAX_NUM_DIGITS) + ->Complexity(); /// benchmarks operator> operator< and operator== for numbers a, b, where a == b, with n digits void BM_RealComparisonEvaluation(benchmark::State& state, Comparison comp) { @@ -88,8 +93,8 @@ void BM_RealComparisonEvaluation(benchmark::State& state, Comparison comp) { for (int i = 0; i < state.range(0); i++) { // we compare 111...1 and 111..1 tmp.push_back('1'); } - boost::real::real a(tmp); - boost::real::real b(tmp); + boost::real::real<> a(tmp); + boost::real::real<> b(tmp); state.ResumeTiming(); diff --git a/include/real/exact_number.hpp b/include/real/exact_number.hpp index 084da06..5080259 100644 --- a/include/real/exact_number.hpp +++ b/include/real/exact_number.hpp @@ -17,7 +17,9 @@ namespace boost { struct exact_number { using exponent_t = int; - static const int BASE = 10; + // TODO: replace all redundant declarations of base with this + // static const T BASE = ; + std::vector digits = {}; exponent_t exponent = 0; bool positive = true; @@ -389,7 +391,7 @@ namespace boost { // ensuring that assignment from -1 * (maximum_precision) to exponent will not // overflow - if (maximum_precision > std::abs(std::numeric_limits::min())) { + if (maximum_precision > (unsigned int) std::abs(std::numeric_limits::min())) { throw exponent_overflow_exception(); } @@ -615,20 +617,21 @@ namespace boost { exact_number(std::vector vec, bool pos = true) : digits(vec), exponent(vec.size()), positive(pos) {}; /// ctor from any integral type - template::value>> - exact_number(I x) { - if (x < 0) - positive = false; - else - positive = true; + /// @TODO: use whichever base. + // template::value>> + // exact_number(I x) { + // if (x < 0) + // positive = false; + // else + // positive = true; - exponent = 0; - while (((x % BASE) != 0) || (x != 0)) { - exponent++; - push_front(std::abs(x%BASE)); - x /= BASE; - } - } + // exponent = 0; + // while (((x % BASE) != 0) || (x != 0)) { + // exponent++; + // push_front(std::abs(x%BASE)); + // x /= BASE; + // } + // } // returns {integer_part, decimal_part, exponent, is_positive} constexpr static std::tuple number_from_string(std::string_view number) { @@ -639,7 +642,6 @@ namespace boost { bool exp_positive = true; bool positive = true; - bool on_integer = false; bool has_exponent = false; bool has_decimal = false; bool has_sign = false; diff --git a/include/real/real.hpp b/include/real/real.hpp index 6251218..dfdd39d 100644 --- a/include/real/real.hpp +++ b/include/real/real.hpp @@ -102,6 +102,7 @@ namespace boost { std::string denominator = "1"; for (int i = 0; iget_real_number()); } + // this is used to control the amount of recursion that goes on in the distribution. Essentially, when we do a + b, we may + // look at one level below them (if applicable, i.e., they're operations pointing to operands of their own). + + // we at most need to look at two levels. + // this is because in the case we have, say, x*a+x*b -> (a+b)*x, we may want to look at a and b's operands, if applicable, to see if + // they may be distributed as well. + enum class RECURSION_LEVEL{ZERO, ONE, TWO}; + // a helper function for distributing when performing addition/subtraction // the returned bool tells us whether we distributed or not, which is mostly useful when assign_and_return_void is true // since std::optional would = std::null_opt if assign_and_return_void is false. - - std::pair>> check_and_distribute(real& other, bool assign_and_return_void, OPERATION op) { + std::pair> check_and_distribute(const real & other, const bool assign_and_return_void, const OPERATION op, const RECURSION_LEVEL rc_lvl) { // The following simplifies using the distributive property, when numbers have the same pointers. // We could do comparison by value, but this may force more computation than is necessary for the user, // since it's difficult to determine whether values are the same @@ -320,8 +328,8 @@ namespace boost { std::shared_ptr> b; std::shared_ptr> x; - if(auto op_ptr = std::get_if>(this->_real_p->get_real_ptr())) { - if (auto op_ptr2 = std::get_if>(other._real_p->get_real_ptr())) { // both of real_operation + if(auto op_ptr = std::get_if>(this->_real_p->get_real_ptr())) { // lhs real_operation + if (auto op_ptr2 = std::get_if>(other._real_p->get_real_ptr())) { // lhs, rhs real_operation if ((op_ptr->get_operation() == OPERATION::MULTIPLICATION) && op_ptr2->get_operation() == OPERATION::MULTIPLICATION) { if (op_ptr->lhs() == op_ptr2->lhs()) { // x * a + x * b = (a + b) * x a = op_ptr->rhs(); @@ -349,10 +357,27 @@ namespace boost { real a_op_b; if(op == OPERATION::ADDITION) { - a_op_b = real(a) + real(b); - } - else if (op == OPERATION::SUBTRACTION) { - a_op_b = real(a) + real(b); + switch(rc_lvl) { + case (RECURSION_LEVEL::TWO): + a_op_b = real(a).add(real(b), RECURSION_LEVEL::ONE); + break; + case (RECURSION_LEVEL::ONE): + a_op_b = real(a).add(real(b), RECURSION_LEVEL::ZERO); + break; + default: + throw invalid_recursion_level_exception(); + } + } else if (op == OPERATION::SUBTRACTION) { + switch(rc_lvl) { + case (RECURSION_LEVEL::TWO): + a_op_b = real(a).subtract(real(b), RECURSION_LEVEL::ONE); + break; + case (RECURSION_LEVEL::ONE): + a_op_b = real(a).subtract(real(b), RECURSION_LEVEL::ZERO); + break; + default: + throw invalid_recursion_level_exception(); + } } else { throw invalid_distribution_operation_exception(); } @@ -364,7 +389,7 @@ namespace boost { return std::make_pair(true, real(real_operation(a_op_b._real_p, x, OPERATION::MULTIPLICATION))); } } - } else { // rhs is not an operation + } else { // lhs is an operation, but rhs is not an operation if (op_ptr->get_operation() == OPERATION::MULTIPLICATION) { if (other._real_p == op_ptr->lhs()) { // (a * x) + a -> (x + 1) * a a = op_ptr->lhs(); @@ -379,15 +404,34 @@ namespace boost { real one ("1"); real x_op_1; + if(op == OPERATION::ADDITION) { - x_op_1 = real(x) + one; + switch(rc_lvl) { + case (RECURSION_LEVEL::TWO): + x_op_1 = real(x).add(real(one), RECURSION_LEVEL::ONE); + break; + case (RECURSION_LEVEL::ONE): + x_op_1 = real(a).add(real(one), RECURSION_LEVEL::ZERO); + break; + default: + throw invalid_recursion_level_exception(); + } } else if (op == OPERATION::SUBTRACTION) { - x_op_1 = real(x) + one; + switch(rc_lvl) { + case (RECURSION_LEVEL::TWO): + x_op_1 = real(a).subtract(real(b), RECURSION_LEVEL::ONE); + break; + case (RECURSION_LEVEL::ONE): + x_op_1 = real(a).subtract(real(b), RECURSION_LEVEL::ZERO); + break; + default: + throw invalid_recursion_level_exception(); + } } else { throw invalid_distribution_operation_exception(); } - + if(assign_and_return_void) { this->_real_p = std::make_shared>(real_operation(x_op_1._real_p, a, OPERATION::MULTIPLICATION)); return std::make_pair(true, std::nullopt); @@ -413,10 +457,28 @@ namespace boost { real one ("1"); if(op == OPERATION::ADDITION) { - x_op_1 = real(x) + one; + switch(rc_lvl) { + case (RECURSION_LEVEL::TWO): + x_op_1 = real(x).add(real(one), RECURSION_LEVEL::ONE); + break; + case (RECURSION_LEVEL::ONE): + x_op_1 = real(x).add(real(one), RECURSION_LEVEL::ZERO); + break; + default: + throw invalid_recursion_level_exception(); + } } else if (op == OPERATION::SUBTRACTION) { - x_op_1 = real(x) + one; + switch(rc_lvl) { + case (RECURSION_LEVEL::TWO): + x_op_1 = real(a).subtract(real(b), RECURSION_LEVEL::ONE); + break; + case (RECURSION_LEVEL::ONE): + x_op_1 = real(a).subtract(real(b), RECURSION_LEVEL::ZERO); + break; + default: + throw invalid_recursion_level_exception(); + } } else { throw invalid_distribution_operation_exception(); } @@ -444,6 +506,46 @@ namespace boost { // at this point, we cannot distribute. return std::make_pair(false, std::nullopt); } + + + // helper function used in check_and_distribute to limit recursion + real recurse_op(real& other, RECURSION_LEVEL rc_lvl, OPERATION op) { + switch (rc_lvl) { + case RECURSION_LEVEL::ONE: { + auto [is_simplified, result] = check_and_distribute(other, false, op, RECURSION_LEVEL::ONE); + + if (!is_simplified) { + real ret = (*this); + ret._real_p = + std::make_shared>(real_operation(this->_real_p, other._real_p, op)); + return ret; + } else { + return result.value(); + } + return *this; + break; + } + case RECURSION_LEVEL::ZERO: { + real ret = (*this); + ret._real_p = + std::make_shared>(real_operation(this->_real_p, other._real_p, op)); + return ret; + break; + } + default: + throw invalid_recursion_level_exception(); + } + } + + /// obtains the result of *this += other, and returns it. + real add(real other, RECURSION_LEVEL rc_lvl) { + return recurse_op(other, rc_lvl, OPERATION::ADDITION); + } + + /// performs *this -= other, and returns *this + real subtract(real other, RECURSION_LEVEL rc_lvl) { + return recurse_op(other, rc_lvl, OPERATION::SUBTRACTION); + } /** * @brief Sets this real_data to that of the operation between this previous @@ -454,12 +556,12 @@ namespace boost { * @param other - the right side operand boost::real::real number. */ - void operator+=(real other) { - auto [is_simplified, result] = check_and_distribute(other, true, OPERATION::ADDITION); + void operator+=(real other) { + auto [is_simplified,result] = check_and_distribute(other, true, OPERATION::ADDITION, RECURSION_LEVEL::TWO); if (!is_simplified) { this->_real_p = - std::make_shared>(real_operation(this->_real_p, other._real_p, OPERATION::ADDITION)); + std::make_shared>(real_operation(this->_real_p, other._real_p, OPERATION::ADDITION)); } } @@ -470,8 +572,8 @@ namespace boost { * @param other - the right side operand boost::real::real number. * @return A copy of the new boost::real::real number representation. */ - real operator+(real other) { - auto [is_simplified, result] = check_and_distribute(other, false, OPERATION::ADDITION); + real operator+(real other) { + auto [is_simplified, result] = check_and_distribute(other, false, OPERATION::ADDITION, RECURSION_LEVEL::TWO); if (is_simplified) { return result.value(); } else { @@ -486,11 +588,11 @@ namespace boost { * @param other - the right side operand boost::real::real number. */ void operator-=(real other) { - auto [is_simplified, result] = check_and_distribute(other, true, OPERATION::SUBTRACTION); + auto [is_simplified, result] = check_and_distribute(other, true, OPERATION::SUBTRACTION, RECURSION_LEVEL::TWO); if(!is_simplified) { this->_real_p = - std::make_shared>(real_operation(this->_real_p, other._real_p, OPERATION::SUBTRACTION)); + std::make_shared>(real_operation(this->_real_p, other._real_p, OPERATION::SUBTRACTION)); } } @@ -501,8 +603,8 @@ namespace boost { * @param other - the right side operand boost::real::real number. * @return A copy of the new boost::real::real number representation. */ - real operator-(real other) { - auto [is_simplified, result] = check_and_distribute(other, false, OPERATION::SUBTRACTION); + real operator-(real other) { + auto [is_simplified, result] = check_and_distribute(other, false, OPERATION::SUBTRACTION, RECURSION_LEVEL::TWO); if (is_simplified) { return result.value(); } else { diff --git a/include/real/real_exception.hpp b/include/real/real_exception.hpp index f670beb..10c0bb6 100644 --- a/include/real/real_exception.hpp +++ b/include/real/real_exception.hpp @@ -75,6 +75,13 @@ namespace boost { } }; + struct invalid_recursion_level_exception : public std::exception { + + const char * what () const throw () override { + return "Distributing with this recursion level is undefined."; + + } + }; } }