mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 14:35:52 +00:00
AMM Add Number class and associated algorithms
This commit is contained in:
committed by
Elliot Lee
parent
31e7e5a56e
commit
0ee63b7c7b
@@ -50,6 +50,7 @@ target_sources (xrpl_core PRIVATE
|
|||||||
src/ripple/basics/impl/FileUtilities.cpp
|
src/ripple/basics/impl/FileUtilities.cpp
|
||||||
src/ripple/basics/impl/IOUAmount.cpp
|
src/ripple/basics/impl/IOUAmount.cpp
|
||||||
src/ripple/basics/impl/Log.cpp
|
src/ripple/basics/impl/Log.cpp
|
||||||
|
src/ripple/basics/impl/Number.cpp
|
||||||
src/ripple/basics/impl/StringUtilities.cpp
|
src/ripple/basics/impl/StringUtilities.cpp
|
||||||
#[===============================[
|
#[===============================[
|
||||||
main sources:
|
main sources:
|
||||||
@@ -153,6 +154,7 @@ install (
|
|||||||
src/ripple/basics/LocalValue.h
|
src/ripple/basics/LocalValue.h
|
||||||
src/ripple/basics/Log.h
|
src/ripple/basics/Log.h
|
||||||
src/ripple/basics/MathUtilities.h
|
src/ripple/basics/MathUtilities.h
|
||||||
|
src/ripple/basics/Number.h
|
||||||
src/ripple/basics/safe_cast.h
|
src/ripple/basics/safe_cast.h
|
||||||
src/ripple/basics/Slice.h
|
src/ripple/basics/Slice.h
|
||||||
src/ripple/basics/spinlock.h
|
src/ripple/basics/spinlock.h
|
||||||
@@ -737,6 +739,7 @@ if (tests)
|
|||||||
src/test/basics/FileUtilities_test.cpp
|
src/test/basics/FileUtilities_test.cpp
|
||||||
src/test/basics/IOUAmount_test.cpp
|
src/test/basics/IOUAmount_test.cpp
|
||||||
src/test/basics/KeyCache_test.cpp
|
src/test/basics/KeyCache_test.cpp
|
||||||
|
src/test/basics/Number_test.cpp
|
||||||
src/test/basics/PerfLog_test.cpp
|
src/test/basics/PerfLog_test.cpp
|
||||||
src/test/basics/RangeSet_test.cpp
|
src/test/basics/RangeSet_test.cpp
|
||||||
src/test/basics/scope_test.cpp
|
src/test/basics/scope_test.cpp
|
||||||
|
|||||||
322
src/ripple/basics/Number.h
Normal file
322
src/ripple/basics/Number.h
Normal file
@@ -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 <ripple/basics/IOUAmount.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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
|
||||||
582
src/ripple/basics/impl/Number.cpp
Normal file
582
src/ripple/basics/impl/Number.cpp
Normal file
@@ -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 <ripple/basics/Number.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <numeric>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#ifdef _MSVC_LANG
|
||||||
|
#include <boost/multiprecision/cpp_int.hpp>
|
||||||
|
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<std::make_unsigned_t<rep>>(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<unsigned>(zm % 10));
|
||||||
|
zm /= 10;
|
||||||
|
++ze;
|
||||||
|
}
|
||||||
|
xm = static_cast<rep>(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<int>(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
|
||||||
143
src/test/basics/Number_test.cpp
Normal file
143
src/test/basics/Number_test.cpp
Normal file
@@ -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 <ripple/basics/IOUAmount.h>
|
||||||
|
#include <ripple/basics/Number.h>
|
||||||
|
#include <ripple/beast/unit_test.h>
|
||||||
|
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user