From 0ee63b7c7b3f0096316aa329b4405b90fae3f85d Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Wed, 13 Apr 2022 16:01:52 -0400 Subject: [PATCH] AMM Add Number class and associated algorithms --- Builds/CMake/RippledCore.cmake | 3 + src/ripple/basics/Number.h | 322 +++++++++++++++++ src/ripple/basics/impl/Number.cpp | 582 ++++++++++++++++++++++++++++++ src/test/basics/Number_test.cpp | 143 ++++++++ 4 files changed, 1050 insertions(+) create mode 100644 src/ripple/basics/Number.h create mode 100644 src/ripple/basics/impl/Number.cpp create mode 100644 src/test/basics/Number_test.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 4bad3a87b4..dca4720e38 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -50,6 +50,7 @@ target_sources (xrpl_core PRIVATE src/ripple/basics/impl/FileUtilities.cpp src/ripple/basics/impl/IOUAmount.cpp src/ripple/basics/impl/Log.cpp + src/ripple/basics/impl/Number.cpp src/ripple/basics/impl/StringUtilities.cpp #[===============================[ main sources: @@ -153,6 +154,7 @@ install ( src/ripple/basics/LocalValue.h src/ripple/basics/Log.h src/ripple/basics/MathUtilities.h + src/ripple/basics/Number.h src/ripple/basics/safe_cast.h src/ripple/basics/Slice.h src/ripple/basics/spinlock.h @@ -737,6 +739,7 @@ if (tests) src/test/basics/FileUtilities_test.cpp src/test/basics/IOUAmount_test.cpp src/test/basics/KeyCache_test.cpp + src/test/basics/Number_test.cpp src/test/basics/PerfLog_test.cpp src/test/basics/RangeSet_test.cpp src/test/basics/scope_test.cpp diff --git a/src/ripple/basics/Number.h b/src/ripple/basics/Number.h new file mode 100644 index 0000000000..607db3ff88 --- /dev/null +++ b/src/ripple/basics/Number.h @@ -0,0 +1,322 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_NUMBER_H_INCLUDED +#define RIPPLE_BASICS_NUMBER_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { + +class Number; + +std::string +to_string(Number const& amount); + +class Number +{ + using rep = std::int64_t; + rep mantissa_{0}; + int exponent_{-2'147'483'648}; + +public: + struct unchecked + { + explicit unchecked() = default; + }; + + explicit Number() = default; + + Number(rep mantissa); + explicit Number(rep mantissa, int exponent); + explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept; + + Number(IOUAmount const& x); + + constexpr rep + mantissa() const noexcept; + constexpr int + exponent() const noexcept; + + constexpr Number + operator+() const noexcept; + constexpr Number + operator-() const noexcept; + Number& + operator++(); + Number + operator++(int); + Number& + operator--(); + Number + operator--(int); + + Number& + operator+=(Number const& x); + Number& + operator-=(Number const& x); + + Number& + operator*=(Number const& x); + Number& + operator/=(Number const& x); + + explicit operator IOUAmount() const; + + friend constexpr bool + operator==(Number const& x, Number const& y) noexcept + { + return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_; + } + + friend constexpr bool + operator!=(Number const& x, Number const& y) noexcept + { + return !(x == y); + } + + friend constexpr bool + operator<(Number const& x, Number const& y) noexcept + { + // If the two amounts have different signs (zero is treated as positive) + // then the comparison is true iff the left is negative. + bool const lneg = x.mantissa_ < 0; + bool const rneg = y.mantissa_ < 0; + + if (lneg != rneg) + return lneg; + + // Both have same sign and the left is zero: the right must be + // greater than 0. + if (x.mantissa_ == 0) + return y.mantissa_ > 0; + + // Both have same sign, the right is zero and the left is non-zero. + if (y.mantissa_ == 0) + return false; + + // Both have the same sign, compare by exponents: + if (x.exponent_ > y.exponent_) + return lneg; + if (x.exponent_ < y.exponent_) + return !lneg; + + // If equal exponents, compare mantissas + return x.mantissa_ < y.mantissa_; + } + + friend constexpr bool + operator>(Number const& x, Number const& y) noexcept + { + return y < x; + } + + friend constexpr bool + operator<=(Number const& x, Number const& y) noexcept + { + return !(y < x); + } + + friend constexpr bool + operator>=(Number const& x, Number const& y) noexcept + { + return !(x < y); + } + + friend std::ostream& + operator<<(std::ostream& os, Number const& x) + { + return os << to_string(x); + } + +private: + void + normalize(); + constexpr bool + isnormal() const noexcept; + + // The range for the mantissa when normalized + constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL; + constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL; + + // The range for the exponent when normalized + constexpr static int minExponent = -32768; + constexpr static int maxExponent = 32768; + + class guard; +}; + +inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept + : mantissa_{mantissa}, exponent_{exponent} +{ +} + +inline Number::Number(rep mantissa, int exponent) + : mantissa_{mantissa}, exponent_{exponent} +{ + normalize(); +} + +inline Number::Number(rep mantissa) : Number{mantissa, 0} +{ +} + +inline Number::Number(IOUAmount const& x) : Number{x.mantissa(), x.exponent()} +{ +} + +inline constexpr Number::rep +Number::mantissa() const noexcept +{ + return mantissa_; +} + +inline constexpr int +Number::exponent() const noexcept +{ + return exponent_; +} + +inline constexpr Number +Number::operator+() const noexcept +{ + return *this; +} + +inline constexpr Number +Number::operator-() const noexcept +{ + auto x = *this; + x.mantissa_ = -x.mantissa_; + return x; +} + +inline Number& +Number::operator++() +{ + *this += Number{1000000000000000, -15, unchecked{}}; + return *this; +} + +inline Number +Number::operator++(int) +{ + auto x = *this; + ++(*this); + return x; +} + +inline Number& +Number::operator--() +{ + *this -= Number{1000000000000000, -15, unchecked{}}; + return *this; +} + +inline Number +Number::operator--(int) +{ + auto x = *this; + --(*this); + return x; +} + +inline Number& +Number::operator-=(Number const& x) +{ + return *this += -x; +} + +inline Number +operator+(Number const& x, Number const& y) +{ + auto z = x; + z += y; + return z; +} + +inline Number +operator-(Number const& x, Number const& y) +{ + auto z = x; + z -= y; + return z; +} + +inline Number +operator*(Number const& x, Number const& y) +{ + auto z = x; + z *= y; + return z; +} + +inline Number +operator/(Number const& x, Number const& y) +{ + auto z = x; + z /= y; + return z; +} + +inline Number::operator IOUAmount() const +{ + return IOUAmount{mantissa(), exponent()}; +} + +inline constexpr bool +Number::isnormal() const noexcept +{ + auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_; + return minMantissa <= abs_m && abs_m <= maxMantissa && + minExponent <= exponent_ && exponent_ <= maxExponent; +} + +inline constexpr Number +abs(Number x) noexcept +{ + if (x < Number{}) + x = -x; + return x; +} + +// Returns f^n +// Uses a log_2(n) number of mulitiplications + +Number +power(Number f, unsigned n); + +// Returns f^(1/d) +// Uses Newton–Raphson iterations until the result stops changing +// to find the root of the polynomial g(x) = x^d - f + +Number +root(Number f, unsigned d); + +// Returns f^(n/d) + +Number +power(Number f, unsigned n, unsigned d); + +} // namespace ripple + +#endif // RIPPLE_BASICS_NUMBER_H_INCLUDED diff --git a/src/ripple/basics/impl/Number.cpp b/src/ripple/basics/impl/Number.cpp new file mode 100644 index 0000000000..1f056a2d93 --- /dev/null +++ b/src/ripple/basics/impl/Number.cpp @@ -0,0 +1,582 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +#ifdef _MSVC_LANG +#include +using uint128_t = boost::multiprecision::uint128_t; +#else // !defined(_MSVC_LANG) +using uint128_t = __uint128_t; +#endif // !defined(_MSVC_LANG) + +namespace ripple { + +// guard + +class Number::guard +{ + std::uint64_t digits_; + std::uint8_t xbit_ : 1; + std::uint8_t sbit_ : 1; // TODO : get rid of + +public: + explicit guard() : digits_{0}, xbit_{0}, sbit_{0} + { + } + + void + set_positive() noexcept; + void + set_negative() noexcept; + bool + is_negative() const noexcept; + void + push(unsigned d) noexcept; + unsigned + pop() noexcept; + int + round() noexcept; +}; + +inline void +Number::guard::set_positive() noexcept +{ + sbit_ = 0; +} + +inline void +Number::guard::set_negative() noexcept +{ + sbit_ = 1; +} + +inline bool +Number::guard::is_negative() const noexcept +{ + return sbit_ == 1; +} + +inline void +Number::guard::push(unsigned d) noexcept +{ + xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0; + digits_ >>= 4; + digits_ |= (d & 0x0000'0000'0000'000FULL) << 60; +} + +inline unsigned +Number::guard::pop() noexcept +{ + unsigned d = (digits_ & 0xF000'0000'0000'0000) >> 60; + digits_ <<= 4; + return d; +} + +int +Number::guard::round() noexcept +{ + if (digits_ > 0x5000'0000'0000'0000) + return 1; + if (digits_ < 0x5000'0000'0000'0000) + return -1; + if (xbit_) + return 1; + return 0; +} + +// Number + +constexpr Number one{1000000000000000, -15, Number::unchecked{}}; + +void +Number::normalize() +{ + if (mantissa_ == 0) + { + *this = Number{}; + return; + } + bool const negative = (mantissa_ < 0); + if (negative) + mantissa_ = -mantissa_; + auto m = static_cast>(mantissa_); + while ((m < minMantissa) && (exponent_ > minExponent)) + { + m *= 10; + --exponent_; + } + while (m > maxMantissa) + { + if (exponent_ >= maxExponent) + throw std::overflow_error("Number::normalize 1"); + m /= 10; + ++exponent_; + } + mantissa_ = m; + if ((exponent_ < minExponent) || (mantissa_ < minMantissa)) + { + *this = Number{}; + return; + } + + if (exponent_ > maxExponent) + throw std::overflow_error("Number::normalize 2"); + + if (negative) + mantissa_ = -mantissa_; +} + +Number& +Number::operator+=(Number const& y) +{ + if (y == Number{}) + return *this; + if (*this == Number{}) + { + *this = y; + return *this; + } + if (*this == -y) + { + *this = Number{}; + return *this; + } + assert(isnormal() && y.isnormal()); + auto xm = mantissa(); + auto xe = exponent(); + int xn = 1; + if (xm < 0) + { + xm = -xm; + xn = -1; + } + auto ym = y.mantissa(); + auto ye = y.exponent(); + int yn = 1; + if (ym < 0) + { + ym = -ym; + yn = -1; + } + guard g; + if (xe < ye) + { + if (xn == -1) + g.set_negative(); + do + { + g.push(xm % 10); + xm /= 10; + ++xe; + } while (xe < ye); + } + else if (xe > ye) + { + if (yn == -1) + g.set_negative(); + do + { + g.push(ym % 10); + ym /= 10; + ++ye; + } while (xe > ye); + } + if (xn == yn) + { + xm += ym; + if (xm > maxMantissa) + { + g.push(xm % 10); + xm /= 10; + ++xe; + } + auto r = g.round(); + if (r == 1 || (r == 0 && (xm & 1) == 1)) + { + ++xm; + if (xm > maxMantissa) + { + xm /= 10; + ++xe; + } + } + if (xe > maxExponent) + throw std::overflow_error("Number::addition overflow"); + } + else + { + if (xm > ym) + { + xm = xm - ym; + } + else + { + xm = ym - xm; + xe = ye; + xn = yn; + } + while (xm < minMantissa) + { + xm *= 10; + xm -= g.pop(); + --xe; + } + auto r = g.round(); + if (r == 1 || (r == 0 && (xm & 1) == 1)) + { + --xm; + if (xm < minMantissa) + { + xm *= 10; + --xe; + } + } + if (xe < minExponent) + { + xm = 0; + xe = Number{}.exponent_; + } + } + mantissa_ = xm * xn; + exponent_ = xe; + assert(isnormal()); + return *this; +} + +Number& +Number::operator*=(Number const& y) +{ + if (*this == Number{}) + return *this; + if (y == Number{}) + { + *this = y; + return *this; + } + assert(isnormal() && y.isnormal()); + auto xm = mantissa(); + auto xe = exponent(); + int xn = 1; + if (xm < 0) + { + xm = -xm; + xn = -1; + } + auto ym = y.mantissa(); + auto ye = y.exponent(); + int yn = 1; + if (ym < 0) + { + ym = -ym; + yn = -1; + } + auto zm = uint128_t(xm) * uint128_t(ym); + auto ze = xe + ye; + auto zn = xn * yn; + guard g; + while (zm > maxMantissa) + { + g.push(static_cast(zm % 10)); + zm /= 10; + ++ze; + } + xm = static_cast(zm); + xe = ze; + auto r = g.round(); + if (r == 1 || (r == 0 && (xm & 1) == 1)) + { + ++xm; + if (xm > maxMantissa) + { + xm /= 10; + ++xe; + } + } + if (xe < minExponent) + { + xm = 0; + xe = Number{}.exponent_; + } + if (xe > maxExponent) + throw std::overflow_error( + "Number::multiplication overflow : exponent is " + + std::to_string(xe)); + mantissa_ = xm * zn; + exponent_ = xe; + assert(isnormal()); + return *this; +} + +Number& +Number::operator/=(Number const& y) +{ + if (y == Number{}) + throw std::overflow_error("Number: divide by 0"); + int np = 1; + auto nm = mantissa(); + if (nm < 0) + { + nm = -nm; + np = -1; + } + int dp = 1; + auto dm = y.mantissa(); + if (dm < 0) + { + dm = -dm; + dp = -1; + } + // Divide numerator and denominator such that the + // denominator is in the range [1, 10). + const int offset = -15 - y.exponent(); + Number n{nm * (np * dp), exponent() + offset}; + Number d{dm, y.exponent() + offset}; + // Quadratic least squares fit to 1/x in the range [1, 10] + constexpr Number a0{9178756872006464, -16, unchecked{}}; + constexpr Number a1{-2149215784206187, -16, unchecked{}}; + constexpr Number a2{1405502114116773, -17, unchecked{}}; + static_assert(a0.isnormal()); + static_assert(a1.isnormal()); + static_assert(a2.isnormal()); + Number rm2{}; + Number rm1{}; + Number r = a2; + r = (a2 * d + a1) * d + a0; + do + { + rm2 = rm1; + rm1 = r; + r = r + r * (one - d * r); + } while (r != rm1 && r != rm2); + *this = n * r; + return *this; +} + +std::string +to_string(Number const& amount) +{ + // keep full internal accuracy, but make more human friendly if possible + if (amount == Number{}) + return "0"; + + auto const exponent = amount.exponent(); + auto mantissa = amount.mantissa(); + + // Use scientific notation for exponents that are too small or too large + if (((exponent != 0) && ((exponent < -25) || (exponent > -5)))) + { + std::string ret = std::to_string(mantissa); + ret.append(1, 'e'); + ret.append(std::to_string(exponent)); + return ret; + } + + bool negative = false; + + if (mantissa < 0) + { + mantissa = -mantissa; + negative = true; + } + + assert(exponent + 43 > 0); + + size_t const pad_prefix = 27; + size_t const pad_suffix = 23; + + std::string const raw_value(std::to_string(mantissa)); + std::string val; + + val.reserve(raw_value.length() + pad_prefix + pad_suffix); + val.append(pad_prefix, '0'); + val.append(raw_value); + val.append(pad_suffix, '0'); + + size_t const offset(exponent + 43); + + auto pre_from(val.begin()); + auto const pre_to(val.begin() + offset); + + auto const post_from(val.begin() + offset); + auto post_to(val.end()); + + // Crop leading zeroes. Take advantage of the fact that there's always a + // fixed amount of leading zeroes and skip them. + if (std::distance(pre_from, pre_to) > pad_prefix) + pre_from += pad_prefix; + + assert(post_to >= post_from); + + pre_from = std::find_if(pre_from, pre_to, [](char c) { return c != '0'; }); + + // Crop trailing zeroes. Take advantage of the fact that there's always a + // fixed amount of trailing zeroes and skip them. + if (std::distance(post_from, post_to) > pad_suffix) + post_to -= pad_suffix; + + assert(post_to >= post_from); + + post_to = std::find_if( + std::make_reverse_iterator(post_to), + std::make_reverse_iterator(post_from), + [](char c) { return c != '0'; }) + .base(); + + std::string ret; + + if (negative) + ret.append(1, '-'); + + // Assemble the output: + if (pre_from == pre_to) + ret.append(1, '0'); + else + ret.append(pre_from, pre_to); + + if (post_to != post_from) + { + ret.append(1, '.'); + ret.append(post_from, post_to); + } + + return ret; +} + +// Returns f^n +// Uses a log_2(n) number of mulitiplications + +Number +power(Number f, unsigned n) +{ + if (n == 0) + return one; + if (n == 1) + return f; + auto r = power(f, n / 2); + r *= r; + if (n % 2 != 0) + r *= f; + return r; +} + +// Returns f^(1/d) +// Uses Newton–Raphson iterations until the result stops changing +// to find the non-negative root of the polynomial g(x) = x^d - f + +Number +root(Number f, unsigned d) +{ + if (f == one || d == 1) + return f; + if (d == 0) + { + if (f == -one) + return one; + if (abs(f) < one) + return Number{}; + throw std::overflow_error("Number::root infinity"); + } + if (f < Number{} && d % 2 == 0) + throw std::overflow_error("Number::root nan"); + if (f == Number{}) + return f; + + // Scale f into the range (0, 1) such that f's exponent is a multiple of d + auto e = f.exponent() + 16; + auto const di = static_cast(d); + auto ex = [e = e, di = di]() // Euclidean remainder of e/d + { + int k = (e >= 0 ? e : e - (di - 1)) / di; + int k2 = e - k * di; + if (k2 == 0) + return 0; + return di - k2; + }(); + e += ex; + f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e; + bool neg = false; + if (f < Number{}) + { + neg = true; + f = -f; + } + + // Quadratic least squares curve fit of f^(1/d) in the range [0, 1] + auto const D = ((6 * di + 11) * di + 6) * di + 1; + auto const a0 = 3 * di * ((2 * di - 3) * di + 1); + auto const a1 = 24 * di * (2 * di - 1); + auto const a2 = -30 * (di - 1) * di; + Number r = ((Number{a2} * f + Number{a1}) * f + Number{a0}) / Number{D}; + if (neg) + { + f = -f; + r = -r; + } + + // Newton–Raphson iteration of f^(1/d) with initial guess r + // halt when r stops changing, checking for bouncing on the last iteration + Number rm1{}; + Number rm2{}; + do + { + rm2 = rm1; + rm1 = r; + r = (Number(d - 1) * r + f / power(r, d - 1)) / Number(d); + } while (r != rm1 && r != rm2); + + // return r * 10^(e/d) to reverse scaling + return Number{r.mantissa(), r.exponent() + e / di}; +} + +// Returns f^(n/d) + +Number +power(Number f, unsigned n, unsigned d) +{ + if (f == one) + return f; + auto g = std::gcd(n, d); + if (g == 0) + throw std::overflow_error("Number::power nan"); + if (d == 0) + { + if (f == -one) + return one; + if (abs(f) < one) + return Number{}; + if (abs(f) > one) + throw std::overflow_error("Number::power infinity"); + throw std::overflow_error("Number::power nan"); + } + if (n == 0) + return one; + n /= g; + d /= g; + if ((n % 2) == 1 && (d % 2) == 0 && f < Number{}) + throw std::overflow_error("Number::power nan"); + return root(power(f, n), d); +} + +} // namespace ripple diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp new file mode 100644 index 0000000000..570e162872 --- /dev/null +++ b/src/test/basics/Number_test.cpp @@ -0,0 +1,143 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +namespace ripple { + +class Number_test : public beast::unit_test::suite +{ +public: + void + testZero() + { + testcase("zero"); + + Number const z{0, 0}; + + BEAST_EXPECT(z.mantissa() == 0); + BEAST_EXPECT(z.exponent() == Number{}.exponent()); + + BEAST_EXPECT((z + z) == z); + BEAST_EXPECT((z - z) == z); + BEAST_EXPECT(z == -z); + } + + void + test_add() + { + testcase("test_add"); + Number x[]{ + Number{1'000'000'000'000'000, -15}, + Number{-1'000'000'000'000'000, -15}, + Number{-1'000'000'000'000'000, -15}, + Number{-6'555'555'555'555'555, -29}}; + Number y[]{ + Number{6'555'555'555'555'555, -29}, + Number{-6'555'555'555'555'555, -29}, + Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}}; + Number z[]{ + Number{1'000'000'000'000'066, -15}, + Number{-1'000'000'000'000'066, -15}, + Number{-9'999'999'999'999'344, -16}, + Number{9'999'999'999'999'344, -16}}; + for (unsigned i = 0; i < std::size(x); ++i) + { + BEAST_EXPECT(x[i] + y[i] == z[i]); + } + } + + void + test_sub() + { + testcase("test_sub"); + Number x[]{ + Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}}; + Number y[]{ + Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}}; + Number z[]{ + Number{9'999'999'999'999'344, -16}, + Number{-9'999'999'999'999'344, -16}}; + for (unsigned i = 0; i < std::size(x); ++i) + { + BEAST_EXPECT(x[i] - y[i] == z[i]); + } + } + + void + test_div() + { + testcase("test_div"); + Number x[]{Number{1}, Number{1}, Number{0}}; + Number y[]{Number{2}, Number{10}, Number{100}}; + Number z[]{Number{5, -1}, Number{1, -1}, Number{0}}; + for (unsigned i = 0; i < std::size(x); ++i) + { + BEAST_EXPECT(x[i] / y[i] == z[i]); + } + } + + void + test_root() + { + testcase("test_root"); + Number x[]{Number{2}, Number{2'000'000}, Number{2, -30}}; + unsigned y[]{2, 2, 2}; + Number z[]{ + Number{1414213562373095, -15}, + Number{1414213562373095, -12}, + Number{1414213562373095, -30}}; + for (unsigned i = 0; i < std::size(x); ++i) + { + BEAST_EXPECT(root(x[i], y[i]) == z[i]); + } + } + + void + testConversions() + { + testcase("testConversions"); + + IOUAmount x{5, 6}; + Number y = x; + BEAST_EXPECT((y == Number{5, 6})); + IOUAmount z{y}; + BEAST_EXPECT(x == z); + } + + void + run() override + { + testZero(); + test_add(); + test_sub(); + test_div(); + test_root(); + testConversions(); + } +}; + +BEAST_DEFINE_TESTSUITE(Number, ripple_basics, ripple); + +} // namespace ripple