Compare commits

..

9 Commits

Author SHA1 Message Date
Mayukha Vadari
a2cee3b6ab Merge branch 'develop' into copilot/convert-boost-to-std-string-view 2026-02-06 11:43:57 -05:00
Ayaz Salikhov
2305bc98a4 chore: Remove CODEOWNERS (#6337) 2026-02-06 11:39:23 -05:00
Bart
677758b1cc perf: Remove unnecessary caches (#5439)
This change removes the cache in `DatabaseNodeImp` and simplifies the caching logic in `SHAMapStoreImp`. As NuDB and RocksDB internally already use caches, additional caches in the code are not very valuable or may even be unnecessary, as also confirmed during preliminary performance analyses.
2026-02-06 09:42:35 -05:00
Bart
25d7c2c4ec chore: Restore unity builds (#6328)
In certain cases, such as when modifying headers used by many compilation units, performing a unity build is slower than when performing a regular build with `ccache` enabled. There is also a benefit to a unity build in that it can detect things such as macro redefinitions within the group of files that are compiled together as a unit. This change therefore restores the ability to perform unity builds. However, instead of running every configuration with and without unity enabled, it is now only enabled for a single configuration to maintain lower computational use.

As part of restoring the code, it became clear that currently two configurations have coverage enabled, since the check doesn't focus specifically on Debian Bookworm so it also applies to Debian Trixie. This has been fixed too in this change.
2026-02-06 14:12:45 +00:00
copilot-swe-agent[bot]
ae624ab894 Remove unnecessary boost/beast/core/string.hpp includes
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-01-30 17:27:50 +00:00
copilot-swe-agent[bot]
21ac390a37 Fix lambda return type inconsistency in ServerHandler
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-01-30 17:02:18 +00:00
copilot-swe-agent[bot]
86aca89bc4 Fix std::string_view constructor usage in BaseWSPeer
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-01-30 16:28:46 +00:00
copilot-swe-agent[bot]
b26dd49fb6 Replace all boost::beast::string_view with std::string_view
Co-authored-by: mvadari <8029314+mvadari@users.noreply.github.com>
2026-01-30 16:26:37 +00:00
copilot-swe-agent[bot]
0969281538 Initial plan 2026-01-30 16:23:54 +00:00
30 changed files with 500 additions and 1232 deletions

8
.github/CODEOWNERS vendored
View File

@@ -1,8 +0,0 @@
# Allow anyone to review any change by default.
*
# Require the rpc-reviewers team to review changes to the rpc code.
include/xrpl/protocol/ @xrplf/rpc-reviewers
src/libxrpl/protocol/ @xrplf/rpc-reviewers
src/xrpld/rpc/ @xrplf/rpc-reviewers
src/xrpld/app/misc/ @xrplf/rpc-reviewers

View File

@@ -196,11 +196,22 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
# Enable code coverage for Debian Bookworm using GCC 15 in Debug on
# linux/amd64
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
f"{os['distro_name']}-{os['distro_version']}" == "debian-bookworm"
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0 {cmake_args}"
cmake_args = f"{cmake_args} -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
# Enable unity build for Ubuntu Jammy using GCC 12 in Debug on
# linux/amd64.
if (
f"{os['distro_name']}-{os['distro_version']}" == "ubuntu-jammy"
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"{cmake_args} -Dunity=ON"
# Generate a unique name for the configuration, e.g. macos-arm64-debug
# or debian-bookworm-gcc-12-amd64-release.
@@ -217,6 +228,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
config_name += f"-{build_type.lower()}"
if "-Dcoverage=ON" in cmake_args:
config_name += "-coverage"
if "-Dunity=ON" in cmake_args:
config_name += "-unity"
# Add the configuration to the list, with the most unique fields first,
# so that they are easier to identify in the GitHub Actions UI, as long

View File

@@ -575,10 +575,16 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
| `assert` | OFF | Enable assertions. |
| `coverage` | OFF | Prepare the coverage report. |
| `tests` | OFF | Build tests. |
| `unity` | OFF | Configure a unity build. |
| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. |
| `werr` | OFF | Treat compilation warnings as errors |
| `wextra` | OFF | Enable additional compilation warnings |
[Unity builds][5] may be faster for the first build (at the cost of much more
memory) since they concatenate sources into fewer translation units. Non-unity
builds may be faster for incremental builds, and can be helpful for detecting
`#include` omissions.
## Troubleshooting
### Conan
@@ -645,6 +651,7 @@ If you want to experiment with a new package, follow these steps:
[1]: https://github.com/conan-io/conan-center-index/issues/13168
[2]: https://en.cppreference.com/w/cpp/compiler_support/20
[3]: https://docs.conan.io/en/latest/getting_started.html
[5]: https://en.wikipedia.org/wiki/Unity_build
[6]: https://github.com/boostorg/beast/issues/2648
[7]: https://github.com/boostorg/beast/issues/2661
[gcovr]: https://gcovr.com/en/stable/getting-started.html

View File

@@ -940,23 +940,7 @@
#
# path Location to store the database
#
# Optional keys
#
# cache_size Size of cache for database records. Default is 16384.
# Setting this value to 0 will use the default value.
#
# cache_age Length of time in minutes to keep database records
# cached. Default is 5 minutes. Setting this value to
# 0 will use the default value.
#
# Note: if neither cache_size nor cache_age is
# specified, the cache for database records will not
# be created. If only one of cache_size or cache_age
# is specified, the cache will be created using the
# default value for the unspecified parameter.
#
# Note: the cache will not be created if online_delete
# is specified.
# Optional keys for NuDB and RocksDB:
#
# fast_load Boolean. If set, load the last persisted ledger
# from disk upon process start before syncing to
@@ -964,8 +948,6 @@
# if sufficient IOPS capacity is available.
# Default 0.
#
# Optional keys for NuDB or RocksDB:
#
# earliest_seq The default is 32570 to match the XRP ledger
# network's earliest allowed sequence. Alternate
# networks may set this value. Minimum value of 1.

View File

@@ -4,7 +4,12 @@
include(target_protobuf_sources)
# Protocol buffers cannot participate in a unity build,
# because all the generated sources
# define a bunch of `static const` variables with the same names,
# so we just build them as a separate library.
add_library(xrpl.libpb)
set_target_properties(xrpl.libpb PROPERTIES UNITY_BUILD OFF)
target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto
PROTOS include/xrpl/proto/xrpl.proto)

View File

@@ -30,6 +30,14 @@ if (tests)
endif ()
endif ()
option(unity "Creates a build using UNITY support in cmake." OFF)
if (unity)
if (NOT is_ci)
set(CMAKE_UNITY_BUILD_BATCH_SIZE 15 CACHE STRING "")
endif ()
set(CMAKE_UNITY_BUILD ON CACHE BOOL "Do a unity build")
endif ()
if (is_clang AND is_linux)
option(voidstar "Enable Antithesis instrumentation." OFF)
endif ()

View File

@@ -23,6 +23,7 @@ class Xrpl(ConanFile):
"shared": [True, False],
"static": [True, False],
"tests": [True, False],
"unity": [True, False],
"xrpld": [True, False],
}
@@ -54,6 +55,7 @@ class Xrpl(ConanFile):
"shared": False,
"static": True,
"tests": False,
"unity": False,
"xrpld": False,
"date/*:header_only": True,
"ed25519/*:shared": False,
@@ -166,6 +168,7 @@ class Xrpl(ConanFile):
tc.variables["rocksdb"] = self.options.rocksdb
tc.variables["BUILD_SHARED_LIBS"] = self.options.shared
tc.variables["static"] = self.options.static
tc.variables["unity"] = self.options.unity
tc.variables["xrpld"] = self.options.xrpld
tc.generate()

View File

@@ -9,10 +9,6 @@
#include <ostream>
#include <string>
#ifdef _MSC_VER
#include <boost/multiprecision/cpp_int.hpp>
#endif // !defined(_MSC_VER)
namespace xrpl {
class Number;
@@ -20,37 +16,18 @@ class Number;
std::string
to_string(Number const& amount);
/** Returns a rough estimate of log10(value).
*
* The return value is a pair (log, rem), where log is the estimated log10,
* and rem is value divided by 10^log. If rem is 1, then value is an exact
* power of ten, and log is the exact log10(value).
*
* This function only works for positive values.
*/
template <typename T>
constexpr std::pair<int, T>
logTenEstimate(T value)
{
int log = 0;
T remainder = value;
while (value >= 10)
{
if (value % 10 == 0)
remainder = remainder / 10;
value /= 10;
++log;
}
return {log, remainder};
}
template <typename T>
constexpr std::optional<int>
logTen(T value)
{
auto const est = logTenEstimate(value);
if (est.second == 1)
return est.first;
int log = 0;
while (value >= 10 && value % 10 == 0)
{
value /= 10;
++log;
}
if (value == 1)
return log;
return std::nullopt;
}
@@ -64,10 +41,12 @@ isPowerOfTen(T value)
/** MantissaRange defines a range for the mantissa of a normalized Number.
*
* The mantissa is in the range [min, max], where
* * min is a power of 10, and
* * max = min * 10 - 1.
*
* The mantissa_scale enum indicates whether the range is "small" or "large".
* This intentionally restricts the number of MantissaRanges that can be
* used to two: one for each scale.
* instantiated to two: one for each scale.
*
* The "small" scale is based on the behavior of STAmount for IOUs. It has a min
* value of 10^15, and a max value of 10^16-1. This was sufficient for
@@ -81,8 +60,8 @@ isPowerOfTen(T value)
* "large" scale.
*
* The "large" scale is intended to represent all values that can be represented
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 2^63/10+1
* (truncated), and a max value of 2^63-1.
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
* value of 10^19-1.
*
* Note that if the mentioned amendments are eventually retired, this class
* should be left in place, but the "small" scale option should be removed. This
@@ -94,50 +73,25 @@ struct MantissaRange
enum mantissa_scale { small, large };
explicit constexpr MantissaRange(mantissa_scale scale_)
: max(getMax(scale_))
, min(computeMin(max))
, referenceMin(getReferenceMin(scale_, min))
, log(computeLog(min))
, scale(scale_)
: min(getMin(scale_)), max(min * 10 - 1), log(logTen(min).value_or(-1)), scale(scale_)
{
// Since this is constexpr, if any of these throw, it won't compile
if (min * 10 <= max)
throw std::out_of_range("min * 10 <= max");
if (max / 10 >= min)
throw std::out_of_range("max / 10 >= min");
if ((min - 1) * 10 > max)
throw std::out_of_range("(min - 1) * 10 > max");
// This is a little hacky
if ((max + 10) / 10 < min)
throw std::out_of_range("(max + 10) / 10 < min");
}
// Explicitly delete copy and move operations
MantissaRange(MantissaRange const&) = delete;
MantissaRange(MantissaRange&&) = delete;
MantissaRange&
operator=(MantissaRange const&) = delete;
MantissaRange&
operator=(MantissaRange&&) = delete;
rep max;
rep min;
// This is not a great name. Used to determine if mantissas are in range,
// but have fewer digits than max
rep referenceMin;
rep max;
int log;
mantissa_scale scale;
private:
static constexpr rep
getMax(mantissa_scale scale)
getMin(mantissa_scale scale_)
{
switch (scale)
switch (scale_)
{
case small:
return 9'999'999'999'999'999ULL;
return 1'000'000'000'000'000ULL;
case large:
return std::numeric_limits<std::int64_t>::max();
return 1'000'000'000'000'000'000ULL;
default:
// Since this can never be called outside a non-constexpr
// context, this throw assures that the build fails if an
@@ -145,59 +99,19 @@ private:
throw std::runtime_error("Unknown mantissa scale");
}
}
static constexpr rep
computeMin(rep max)
{
return max / 10 + 1;
}
static constexpr rep
getReferenceMin(mantissa_scale scale, rep min)
{
switch (scale)
{
case large:
return 1'000'000'000'000'000'000ULL;
default:
if (isPowerOfTen(min))
return min;
throw std::runtime_error("Unknown/bad mantissa scale");
}
}
static constexpr rep
computeLog(rep min)
{
auto const estimate = logTenEstimate(min);
return estimate.first + (estimate.second == 1 ? 0 : 1);
}
};
// Like std::integral, but only 64-bit integral types.
template <class T>
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
namespace detail {
#ifdef _MSC_VER
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
template <class T>
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
} // namespace detail
/** Number is a floating point type that can represent a wide range of values.
*
* It can represent all values that can be represented by an STAmount -
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
* as much precision as those types require.
*
* ---- Internal Operational Representation ----
* ---- Internal Representation ----
*
* Internally, Number is represented with three values:
* 1. a bool sign flag,
@@ -212,21 +126,15 @@ concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>
*
* A non-zero mantissa is (almost) always normalized, meaning it and the
* exponent are grown or shrunk until the mantissa is in the range
* [MantissaRange.referenceMin, MantissaRange.referenceMin * 10 - 1].
*
* This internal representation is only used during some operations to ensure
* that the mantissa is a known, predictable size. The class itself stores the
* values using the external representation described below.
* [MantissaRange.min, MantissaRange.max].
*
* Note:
* 1. Normalization can be disabled by using the "unchecked" ctor tag. This
* should only be used at specific conversion points, some constexpr
* values, and in unit tests.
* 2. Unlike MantissaRange.min, referenceMin is always an exact power of 10,
* so a mantissa in the internal representation will always have a
* consistent number of digits.
* 3. The functions toInternal() and fromInternal() are used to convert
* between the two representations.
* 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that
* fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and
* 10^20-1 > 2^64-1). This avoids under- and overflows.
*
* ---- External Interface ----
*
@@ -239,12 +147,13 @@ concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>
* represent the full range of valid XRP and MPT integer values accurately.
*
* Note:
* 1. The "large" mantissa range is (2^63/10+1) to 2^63-1. 2^63-1 is between
* 10^18 and 10^19-1, and (2^63/10+1) is between 10^17 and 10^18-1. Thus,
* the mantissa may have 18 or 19 digits. This value will be modified to
* always have 19 digits before some operations to ensure consistency.
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
* mantissa range.
* 2. The functions mantissa() and exponent() return the external view of the
* Number value, specifically using a signed 63-bit mantissa.
* Number value, specifically using a signed 63-bit mantissa. This may
* require altering the internal representation to fit into that range
* before the value is returned. The interface guarantees consistency of
* the two values.
* 3. Number cannot represent -2^63 (std::numeric_limits<std::int64_t>::min())
* as an exact integer, but it doesn't need to, because all asset values
* on-ledger are non-negative. This is due to implementation details of
@@ -299,7 +208,8 @@ class Number
using rep = std::int64_t;
using internalrep = MantissaRange::rep;
rep mantissa_{0};
bool negative_{false};
internalrep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
public:
@@ -307,6 +217,10 @@ public:
constexpr static int minExponent = -32768;
constexpr static int maxExponent = 32768;
constexpr static internalrep maxRep = std::numeric_limits<rep>::max();
static_assert(maxRep == 9'223'372'036'854'775'807);
static_assert(-maxRep == std::numeric_limits<rep>::min() + 1);
// May need to make unchecked private
struct unchecked
{
@@ -380,7 +294,7 @@ public:
friend constexpr bool
operator==(Number const& x, Number const& y) noexcept
{
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
}
friend constexpr bool
@@ -394,8 +308,8 @@ public:
{
// 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;
bool const lneg = x.negative_;
bool const rneg = y.negative_;
if (lneg != rneg)
return lneg;
@@ -423,7 +337,7 @@ public:
constexpr int
signum() const noexcept
{
return mantissa_ < 0 ? -1 : (mantissa_ ? 1 : 0);
return negative_ ? -1 : (mantissa_ ? 1 : 0);
}
Number
@@ -462,9 +376,6 @@ public:
friend Number
root2(Number f);
friend Number
power(Number const& f, unsigned n, unsigned d);
// Thread local rounding control. Default is to_nearest
enum rounding_mode { to_nearest, towards_zero, downward, upward };
static rounding_mode
@@ -529,39 +440,22 @@ private:
static_assert(isPowerOfTen(smallRange.min));
static_assert(smallRange.min == 1'000'000'000'000'000LL);
static_assert(smallRange.max == 9'999'999'999'999'999LL);
static_assert(smallRange.referenceMin == smallRange.min);
static_assert(smallRange.log == 15);
static_assert(smallRange.min < maxRep);
static_assert(smallRange.max < maxRep);
constexpr static MantissaRange largeRange{MantissaRange::large};
static_assert(!isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 922'337'203'685'477'581ULL);
static_assert(largeRange.max == internalrep(9'223'372'036'854'775'807ULL));
static_assert(largeRange.max == std::numeric_limits<rep>::max());
static_assert(largeRange.referenceMin == 1'000'000'000'000'000'000ULL);
static_assert(isPowerOfTen(largeRange.min));
static_assert(largeRange.min == 1'000'000'000'000'000'000ULL);
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
static_assert(largeRange.log == 18);
// There are 2 values that will not fit in largeRange without some extra
// work
// * 9223372036854775808
// * 9223372036854775809
// They both end up < min, but with a leftover. If they round up, everything
// will be fine. If they don't, we'll need to bring them up into range.
// Guard::bringIntoRange handles this situation.
static_assert(largeRange.min < maxRep);
static_assert(largeRange.max > maxRep);
// The range for the mantissa when normalized.
// Use reference_wrapper to avoid making copies, and prevent accidentally
// changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> range_;
// And one is needed because it needs to choose between oneSmall and
// oneLarge based on the current range
static Number
one(MantissaRange const& range);
static Number
root(MantissaRange const& range, Number f, unsigned d);
void
normalize(MantissaRange const& range);
void
normalize();
@@ -584,14 +478,11 @@ private:
friend void
doNormalize(
bool& negative,
T& mantissa,
int& exponent,
T& mantissa_,
int& exponent_,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa);
bool
isnormal(MantissaRange const& range) const noexcept;
bool
isnormal() const noexcept;
@@ -601,64 +492,18 @@ private:
Number
shiftExponent(int exponentDelta) const;
// Safely return the absolute value of a rep (int64) mantissa as an internalrep (uint64).
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
static internalrep
externalToInternal(rep mantissa);
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep = internalrep>
std::tuple<bool, Rep, int>
toInternal(MantissaRange const& range) const;
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep = internalrep>
std::tuple<bool, Rep, int>
toInternal() const;
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool expectNormal = true, detail::UnsignedMantissa Rep = internalrep>
void
fromInternal(bool negative, Rep mantissa, int exponent, MantissaRange const* pRange);
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool expectNormal = true, detail::UnsignedMantissa Rep = internalrep>
void
fromInternal(bool negative, Rep mantissa, int exponent);
class Guard;
public:
constexpr static internalrep largestMantissa = largeRange.max;
};
inline constexpr Number::Number(bool negative, internalrep mantissa, int exponent, unchecked) noexcept
: mantissa_{negative ? -static_cast<rep>(mantissa) : static_cast<rep>(mantissa)}, exponent_{exponent}
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
{
}
@@ -669,6 +514,12 @@ inline constexpr Number::Number(internalrep mantissa, int exponent, unchecked) n
constexpr static Number numZero{};
inline Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
: Number(negative, mantissa, exponent, unchecked{})
{
normalize();
}
inline Number::Number(internalrep mantissa, int exponent, normalized) : Number(false, mantissa, exponent, normalized{})
{
}
@@ -690,7 +541,17 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
inline constexpr Number::rep
Number::mantissa() const noexcept
{
return mantissa_;
auto m = mantissa_;
if (m > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (m % 10 == 0 && m / 10 <= maxRep),
"xrpl::Number::mantissa",
"large normalized mantissa has no remainder");
m /= 10;
}
auto const sign = negative_ ? -1 : 1;
return sign * static_cast<Number::rep>(m);
}
/** Returns the exponent of the external view of the Number.
@@ -701,7 +562,16 @@ Number::mantissa() const noexcept
inline constexpr int
Number::exponent() const noexcept
{
return exponent_;
auto e = exponent_;
if (mantissa_ > maxRep)
{
XRPL_ASSERT_PARTS(
!isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep),
"xrpl::Number::exponent",
"large normalized mantissa has no remainder");
++e;
}
return e;
}
inline constexpr Number
@@ -716,7 +586,7 @@ Number::operator-() const noexcept
if (mantissa_ == 0)
return Number{};
auto x = *this;
x.mantissa_ = -x.mantissa_;
x.negative_ = !x.negative_;
return x;
}
@@ -797,55 +667,39 @@ Number::min() noexcept
inline Number
Number::max() noexcept
{
return Number{false, range_.get().max, maxExponent, unchecked{}};
return Number{false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
}
inline Number
Number::lowest() noexcept
{
return Number{true, range_.get().max, maxExponent, unchecked{}};
}
inline bool
Number::isnormal(MantissaRange const& range) const noexcept
{
auto const abs_m = externalToInternal(mantissa_);
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max && //
minExponent <= exponent_ && exponent_ <= maxExponent);
return Number{true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
}
inline bool
Number::isnormal() const noexcept
{
return isnormal(range_);
MantissaRange const& range = range_;
auto const abs_m = mantissa_;
return *this == Number{} ||
(range.min <= abs_m && abs_m <= range.max && (abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ &&
exponent_ <= maxExponent);
}
template <Integral64 T>
std::pair<T, int>
Number::normalizeToRange(T minMantissa, T maxMantissa) const
{
bool negative = mantissa_ < 0;
internalrep mantissa = externalToInternal(mantissa_);
bool negative = negative_;
internalrep mantissa = mantissa_;
int exponent = exponent_;
if constexpr (std::is_unsigned_v<T>)
{
XRPL_ASSERT_PARTS(!negative, "xrpl::Number::normalizeToRange", "Number is non-negative for unsigned range.");
// To avoid logical errors in release builds, throw if the Number is
// negative for an unsigned range.
if (negative)
throw std::runtime_error(
"Number::normalizeToRange: Number is negative for "
"unsigned range.");
}
Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa);
// Cast mantissa to signed type first (if T is a signed type) to avoid
// unsigned integer overflow when multiplying by negative sign
T signedMantissa = negative ? -static_cast<T>(mantissa) : static_cast<T>(mantissa);
return std::make_pair(signedMantissa, exponent);
auto const sign = negative ? -1 : 1;
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
}
inline constexpr Number

View File

@@ -10,6 +10,7 @@
#include <cctype>
#include <iterator>
#include <string>
#include <string_view>
#include <vector>
namespace beast {
@@ -178,7 +179,7 @@ split_commas(FwdIt first, FwdIt last)
template <class Result = std::vector<std::string>>
Result
split_commas(boost::beast::string_view const& s)
split_commas(std::string_view const& s)
{
return split_commas(s.begin(), s.end());
}

View File

@@ -1,20 +1,19 @@
#pragma once
#include <boost/beast/core/string.hpp>
#include <functional>
#include <string>
#include <string_view>
namespace Json {
class Value;
using Output = std::function<void(boost::beast::string_view const&)>;
using Output = std::function<void(std::string_view const&)>;
inline Output
stringOutput(std::string& s)
{
return [&](boost::beast::string_view const& b) { s.append(b.data(), b.size()); };
return [&](std::string_view const& b) { s.append(b.data(), b.size()); };
}
/** Writes a minimal representation of a Json value to an Output in O(n) time.

View File

@@ -133,10 +133,6 @@ public:
std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback);
/** Remove expired entries from the positive and negative caches. */
virtual void
sweep() = 0;
/** Gather statistics pertaining to read and write activities.
*
* @param obj Json object reference into which to place counters.

View File

@@ -23,32 +23,6 @@ public:
beast::Journal j)
: Database(scheduler, readThreads, config, j), backend_(std::move(backend))
{
std::optional<int> cacheSize, cacheAge;
if (config.exists("cache_size"))
{
cacheSize = get<int>(config, "cache_size");
if (cacheSize.value() < 0)
{
Throw<std::runtime_error>("Specified negative value for cache_size");
}
}
if (config.exists("cache_age"))
{
cacheAge = get<int>(config, "cache_age");
if (cacheAge.value() < 0)
{
Throw<std::runtime_error>("Specified negative value for cache_age");
}
}
if (cacheSize != 0 || cacheAge != 0)
{
cache_ = std::make_shared<TaggedCache<uint256, NodeObject>>(
"DatabaseNodeImp", cacheSize.value_or(0), std::chrono::minutes(cacheAge.value_or(0)), stopwatch(), j);
}
XRPL_ASSERT(
backend_,
"xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null "
@@ -103,13 +77,7 @@ public:
std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback) override;
void
sweep() override;
private:
// Cache for database objects. This cache is not always initialized. Check
// for null before using.
std::shared_ptr<TaggedCache<uint256, NodeObject>> cache_;
// Persistent key/value storage
std::shared_ptr<Backend> backend_;

View File

@@ -55,9 +55,6 @@ public:
void
sync() override;
void
sweep() override;
private:
std::shared_ptr<Backend> writableBackend_;
std::shared_ptr<Backend> archiveBackend_;

View File

@@ -231,7 +231,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
/** The maximum amount of MPTokenIssuance */
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
static_assert(Number::largestMantissa >= maxMPTokenAmount);
static_assert(Number::maxRep >= maxMPTokenAmount);
/** The maximum length of Data payload */
std::size_t constexpr maxDataPayloadLength = 256;

View File

@@ -521,7 +521,6 @@ STAmount::fromNumber(A const& a, Number const& number)
return STAmount{asset, intValue, 0, negative};
}
XRPL_ASSERT_PARTS(working.signum() >= 0, "xrpl::STAmount::fromNumber", "non-negative Number to normalize");
auto const [mantissa, exponent] = working.normalizeToRange(cMinValue, cMaxValue);
return STAmount{asset, mantissa, exponent, negative};

View File

@@ -23,7 +23,7 @@ systemName()
/** Number of drops in the genesis account. */
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
static_assert(Number::largestMantissa >= INITIAL_XRP.drops());
static_assert(Number::maxRep >= INITIAL_XRP.drops());
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -17,6 +17,7 @@
#include <functional>
#include <list>
#include <string_view>
namespace xrpl {
@@ -48,7 +49,7 @@ private:
bool ping_active_ = false;
boost::beast::websocket::ping_data payload_;
error_code ec_;
std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)> control_callback_;
std::function<void(boost::beast::websocket::frame_type, std::string_view)> control_callback_;
public:
template <class Body, class Headers>
@@ -136,7 +137,7 @@ protected:
on_ping(error_code const& ec);
void
on_ping_pong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload);
on_ping_pong(boost::beast::websocket::frame_type kind, std::string_view payload);
void
on_timer(error_code ec);
@@ -389,11 +390,11 @@ BaseWSPeer<Handler, Impl>::on_ping(error_code const& ec)
template <class Handler, class Impl>
void
BaseWSPeer<Handler, Impl>::on_ping_pong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload)
BaseWSPeer<Handler, Impl>::on_ping_pong(boost::beast::websocket::frame_type kind, std::string_view payload)
{
if (kind == boost::beast::websocket::frame_type::pong)
{
boost::beast::string_view p(payload_.begin());
std::string_view p(payload_.begin(), payload_.size());
if (payload == p)
{
close_on_timer_ = false;

View File

@@ -11,16 +11,18 @@
#include <numeric>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#ifdef _MSC_VER
#pragma message("Using boost::multiprecision::uint128_t and int128_t")
#endif
using uint128_t = xrpl::detail::uint128_t;
using int128_t = xrpl::detail::int128_t;
#include <boost/multiprecision/cpp_int.hpp>
using uint128_t = boost::multiprecision::uint128_t;
using int128_t = boost::multiprecision::int128_t;
#else // !defined(_MSC_VER)
using uint128_t = __uint128_t;
using int128_t = __int128_t;
#endif // !defined(_MSC_VER)
namespace xrpl {
@@ -59,6 +61,9 @@ Number::setMantissaScale(MantissaRange::mantissa_scale scale)
// precision to an operation. This enables the final result
// to be correctly rounded to the internal precision of Number.
template <class T>
concept UnsignedMantissa = std::is_unsigned_v<T> || std::is_same_v<T, uint128_t>;
class Number::Guard
{
std::uint64_t digits_; // 16 decimal guard digits
@@ -94,7 +99,7 @@ public:
round() noexcept;
// Modify the result to the correctly rounded value
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
doRoundUp(
bool& negative,
@@ -102,22 +107,22 @@ public:
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
std::string_view location);
std::string location);
// Modify the result to the correctly rounded value
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
// Modify the result to the correctly rounded value
void
doRound(rep& drops, std::string_view location);
doRound(rep& drops, std::string location);
private:
void
doPush(unsigned d) noexcept;
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
};
@@ -204,7 +209,7 @@ Number::Guard::round() noexcept
return 0;
}
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa)
{
@@ -219,13 +224,13 @@ Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent, intern
{
constexpr Number zero = Number{};
negative = false;
negative = zero.negative_;
mantissa = zero.mantissa_;
exponent = zero.exponent_;
}
}
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
Number::Guard::doRoundUp(
bool& negative,
@@ -233,7 +238,7 @@ Number::Guard::doRoundUp(
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
std::string_view location)
std::string location)
{
auto r = round();
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
@@ -241,7 +246,7 @@ Number::Guard::doRoundUp(
++mantissa;
// Ensure mantissa after incrementing fits within both the
// min/maxMantissa range and is a valid "rep".
if (mantissa > maxMantissa)
if (mantissa > maxMantissa || mantissa > maxRep)
{
mantissa /= 10;
++exponent;
@@ -249,10 +254,10 @@ Number::Guard::doRoundUp(
}
bringIntoRange(negative, mantissa, exponent, minMantissa);
if (exponent > maxExponent)
throw std::overflow_error(std::string{location});
throw std::overflow_error(location);
}
template <detail::UnsignedMantissa T>
template <UnsignedMantissa T>
void
Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa)
{
@@ -271,22 +276,21 @@ Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent, internalr
// Modify the result to the correctly rounded value
void
Number::Guard::doRound(rep& drops, std::string_view location)
Number::Guard::doRound(rep& drops, std::string location)
{
auto r = round();
if (r == 1 || (r == 0 && (drops & 1) == 1))
{
auto const& range = range_.get();
if (drops >= range.max)
if (drops >= maxRep)
{
static_assert(sizeof(internalrep) == sizeof(rep));
// This should be impossible, because it's impossible to represent
// "largestMantissa + 0.6" in Number, regardless of the scale. There aren't
// enough digits available. You'd either get a mantissa of "largestMantissa "
// or "largestMantissa / 10 + 1", neither of which will round up when
// "maxRep + 0.6" in Number, regardless of the scale. There aren't
// enough digits available. You'd either get a mantissa of "maxRep"
// or "(maxRep + 1) / 10", neither of which will round up when
// converting to rep, though the latter might overflow _before_
// rounding.
throw std::overflow_error(std::string{location}); // LCOV_EXCL_LINE
throw std::overflow_error(location); // LCOV_EXCL_LINE
}
++drops;
}
@@ -306,126 +310,23 @@ Number::externalToInternal(rep mantissa)
// If the mantissa is already positive, just return it
if (mantissa >= 0)
return mantissa;
// If the mantissa is negative, but fits within the positive range of rep,
// return it negated
if (mantissa >= -std::numeric_limits<rep>::max())
return -mantissa;
// Cast to unsigned before negating to avoid undefined behavior
// when v == INT64_MIN (negating INT64_MIN in signed is UB)
return -static_cast<internalrep>(mantissa);
}
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal(MantissaRange const& range) const
{
auto exponent = exponent_;
bool const negative = mantissa_ < 0;
// It should be impossible for mantissa_ to be INT64_MIN, but use externalToInternal just in case.
Rep mantissa = static_cast<Rep>(externalToInternal(mantissa_));
auto const referenceMin = range.referenceMin;
auto const minMantissa = range.min;
if (mantissa != 0 && mantissa >= minMantissa && mantissa < referenceMin)
{
// Ensure the mantissa has the correct number of digits
mantissa *= 10;
--exponent;
XRPL_ASSERT_PARTS(
mantissa >= referenceMin && mantissa < referenceMin * 10,
"xrpl::Number::toInternal()",
"Number is within reference range and has 'log' digits");
}
return {negative, mantissa, exponent};
}
/** Breaks down the number into components, potentially de-normalizing it.
*
* Ensures that the mantissa always has exactly range_.log + 1 digits.
*
*/
template <detail::UnsignedMantissa Rep>
std::tuple<bool, Rep, int>
Number::toInternal() const
{
return toInternal(range_);
}
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all
* in their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near
* normalized", meaning that the mantissa has to be modified at most once to
* bring it back into range.
*
*/
template <bool expectNormal, detail::UnsignedMantissa Rep>
void
Number::fromInternal(bool negative, Rep mantissa, int exponent, MantissaRange const* pRange)
{
if constexpr (std::is_same_v<std::bool_constant<expectNormal>, std::false_type>)
{
if (!pRange)
throw std::runtime_error("Missing range to Number::fromInternal!");
auto const& range = *pRange;
auto const maxMantissa = range.max;
auto const minMantissa = range.min;
XRPL_ASSERT_PARTS(mantissa >= minMantissa, "xrpl::Number::fromInternal", "mantissa large enough");
if (mantissa > maxMantissa || mantissa < minMantissa)
{
normalize(negative, mantissa, exponent, range.min, maxMantissa);
}
XRPL_ASSERT_PARTS(
mantissa >= minMantissa && mantissa <= maxMantissa, "xrpl::Number::fromInternal", "mantissa in range");
}
// mantissa is unsigned, but it might not be uint64
mantissa_ = static_cast<rep>(static_cast<internalrep>(mantissa));
if (negative)
mantissa_ = -mantissa_;
exponent_ = exponent;
XRPL_ASSERT_PARTS(
(pRange && isnormal(*pRange)) || isnormal(), "xrpl::Number::fromInternal", "Number is normalized");
}
/** Rebuilds the number from components.
*
* If "expectNormal" is true, the values are expected to be normalized - all in
* their valid ranges.
*
* If "expectNormal" is false, the values are expected to be "near normalized",
* meaning that the mantissa has to be modified at most once to bring it back
* into range.
*
*/
template <bool expectNormal, detail::UnsignedMantissa Rep>
void
Number::fromInternal(bool negative, Rep mantissa, int exponent)
{
MantissaRange const* pRange = nullptr;
if constexpr (std::is_same_v<std::bool_constant<expectNormal>, std::false_type>)
{
pRange = &Number::range_.get();
}
fromInternal(negative, mantissa, exponent, pRange);
// If the mantissa doesn't fit within the positive range, convert to
// int128_t, negate that, and cast it back down to the internalrep
// In practice, this is only going to cover the case of
// std::numeric_limits<rep>::min().
int128_t temp = mantissa;
return static_cast<internalrep>(-temp);
}
constexpr Number
Number::oneSmall()
{
return Number{false, Number::smallRange.referenceMin, -Number::smallRange.log, Number::unchecked{}};
return Number{false, Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}};
};
constexpr Number oneSml = Number::oneSmall();
@@ -433,84 +334,101 @@ constexpr Number oneSml = Number::oneSmall();
constexpr Number
Number::oneLarge()
{
return Number{false, Number::largeRange.referenceMin, -Number::largeRange.log, Number::unchecked{}};
return Number{false, Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}};
};
constexpr Number oneLrg = Number::oneLarge();
Number
Number::one(MantissaRange const& range)
Number::one()
{
if (&range == &smallRange)
if (&range_.get() == &smallRange)
return oneSml;
XRPL_ASSERT(&range == &largeRange, "Number::one() : valid range");
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
return oneLrg;
}
Number
Number::one()
{
return one(range_);
}
// Use the member names in this static function for now so the diff is cleaner
// TODO: Rename the function parameters to get rid of the "_" suffix
template <class T>
void
doNormalize(
bool& negative,
T& mantissa,
int& exponent,
T& mantissa_,
int& exponent_,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa)
{
auto constexpr minExponent = Number::minExponent;
auto constexpr maxExponent = Number::maxExponent;
auto constexpr maxRep = Number::maxRep;
using Guard = Number::Guard;
constexpr Number zero = Number{};
if (mantissa == 0 || (mantissa < minMantissa && exponent <= minExponent))
if (mantissa_ == 0)
{
mantissa = zero.mantissa_;
exponent = zero.exponent_;
negative = false;
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
negative = zero.negative_;
return;
}
auto m = mantissa;
while ((m < minMantissa) && (exponent > minExponent))
auto m = mantissa_;
while ((m < minMantissa) && (exponent_ > minExponent))
{
m *= 10;
--exponent;
--exponent_;
}
Guard g;
if (negative)
g.set_negative();
while (m > maxMantissa)
{
if (exponent >= maxExponent)
if (exponent_ >= maxExponent)
throw std::overflow_error("Number::normalize 1");
g.push(m % 10);
m /= 10;
++exponent;
++exponent_;
}
if ((exponent < minExponent) || (m == 0))
if ((exponent_ < minExponent) || (m < minMantissa))
{
mantissa = zero.mantissa_;
exponent = zero.exponent_;
negative = false;
mantissa_ = zero.mantissa_;
exponent_ = zero.exponent_;
negative = zero.negative_;
return;
}
XRPL_ASSERT_PARTS(m <= maxMantissa, "xrpl::doNormalize", "intermediate mantissa fits in int64");
mantissa = m;
g.doRoundUp(negative, mantissa, exponent, minMantissa, maxMantissa, "Number::normalize 2");
// When using the largeRange, "m" needs fit within an int64, even if
// the final mantissa_ is going to end up larger to fit within the
// MantissaRange. Cut it down here so that the rounding will be done while
// it's smaller.
//
// Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807,
// so "m" will be modified to 990,000,000,000,012,345. Then that value
// will be rounded to 990,000,000,000,012,345 or
// 990,000,000,000,012,346, depending on the rounding mode. Finally,
// mantissa_ will be "m*10" so it fits within the range, and end up as
// 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460.
// mantissa() will return mantissa_ / 10, and exponent() will return
// exponent_ + 1.
if (m > maxRep)
{
if (exponent_ >= maxExponent)
throw std::overflow_error("Number::normalize 1.5");
g.push(m % 10);
m /= 10;
++exponent_;
}
// Before modification, m should be within the min/max range. After
// modification, it must be less than maxRep. In other words, the original
// value should have been no more than maxRep * 10.
// (maxRep * 10 > maxMantissa)
XRPL_ASSERT_PARTS(m <= maxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
mantissa_ = m;
g.doRoundUp(negative, mantissa_, exponent_, minMantissa, maxMantissa, "Number::normalize 2");
XRPL_ASSERT_PARTS(
mantissa >= minMantissa && mantissa <= maxMantissa, "xrpl::doNormalize", "final mantissa fits in range");
XRPL_ASSERT_PARTS(
exponent >= minExponent && exponent <= maxExponent, "xrpl::doNormalize", "final exponent fits in range");
mantissa_ >= minMantissa && mantissa_ <= maxMantissa, "xrpl::doNormalize", "final mantissa fits in range");
}
template <>
@@ -549,20 +467,11 @@ Number::normalize<unsigned long>(
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa);
}
void
Number::normalize(MantissaRange const& range)
{
auto [negative, mantissa, exponent] = toInternal(range);
normalize(negative, mantissa, exponent, range.min, range.max);
fromInternal(negative, mantissa, exponent, &range);
}
void
Number::normalize()
{
normalize(range_);
auto const& range = range_.get();
normalize(negative_, mantissa_, exponent_, range.min, range.max);
}
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
@@ -572,33 +481,21 @@ Number
Number::shiftExponent(int exponentDelta) const
{
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
Number result = *this;
result.exponent_ += exponentDelta;
if (result.exponent_ >= maxExponent)
auto const newExponent = exponent_ + exponentDelta;
if (newExponent >= maxExponent)
throw std::overflow_error("Number::shiftExponent");
if (result.exponent_ < minExponent)
if (newExponent < minExponent)
{
return Number{};
}
Number const result{negative_, mantissa_, newExponent, unchecked{}};
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::Number::shiftExponent", "result is normalized");
return result;
}
Number::Number(bool negative, internalrep mantissa, int exponent, normalized)
{
auto const& range = range_.get();
normalize(negative, mantissa, exponent, range.min, range.max);
fromInternal(negative, mantissa, exponent, &range);
}
Number&
Number::operator+=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (y == zero)
return *this;
@@ -613,7 +510,7 @@ Number::operator+=(Number const& y)
return *this;
}
XRPL_ASSERT(isnormal(range) && y.isnormal(range), "xrpl::Number::operator+=(Number) : is normal");
XRPL_ASSERT(isnormal() && y.isnormal(), "xrpl::Number::operator+=(Number) : is normal");
// *n = negative
// *s = sign
// *m = mantissa
@@ -621,10 +518,13 @@ Number::operator+=(Number const& y)
// Need to use uint128_t, because large mantissas can overflow when added
// together.
auto [xn, xm, xe] = toInternal<uint128_t>(range);
auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
bool xn = negative_;
uint128_t xm = mantissa_;
auto xe = exponent_;
bool yn = y.negative_;
uint128_t ym = y.mantissa_;
auto ye = y.exponent_;
Guard g;
if (xe < ye)
{
@@ -649,13 +549,14 @@ Number::operator+=(Number const& y)
} while (xe > ye);
}
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
if (xn == yn)
{
xm += ym;
if (xm > maxMantissa)
if (xm > maxMantissa || xm > maxRep)
{
g.push(xm % 10);
xm /= 10;
@@ -675,7 +576,7 @@ Number::operator+=(Number const& y)
xe = ye;
xn = yn;
}
while (xm < minMantissa)
while (xm < minMantissa && xm * 10 <= maxRep)
{
xm *= 10;
xm -= g.pop();
@@ -684,8 +585,10 @@ Number::operator+=(Number const& y)
g.doRoundDown(xn, xm, xe, minMantissa);
}
normalize(xn, xm, xe, minMantissa, maxMantissa);
fromInternal(xn, xm, xe, &range);
negative_ = xn;
mantissa_ = static_cast<internalrep>(xm);
exponent_ = xe;
normalize();
return *this;
}
@@ -720,8 +623,6 @@ divu10(uint128_t& u)
Number&
Number::operator*=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (*this == zero)
return *this;
@@ -735,11 +636,15 @@ Number::operator*=(Number const& y)
// *m = mantissa
// *e = exponent
auto [xn, xm, xe] = toInternal(range);
bool xn = negative_;
int xs = xn ? -1 : 1;
internalrep xm = mantissa_;
auto xe = exponent_;
auto [yn, ym, ye] = y.toInternal(range);
bool yn = y.negative_;
int ys = yn ? -1 : 1;
internalrep ym = y.mantissa_;
auto ye = y.exponent_;
auto zm = uint128_t(xm) * uint128_t(ym);
auto ze = xe + ye;
@@ -749,10 +654,11 @@ Number::operator*=(Number const& y)
if (zn)
g.set_negative();
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
while (zm > maxMantissa)
while (zm > maxMantissa || zm > maxRep)
{
// The following is optimization for:
// g.push(static_cast<unsigned>(zm % 10));
@@ -764,17 +670,17 @@ Number::operator*=(Number const& y)
xe = ze;
g.doRoundUp(
zn, xm, xe, minMantissa, maxMantissa, "Number::multiplication overflow : exponent is " + std::to_string(xe));
negative_ = zn;
mantissa_ = xm;
exponent_ = xe;
normalize(zn, xm, xe, minMantissa, maxMantissa);
fromInternal(zn, xm, xe, &range);
normalize();
return *this;
}
Number&
Number::operator/=(Number const& y)
{
auto const& range = range_.get();
constexpr Number zero = Number{};
if (y == zero)
throw std::overflow_error("Number: divide by 0");
@@ -787,12 +693,17 @@ Number::operator/=(Number const& y)
// *m = mantissa
// *e = exponent
auto [np, nm, ne] = toInternal(range);
bool np = negative_;
int ns = (np ? -1 : 1);
auto nm = mantissa_;
auto ne = exponent_;
auto [dp, dm, de] = y.toInternal(range);
bool dp = y.negative_;
int ds = (dp ? -1 : 1);
auto dm = y.mantissa_;
auto de = y.exponent_;
auto const& range = range_.get();
auto const& minMantissa = range.min;
auto const& maxMantissa = range.max;
@@ -804,7 +715,7 @@ Number::operator/=(Number const& y)
// f can be up to 10^(38-19) = 10^19 safely
static_assert(smallRange.log == 15);
static_assert(largeRange.log == 18);
bool small = range.scale == MantissaRange::small;
bool small = Number::getMantissaScale() == MantissaRange::small;
uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL;
XRPL_ASSERT_PARTS(f >= minMantissa * 10, "Number::operator/=", "factor expected size");
@@ -854,8 +765,10 @@ Number::operator/=(Number const& y)
}
}
normalize(zn, zm, ze, minMantissa, maxMantissa);
fromInternal(zn, zm, ze, &range);
XRPL_ASSERT_PARTS(isnormal(range), "xrpl::Number::operator/=", "result is normalized");
negative_ = zn;
mantissa_ = static_cast<internalrep>(zm);
exponent_ = ze;
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::operator/=", "result is normalized");
return *this;
}
@@ -867,10 +780,10 @@ Number::operator rep() const
Guard g;
if (drops != 0)
{
if (drops < 0)
if (negative_)
{
g.set_negative();
drops = externalToInternal(drops);
drops = -drops;
}
for (; offset < 0; ++offset)
{
@@ -879,7 +792,7 @@ Number::operator rep() const
}
for (; offset > 0; --offset)
{
if (drops >= largeRange.min)
if (drops > maxRep / 10)
throw std::overflow_error("Number::operator rep() overflow");
drops *= 10;
}
@@ -909,21 +822,19 @@ Number::truncate() const noexcept
std::string
to_string(Number const& amount)
{
auto const& range = Number::range_.get();
// keep full internal accuracy, but make more human friendly if possible
constexpr Number zero = Number{};
if (amount == zero)
return "0";
// The mantissa must have a set number of decimal places for this to work
auto [negative, mantissa, exponent] = amount.toInternal(range);
auto exponent = amount.exponent_;
auto mantissa = amount.mantissa_;
bool const negative = amount.negative_;
// Use scientific notation for exponents that are too small or too large
auto const rangeLog = range.log;
if (((exponent != 0 && amount.exponent() != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
auto const rangeLog = Number::mantissaLog();
if (((exponent != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
{
// Remove trailing zeroes from the mantissa.
while (mantissa != 0 && mantissa % 10 == 0 && exponent < Number::maxExponent)
{
mantissa /= 10;
@@ -931,11 +842,8 @@ to_string(Number const& amount)
}
std::string ret = negative ? "-" : "";
ret.append(std::to_string(mantissa));
if (exponent != 0)
{
ret.append(1, 'e');
ret.append(std::to_string(exponent));
}
ret.append(1, 'e');
ret.append(std::to_string(exponent));
return ret;
}
@@ -1017,11 +925,20 @@ power(Number const& f, unsigned n)
return r;
}
// Returns f^(1/d)
// Uses NewtonRaphson iterations until the result stops changing
// to find the non-negative root of the polynomial g(x) = x^d - f
// This function, and power(Number f, unsigned n, unsigned d)
// treat corner cases such as 0 roots as advised by Annex F of
// the C standard, which itself is consistent with the IEEE
// floating point standards.
Number
Number::root(MantissaRange const& range, Number f, unsigned d)
root(Number f, unsigned d)
{
constexpr Number zero = Number{};
auto const one = Number::one(range);
auto const one = Number::one();
if (f == one || d == 1)
return f;
@@ -1038,28 +955,21 @@ Number::root(MantissaRange const& range, Number f, unsigned d)
if (f == zero)
return f;
auto const [e, di] = [&]() {
auto const [negative, mantissa, exponent] = f.toInternal(range);
// Scale f into the range (0, 1) such that the scale change (e) is a
// multiple of the root (d)
auto e = exponent + range.log + 1;
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 = f.shiftExponent(-e); // f /= 10^e;
return std::make_tuple(e, di);
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent_ + Number::mantissaLog() + 1;
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 = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(e % di == 0, "xrpl::root(Number, unsigned)", "e is divisible by d");
XRPL_ASSERT_PARTS(f.isnormal(range), "xrpl::root(Number, unsigned)", "f is normalized");
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized");
bool neg = false;
if (f < zero)
{
@@ -1092,32 +1002,15 @@ Number::root(MantissaRange const& range, Number f, unsigned d)
// return r * 10^(e/d) to reverse scaling
auto const result = r.shiftExponent(e / di);
XRPL_ASSERT_PARTS(result.isnormal(range), "xrpl::root(Number, unsigned)", "result is normalized");
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root(Number, unsigned)", "result is normalized");
return result;
}
// Returns f^(1/d)
// Uses NewtonRaphson iterations until the result stops changing
// to find the non-negative root of the polynomial g(x) = x^d - f
// This function, and power(Number f, unsigned n, unsigned d)
// treat corner cases such as 0 roots as advised by Annex F of
// the C standard, which itself is consistent with the IEEE
// floating point standards.
Number
root(Number f, unsigned d)
{
auto const& range = Number::range_.get();
return Number::root(range, f, d);
}
Number
root2(Number f)
{
auto const& range = Number::range_.get();
constexpr Number zero = Number{};
auto const one = Number::one(range);
auto const one = Number::one();
if (f == one)
return f;
@@ -1126,18 +1019,12 @@ root2(Number f)
if (f == zero)
return f;
auto const e = [&]() {
auto const [negative, mantissa, exponent] = f.toInternal(range);
// Scale f into the range (0, 1) such that f's exponent is a
// multiple of d
auto e = exponent + range.log + 1;
if (e % 2 != 0)
++e;
f = f.shiftExponent(-e); // f /= 10^e;
return e;
}();
XRPL_ASSERT_PARTS(f.isnormal(range), "xrpl::root2(Number)", "f is normalized");
// Scale f into the range (0, 1) such that f's exponent is a multiple of d
auto e = f.exponent_ + Number::mantissaLog() + 1;
if (e % 2 != 0)
++e;
f = f.shiftExponent(-e); // f /= 10^e;
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized");
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
auto const D = 105;
@@ -1159,7 +1046,7 @@ root2(Number f)
// return r * 10^(e/2) to reverse scaling
auto const result = r.shiftExponent(e / 2);
XRPL_ASSERT_PARTS(result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root2(Number)", "result is normalized");
return result;
}
@@ -1169,10 +1056,8 @@ root2(Number f)
Number
power(Number const& f, unsigned n, unsigned d)
{
auto const& range = Number::range_.get();
constexpr Number zero = Number{};
auto const one = Number::one(range);
auto const one = Number::one();
if (f == one)
return f;
@@ -1194,7 +1079,7 @@ power(Number const& f, unsigned n, unsigned d)
d /= g;
if ((n % 2) == 1 && (d % 2) == 0 && f < zero)
throw std::overflow_error("Number::power nan");
return Number::root(range, power(f, n), d);
return root(power(f, n), d);
}
} // namespace xrpl

View File

@@ -8,6 +8,7 @@
#include <set>
#include <stack>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -87,14 +88,14 @@ public:
}
void
output(boost::beast::string_view const& bytes)
output(std::string_view const& bytes)
{
markStarted();
output_(bytes);
}
void
stringOutput(boost::beast::string_view const& bytes)
stringOutput(std::string_view const& bytes)
{
markStarted();
std::size_t position = 0, writtenUntil = 0;

View File

@@ -85,8 +85,7 @@ registerSSLCerts(boost::asio::ssl::context& ctx, boost::system::error_code& ec,
// There is a very unpleasant interaction between <wincrypt> and
// openssl x509 types (namely the former has macros that stomp
// on the latter), these undefs allow this TU to be safely used in
// unity builds without messing up subsequent TUs. Although we
// no longer use unity builds, leaving the undefs here does no harm.
// unity builds without messing up subsequent TUs.
#if BOOST_OS_WINDOWS
#undef X509_NAME
#undef X509_EXTENSIONS

View File

@@ -10,11 +10,6 @@ DatabaseNodeImp::store(NodeObjectType type, Blob&& data, uint256 const& hash, st
auto obj = NodeObject::createObject(type, std::move(data), hash);
backend_->store(obj);
if (cache_)
{
// After the store, replace a negative cache entry if there is one
cache_->canonicalize(hash, obj, [](std::shared_ptr<NodeObject> const& n) { return n->getType() == hotDUMMY; });
}
}
void
@@ -23,77 +18,36 @@ DatabaseNodeImp::asyncFetch(
std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback)
{
if (cache_)
{
std::shared_ptr<NodeObject> obj = cache_->fetch(hash);
if (obj)
{
callback(obj->getType() == hotDUMMY ? nullptr : obj);
return;
}
}
Database::asyncFetch(hash, ledgerSeq, std::move(callback));
}
void
DatabaseNodeImp::sweep()
{
if (cache_)
cache_->sweep();
}
std::shared_ptr<NodeObject>
DatabaseNodeImp::fetchNodeObject(uint256 const& hash, std::uint32_t, FetchReport& fetchReport, bool duplicate)
{
std::shared_ptr<NodeObject> nodeObject = cache_ ? cache_->fetch(hash) : nullptr;
std::shared_ptr<NodeObject> nodeObject = nullptr;
Status status;
if (!nodeObject)
try
{
JLOG(j_.trace()) << "fetchNodeObject " << hash << ": record not " << (cache_ ? "cached" : "found");
Status status;
try
{
status = backend_->fetch(hash.data(), &nodeObject);
}
catch (std::exception const& e)
{
JLOG(j_.fatal()) << "fetchNodeObject " << hash << ": Exception fetching from backend: " << e.what();
Rethrow();
}
switch (status)
{
case ok:
if (cache_)
{
if (nodeObject)
cache_->canonicalize_replace_client(hash, nodeObject);
else
{
auto notFound = NodeObject::createObject(hotDUMMY, {}, hash);
cache_->canonicalize_replace_client(hash, notFound);
if (notFound->getType() != hotDUMMY)
nodeObject = notFound;
}
}
break;
case notFound:
break;
case dataCorrupt:
JLOG(j_.fatal()) << "fetchNodeObject " << hash << ": nodestore data is corrupted";
break;
default:
JLOG(j_.warn()) << "fetchNodeObject " << hash << ": backend returns unknown result " << status;
break;
}
status = backend_->fetch(hash.data(), &nodeObject);
}
else
catch (std::exception const& e)
{
JLOG(j_.trace()) << "fetchNodeObject " << hash << ": record found in cache";
if (nodeObject->getType() == hotDUMMY)
nodeObject.reset();
JLOG(j_.fatal()) << "fetchNodeObject " << hash << ": Exception fetching from backend: " << e.what();
Rethrow();
}
switch (status)
{
case ok:
case notFound:
break;
case dataCorrupt:
JLOG(j_.fatal()) << "fetchNodeObject " << hash << ": nodestore data is corrupted";
break;
default:
JLOG(j_.warn()) << "fetchNodeObject " << hash << ": backend returns unknown result " << status;
break;
}
if (nodeObject)
@@ -105,66 +59,36 @@ DatabaseNodeImp::fetchNodeObject(uint256 const& hash, std::uint32_t, FetchReport
std::vector<std::shared_ptr<NodeObject>>
DatabaseNodeImp::fetchBatch(std::vector<uint256> const& hashes)
{
std::vector<std::shared_ptr<NodeObject>> results{hashes.size()};
using namespace std::chrono;
auto const before = steady_clock::now();
std::unordered_map<uint256 const*, size_t> indexMap;
std::vector<uint256 const*> cacheMisses;
uint64_t hits = 0;
uint64_t fetches = 0;
std::vector<uint256 const*> batch{};
batch.reserve(hashes.size());
for (size_t i = 0; i < hashes.size(); ++i)
{
auto const& hash = hashes[i];
// See if the object already exists in the cache
auto nObj = cache_ ? cache_->fetch(hash) : nullptr;
++fetches;
if (!nObj)
{
// Try the database
indexMap[&hash] = i;
cacheMisses.push_back(&hash);
}
else
{
results[i] = nObj->getType() == hotDUMMY ? nullptr : nObj;
// It was in the cache.
++hits;
}
batch.push_back(&hash);
}
JLOG(j_.debug()) << "fetchBatch - cache hits = " << (hashes.size() - cacheMisses.size())
<< " - cache misses = " << cacheMisses.size();
auto dbResults = backend_->fetchBatch(cacheMisses).first;
for (size_t i = 0; i < dbResults.size(); ++i)
// Get the node objects that match the hashes from the backend. To protect
// against the backends returning fewer or more results than expected, the
// container is resized to the number of hashes.
auto results = backend_->fetchBatch(batch).first;
XRPL_ASSERT(
results.size() == hashes.size() || results.empty(),
"number of output objects either matches number of input hashes or is empty");
results.resize(hashes.size());
for (size_t i = 0; i < results.size(); ++i)
{
auto nObj = std::move(dbResults[i]);
size_t index = indexMap[cacheMisses[i]];
auto const& hash = hashes[index];
if (nObj)
{
// Ensure all threads get the same object
if (cache_)
cache_->canonicalize_replace_client(hash, nObj);
}
else
if (!results[i])
{
JLOG(j_.error()) << "fetchBatch - "
<< "record not found in db or cache. hash = " << strHex(hash);
if (cache_)
{
auto notFound = NodeObject::createObject(hotDUMMY, {}, hash);
cache_->canonicalize_replace_client(hash, notFound);
if (notFound->getType() != hotDUMMY)
nObj = std::move(notFound);
}
<< "record not found in db. hash = " << strHex(hashes[i]);
}
results[index] = std::move(nObj);
}
auto fetchDurationUs = std::chrono::duration_cast<std::chrono::microseconds>(steady_clock::now() - before).count();
updateFetchMetrics(fetches, hits, fetchDurationUs);
updateFetchMetrics(hashes.size(), 0, fetchDurationUs);
return results;
}

View File

@@ -93,12 +93,6 @@ DatabaseRotatingImp::store(NodeObjectType type, Blob&& data, uint256 const& hash
storeStats(1, nObj->getData().size());
}
void
DatabaseRotatingImp::sweep()
{
// nothing to do
}
std::shared_ptr<NodeObject>
DatabaseRotatingImp::fetchNodeObject(uint256 const& hash, std::uint32_t, FetchReport& fetchReport, bool duplicate)
{

View File

@@ -490,19 +490,8 @@ public:
Env env(*this, envconfig(onlineDelete));
/////////////////////////////////////////////////////////////
// Create the backend. Normally, SHAMapStoreImp handles all these
// details
auto nscfg = env.app().config().section(ConfigSection::nodeDatabase());
// Provide default values:
if (!nscfg.exists("cache_size"))
nscfg.set(
"cache_size", std::to_string(env.app().config().getValueFor(SizedItem::treeCacheSize, std::nullopt)));
if (!nscfg.exists("cache_age"))
nscfg.set(
"cache_age", std::to_string(env.app().config().getValueFor(SizedItem::treeCacheAge, std::nullopt)));
// Create NodeStore with two backends to allow online deletion of data.
// Normally, SHAMapStoreImp handles all these details.
NodeStoreScheduler scheduler(env.app().getJobQueue());
std::string const writableDb = "write";
@@ -510,9 +499,8 @@ public:
auto writableBackend = makeBackendRotating(env, scheduler, writableDb);
auto archiveBackend = makeBackendRotating(env, scheduler, archiveDb);
// Create NodeStore with two backends to allow online deletion of
// data
constexpr int readThreads = 4;
auto nscfg = env.app().config().section(ConfigSection::nodeDatabase());
auto dbr = std::make_unique<NodeStore::DatabaseRotatingImp>(
scheduler,
readThreads,

View File

@@ -32,10 +32,9 @@ public:
test_limits()
{
auto const scale = Number::getMantissaScale();
auto const minMantissa = Number::minMantissa();
testcase << "test_limits " << to_string(scale) << ", " << minMantissa;
testcase << "test_limits " << to_string(scale);
bool caught = false;
auto const minMantissa = Number::minMantissa();
try
{
Number x = Number{false, minMantissa * 10, 32768, Number::normalized{}};
@@ -59,9 +58,8 @@ public:
__LINE__);
test(Number{false, minMantissa, -32769, Number::normalized{}}, Number{}, __LINE__);
test(
// Use 1501 to force rounding up
Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 +
Number{false, 1'501, 32000, Number::normalized{}},
Number{false, 1'500, 32000, Number::normalized{}},
Number{false, minMantissa + 2, 32003, Number::normalized{}},
__LINE__);
// 9,223,372,036,854,775,808
@@ -161,8 +159,8 @@ public:
{Number{true, 9'999'999'999'999'999'999ULL, -37, Number::normalized{}},
Number{1'000'000'000'000'000'000, -18},
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::normalized{}}},
{Number{Number::largestMantissa}, Number{6, -1}, Number{Number::largestMantissa / 10, 1}},
{Number{Number::largestMantissa - 1}, Number{1, 0}, Number{Number::largestMantissa}},
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep / 10, 1}},
{Number{Number::maxRep - 1}, Number{1, 0}, Number{Number::maxRep}},
// Test extremes
{
// Each Number operand rounds up, so the actual mantissa is
@@ -172,18 +170,11 @@ public:
Number{2, 19},
},
{
// Does not round. Mantissas are going to be >
// largestMantissa, so if added together as uint64_t's, the
// result will overflow. With addition using uint128_t,
// there's no problem. After normalizing, the resulting
// mantissa ends up less than largestMantissa.
Number{false, Number::largestMantissa, 0, Number::normalized{}},
Number{false, Number::largestMantissa, 0, Number::normalized{}},
Number{false, Number::largestMantissa * 2, 0, Number::normalized{}},
},
{
// These mantissas round down, so adding them together won't
// have any consequences.
// Does not round. Mantissas are going to be > maxRep, so if
// added together as uint64_t's, the result will overflow.
// With addition using uint128_t, there's no problem. After
// normalizing, the resulting mantissa ends up less than
// maxRep.
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::normalized{}},
Number{false, 9'999'999'999'999'999'990ULL, 0, Number::normalized{}},
Number{false, 1'999'999'999'999'999'998ULL, 1, Number::normalized{}},
@@ -270,14 +261,12 @@ public:
{Number{1'000'000'000'000'000'001, -18},
Number{1'000'000'000'000'000'000, -18},
Number{1'000'000'000'000'000'000, -36}},
{Number{Number::largestMantissa}, Number{6, -1}, Number{Number::largestMantissa - 1}},
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
{Number{Number::maxRep}, Number{6, -1}, Number{Number::maxRep - 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
Number{1, 0},
Number{Number::largestMantissa / 10 + 1, 1}},
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
Number{3, 0},
Number{Number::largestMantissa}},
{power(2, 63), Number{3, 0}, Number{Number::largestMantissa}},
Number{Number::maxRep / 10 + 1, 1}},
{Number{false, Number::maxRep + 1, 0, Number::normalized{}}, Number{3, 0}, Number{Number::maxRep}},
{power(2, 63), Number{3, 0}, Number{Number::maxRep}},
});
auto test = [this](auto const& c) {
for (auto const& [x, y, z] : c)
@@ -300,15 +289,14 @@ public:
auto const scale = Number::getMantissaScale();
testcase << "test_mul " << to_string(scale);
// Case: Factor 1, Factor 2, Expected product, Line number
using Case = std::tuple<Number, Number, Number, int>;
using Case = std::tuple<Number, Number, Number>;
auto test = [this](auto const& c) {
for (auto const& [x, y, z, line] : c)
for (auto const& [x, y, z] : c)
{
auto const result = x * y;
std::stringstream ss;
ss << x << " * " << y << " = " << result << ". Expected: " << z;
BEAST_EXPECTS(result == z, ss.str() + " line: " + std::to_string(line));
BEAST_EXPECTS(result == z, ss.str());
}
};
auto tests = [&](auto const& cSmall, auto const& cLarge) {
@@ -318,83 +306,48 @@ public:
test(cLarge);
};
auto const maxMantissa = Number::maxMantissa();
auto const maxInternalMantissa =
static_cast<std::uint64_t>(static_cast<std::int64_t>(power(10, Number::mantissaLog()))) * 10 - 1;
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
{
auto const cSmall = std::to_array<Case>({
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{2000000000000000, -15}, __LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{1000000000000000, -14}, __LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}, __LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{2000000000000000, -15}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-2000000000000000, -15}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{2000000000000000, -15}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}},
// Maximum mantissa range
{Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'999, 0},
Number{9'999'999'999'999'998, 16},
__LINE__},
{Number{9'999'999'999'999'999, 0}, Number{9'999'999'999'999'999, 0}, Number{9'999'999'999'999'998, 16}},
});
auto const cLarge = std::to_array<Case>({
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999862, -18}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999862, -18}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999862, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}, __LINE__},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18},
__LINE__},
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2000000000000000001, -18}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18},
__LINE__},
Number{-1999999999999999998, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}, __LINE__},
// Maximum internal mantissa range - rounds up to 1e19
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{1, 38},
__LINE__},
// Maximum actual mantissa range - same as int64 range
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds up to 1e19
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
Number{1, 38}},
// Maximum int64 range
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
}
@@ -402,78 +355,44 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}, __LINE__}});
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999, -15}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999, -15}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999, -15}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999861, -18}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999861, -18}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999861, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9999999999999999579ULL, -18, Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}, __LINE__},
Number{false, 9999999999999999579ULL, -18, Number::normalized{}}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}, __LINE__},
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18},
__LINE__},
Number{-1999999999999999997, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}, __LINE__},
// Maximum internal mantissa range - rounds down to
// maxMantissa/10e1
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}},
__LINE__},
// Maximum actual mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
}
@@ -481,78 +400,44 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " downward";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{9999999999999999, -15},
__LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}, __LINE__}});
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999, -15}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-2000000000000000, -15}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999, -15}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{9999999999999999, -15}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999861, -18},
__LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999861, -18}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999862, -18}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999861, -18}},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
__LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}, __LINE__},
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}, __LINE__},
{Number{1414213562373095049, -18}, Number{1414213562373095049, -18}, Number{2, 0}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999998, -18},
__LINE__},
Number{-1999999999999999998, -18}},
{Number{-1414213562373095048, -18},
Number{-1414213562373095049, -18},
Number{1999999999999999999, -18},
__LINE__},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}, __LINE__},
// Maximum internal mantissa range - rounds down to
// maxMantissa/10-1
Number{1999999999999999999, -18}},
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
// Maximum mantissa range - rounds down to maxMantissa/10e1
// 99'999'999'999'999'999'800'000'000'000'000'000'100
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}},
__LINE__},
// Maximum mantissa range - same as int64
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'84, 19},
__LINE__},
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'84, 19}},
});
tests(cSmall, cLarge);
}
@@ -560,80 +445,44 @@ public:
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " upward";
{
auto const cSmall = std::to_array<Case>(
{{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999, -15},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{2000000000000000, -15},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{1000000000000000, -14},
__LINE__},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}, __LINE__}});
{{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{2000000000000000, -15}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999, -15}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{2000000000000000, -15}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{1000000000000000, -14}},
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
auto const cLarge = std::to_array<Case>(
// Note that items with extremely large mantissas need to be
// calculated, because otherwise they overflow uint64. Items
// from C with larger mantissa
{
{Number{7}, Number{8}, Number{56}, __LINE__},
{Number{1414213562373095, -15},
Number{1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{1414213562373095, -15},
Number{-1999999999999999861, -18},
__LINE__},
{Number{-1414213562373095, -15},
Number{-1414213562373095, -15},
Number{1999999999999999862, -18},
__LINE__},
{Number{3214285714285706, -15},
Number{3111111111111119, -15},
Number{999999999999999958, -17},
__LINE__},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}, __LINE__},
{Number{7}, Number{8}, Number{56}},
{Number{1414213562373095, -15}, Number{1414213562373095, -15}, Number{1999999999999999862, -18}},
{Number{-1414213562373095, -15}, Number{1414213562373095, -15}, Number{-1999999999999999861, -18}},
{Number{-1414213562373095, -15}, Number{-1414213562373095, -15}, Number{1999999999999999862, -18}},
{Number{3214285714285706, -15}, Number{3111111111111119, -15}, Number{999999999999999958, -17}},
{Number{1000000000000000000, -32768}, Number{1000000000000000000, -32768}, Number{0}},
// Items from cSmall expanded for the larger mantissa,
// except duplicates. Sadly, it looks like sqrt(2)^2 != 2
// with higher precision
{Number{1414213562373095049, -18},
Number{1414213562373095049, -18},
Number{2000000000000000001, -18},
__LINE__},
Number{2000000000000000001, -18}},
{Number{-1414213562373095048, -18},
Number{1414213562373095048, -18},
Number{-1999999999999999997, -18},
__LINE__},
{Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, Number{2, 0}, __LINE__},
Number{-1999999999999999997, -18}},
{Number{-1414213562373095048, -18}, Number{-1414213562373095049, -18}, Number{2, 0}},
{Number{3214285714285714278, -18},
Number{3111111111111111119, -18},
Number{1000000000000000001, -17},
__LINE__},
// Maximum internal mantissa range - rounds up to
// minMantissa*10 1e19*1e19=1e38
{Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{false, maxInternalMantissa, 0, Number::normalized{}},
Number{1, 38},
__LINE__},
// Maximum mantissa range - same as int64
Number{1000000000000000001, -17}},
// Maximum mantissa range - rounds up to minMantissa*10
// 1e19*1e19=1e38
{Number{false, maxMantissa, 0, Number::normalized{}},
Number{false, maxMantissa, 0, Number::normalized{}},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
Number{1, 38}},
// Maximum int64 range
// 85'070'591'730'234'615'847'396'907'784'232'501'249
{Number{Number::largestMantissa, 0},
Number{Number::largestMantissa, 0},
Number{85'070'591'730'234'615'85, 19},
__LINE__},
{Number{Number::maxRep, 0}, Number{Number::maxRep, 0}, Number{85'070'591'730'234'615'85, 19}},
});
tests(cSmall, cLarge);
}
@@ -848,9 +697,6 @@ public:
};
*/
auto const maxInternalMantissa =
static_cast<std::uint64_t>(static_cast<std::int64_t>(power(10, Number::mantissaLog()))) * 10 - 1;
auto const cSmall = std::to_array<Case>(
{{Number{2}, 2, Number{1414213562373095049, -18}},
{Number{2'000'000}, 2, Number{1414213562373095049, -15}},
@@ -862,14 +708,14 @@ public:
{Number{0}, 5, Number{0}},
{Number{5625, -4}, 2, Number{75, -2}}});
auto const cLarge = std::to_array<Case>({
{Number{false, maxInternalMantissa - 9, -1, Number::normalized{}},
{Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}},
2,
Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}},
{Number{false, maxInternalMantissa - 9, 0, Number::normalized{}},
{Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}},
2,
Number{false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
{Number{Number::largestMantissa}, 2, Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
{Number{Number::largestMantissa}, 4, Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
{Number{Number::maxRep}, 2, Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
{Number{Number::maxRep}, 4, Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
});
test(cSmall);
if (Number::getMantissaScale() != MantissaRange::small)
@@ -916,8 +762,6 @@ public:
}
};
auto const maxInternalMantissa = power(10, Number::mantissaLog()) * 10 - 1;
auto const cSmall = std::to_array<Number>({
Number{2},
Number{2'000'000},
@@ -927,10 +771,7 @@ public:
Number{5, -1},
Number{0},
Number{5625, -4},
Number{Number::largestMantissa},
maxInternalMantissa,
Number{Number::minMantissa(), 0, Number::unchecked{}},
Number{Number::maxMantissa(), 0, Number::unchecked{}},
Number{Number::maxRep},
});
test(cSmall);
bool caught = false;
@@ -1272,16 +1113,16 @@ public:
case MantissaRange::large:
// Test the edges
// ((exponent < -(28)) || (exponent > -(8)))))
test(Number::min(), "922337203685477581e-32768");
test(Number::min(), "1e-32750");
test(Number::max(), "9223372036854775807e32768");
test(Number::lowest(), "-9223372036854775807e32768");
{
NumberRoundModeGuard mg(Number::towards_zero);
auto const maxMantissa = Number::maxMantissa();
BEAST_EXPECT(maxMantissa == 9'223'372'036'854'775'807ULL);
test(Number{false, maxMantissa, 0, Number::normalized{}}, "9223372036854775807");
test(Number{true, maxMantissa, 0, Number::normalized{}}, "-9223372036854775807");
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
test(Number{false, maxMantissa, 0, Number::normalized{}}, "9999999999999999990");
test(Number{true, maxMantissa, 0, Number::normalized{}}, "-9999999999999999990");
test(Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
test(-(Number{std::numeric_limits<std::int64_t>::max(), 0}), "-9223372036854775807");
@@ -1459,7 +1300,7 @@ public:
Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() > 0);
Number const maxInt64{Number::largestMantissa};
Number const maxInt64{Number::maxRep};
BEAST_EXPECT(maxInt64.exponent() > 0);
// 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'62, 22}));
@@ -1476,198 +1317,20 @@ public:
Number const initalXrp{INITIAL_XRP};
BEAST_EXPECT(initalXrp.exponent() <= 0);
Number const maxInt64{Number::largestMantissa};
Number const maxInt64{Number::maxRep};
BEAST_EXPECT(maxInt64.exponent() <= 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits
BEAST_EXPECT((power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19}));
NumberRoundModeGuard mg(Number::towards_zero);
{
auto const maxInternalMantissa =
static_cast<std::uint64_t>(static_cast<std::int64_t>(power(10, Number::mantissaLog()))) * 10 - 1;
// Rounds down to fit under 2^63
Number const max = Number{false, maxInternalMantissa, 0, Number::normalized{}};
// No alterations by the accessors
BEAST_EXPECT(max.mantissa() == maxInternalMantissa / 10);
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
BEAST_EXPECT((power(max, 2) == Number{false, maxInternalMantissa / 10 - 1, 20, Number::normalized{}}));
}
{
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{false, maxMantissa, 0, Number::normalized{}};
// No alterations by the accessors
BEAST_EXPECT(max.mantissa() == maxMantissa);
BEAST_EXPECT(max.exponent() == 0);
// 85'070'591'730'234'615'847'396'907'784'232'501'249 - also 38
// digits
BEAST_EXPECT((power(max, 2) == Number{false, 85'070'591'730'234'615'84, 19, Number::normalized{}}));
}
}
}
void
testNormalizeToRange()
{
// Test edge-cases of normalizeToRange
auto const scale = Number::getMantissaScale();
testcase << "normalizeToRange " << to_string(scale);
auto test = [this](
Number const& n,
auto const rangeMin,
auto const rangeMax,
auto const expectedMantissa,
auto const expectedExponent,
auto const line) {
auto const normalized = n.normalizeToRange(rangeMin, rangeMax);
BEAST_EXPECTS(
normalized.first == expectedMantissa,
"Number " + to_string(n) + " scaled to " + std::to_string(rangeMax) +
". Expected mantissa:" + std::to_string(expectedMantissa) +
", got: " + std::to_string(normalized.first) + " @ " + std::to_string(line));
BEAST_EXPECTS(
normalized.second == expectedExponent,
"Number " + to_string(n) + " scaled to " + std::to_string(rangeMax) +
". Expected exponent:" + std::to_string(expectedExponent) +
", got: " + std::to_string(normalized.second) + " @ " + std::to_string(line));
};
std::int64_t constexpr iRangeMin = 100;
std::int64_t constexpr iRangeMax = 999;
std::uint64_t constexpr uRangeMin = 100;
std::uint64_t constexpr uRangeMax = 999;
constexpr static MantissaRange largeRange{MantissaRange::large};
std::int64_t constexpr iBigMin = largeRange.min;
std::int64_t constexpr iBigMax = largeRange.max;
auto const testSuite = [&](Number const& n,
auto const expectedSmallMantissa,
auto const expectedSmallExponent,
auto const expectedLargeMantissa,
auto const expectedLargeExponent,
auto const line) {
test(n, iRangeMin, iRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
test(n, iBigMin, iBigMax, expectedLargeMantissa, expectedLargeExponent, line);
// Only test non-negative. testing a negative number with an
// unsigned range will assert, and asserts can't be tested.
if (n.signum() >= 0)
{
test(n, uRangeMin, uRangeMax, expectedSmallMantissa, expectedSmallExponent, line);
test(n, largeRange.min, largeRange.max, expectedLargeMantissa, expectedLargeExponent, line);
}
};
{
// zero
Number const n{0};
testSuite(n, 0, std::numeric_limits<int>::lowest(), 0, std::numeric_limits<int>::lowest(), __LINE__);
}
{
// Small positive number
Number const n{2};
testSuite(n, 200, -2, 2'000'000'000'000'000'000, -18, __LINE__);
}
{
// Negative number
Number const n{-2};
testSuite(n, -200, -2, -2'000'000'000'000'000'000, -18, __LINE__);
}
{
// Biggest valid mantissa
Number const n{Number::largestMantissa, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, Number::largestMantissa, 0, __LINE__);
}
{
// Biggest valid mantissa + 1
Number const n{Number::largestMantissa + 1, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// Biggest valid mantissa + 2
Number const n{Number::largestMantissa + 2, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// Biggest valid mantissa + 3
Number const n{Number::largestMantissa + 3, 0, Number::normalized{}};
if (scale == MantissaRange::small)
// With the small mantissa range, the value rounds up. Because
// it rounds up, when scaling up to the full int64 range, it
// can't go over the max, so it is one digit smaller than the
// full value.
testSuite(n, 922, 16, 922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, 922, 16, 922'337'203'685'477'581, 1, __LINE__);
}
{
// int64 min
Number const n{std::numeric_limits<std::int64_t>::min(), 0};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
}
{
// int64 min + 1
Number const n{std::numeric_limits<std::int64_t>::min() + 1, 0};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -9'223'372'036'854'775'807, 0, __LINE__);
}
{
// int64 min - 1
// Need to cast to uint, even though we're dealing with a negative
// number to avoid overflow and UB
Number const n{
true,
-static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::min()) + 1,
0,
Number::normalized{}};
if (scale == MantissaRange::small)
testSuite(n, -922, 16, -922'337'203'685'477'600, 1, __LINE__);
else
testSuite(n, -922, 16, -922'337'203'685'477'581, 1, __LINE__);
auto const maxMantissa = Number::maxMantissa();
Number const max = Number{false, maxMantissa, 0, Number::normalized{}};
BEAST_EXPECT(max.mantissa() == maxMantissa / 10);
BEAST_EXPECT(max.exponent() == 1);
// 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38
// digits
BEAST_EXPECT((power(max, 2) == Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}}));
}
}
@@ -1698,7 +1361,6 @@ public:
test_truncate();
testRounding();
testInt64();
testNormalizeToRange();
}
}
};

View File

@@ -908,10 +908,6 @@ public:
JLOG(m_journal.debug()) << "MasterTransaction sweep. Size before: " << oldMasterTxSize
<< "; size after: " << masterTxCache.size();
}
{
// Does not appear to have an associated cache.
getNodeStore().sweep();
}
{
std::size_t const oldLedgerMasterCacheSize = getLedgerMaster().getFetchPackCacheSize();

View File

@@ -130,14 +130,6 @@ std::unique_ptr<NodeStore::Database>
SHAMapStoreImp::makeNodeStore(int readThreads)
{
auto nscfg = app_.config().section(ConfigSection::nodeDatabase());
// Provide default values:
if (!nscfg.exists("cache_size"))
nscfg.set("cache_size", std::to_string(app_.config().getValueFor(SizedItem::treeCacheSize, std::nullopt)));
if (!nscfg.exists("cache_age"))
nscfg.set("cache_age", std::to_string(app_.config().getValueFor(SizedItem::treeCacheAge, std::nullopt)));
std::unique_ptr<NodeStore::Database> db;
if (deleteInterval_)
@@ -226,8 +218,6 @@ SHAMapStoreImp::run()
LedgerIndex lastRotated = state_db_.getState().lastRotated;
netOPs_ = &app_.getOPs();
ledgerMaster_ = &app_.getLedgerMaster();
fullBelowCache_ = &(*app_.getNodeFamily().getFullBelowCache());
treeNodeCache_ = &(*app_.getNodeFamily().getTreeNodeCache());
if (advisoryDelete_)
canDelete_ = state_db_.getCanDelete();
@@ -490,16 +480,19 @@ void
SHAMapStoreImp::clearCaches(LedgerIndex validatedSeq)
{
ledgerMaster_->clearLedgerCachePrior(validatedSeq);
fullBelowCache_->clear();
// Also clear the FullBelowCache so its generation counter is bumped.
// This prevents stale "full below" markers from persisting across
// backend rotation/online deletion and interfering with SHAMap sync.
app_.getNodeFamily().getFullBelowCache()->clear();
}
void
SHAMapStoreImp::freshenCaches()
{
if (freshenCache(*treeNodeCache_))
return;
if (freshenCache(app_.getMasterTransaction().getCache()))
if (freshenCache(*app_.getNodeFamily().getTreeNodeCache()))
return;
freshenCache(app_.getMasterTransaction().getCache());
}
void

View File

@@ -93,8 +93,6 @@ private:
// as of run() or before
NetworkOPs* netOPs_ = nullptr;
LedgerMaster* ledgerMaster_ = nullptr;
FullBelowCache* fullBelowCache_ = nullptr;
TreeNodeCache* treeNodeCache_ = nullptr;
static constexpr auto nodeStoreName_ = "NodeStore";

View File

@@ -58,7 +58,7 @@ to_string(ProtocolVersion const& p)
}
std::vector<ProtocolVersion>
parseProtocolVersions(boost::beast::string_view const& value)
parseProtocolVersions(std::string_view const& value)
{
static boost::regex re(
"^" // start of line
@@ -127,7 +127,7 @@ negotiateProtocolVersion(std::vector<ProtocolVersion> const& versions)
}
std::optional<ProtocolVersion>
negotiateProtocolVersion(boost::beast::string_view const& versions)
negotiateProtocolVersion(std::string_view const& versions)
{
auto const them = parseProtocolVersions(versions);

View File

@@ -1,10 +1,9 @@
#pragma once
#include <boost/beast/core/string.hpp>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -39,7 +38,7 @@ to_string(ProtocolVersion const& p);
no duplicates and will be sorted in ascending protocol order.
*/
std::vector<ProtocolVersion>
parseProtocolVersions(boost::beast::string_view const& s);
parseProtocolVersions(std::string_view const& s);
/** Given a list of supported protocol versions, choose the one we prefer. */
std::optional<ProtocolVersion>
@@ -47,7 +46,7 @@ negotiateProtocolVersion(std::vector<ProtocolVersion> const& versions);
/** Given a list of supported protocol versions, choose the one we prefer. */
std::optional<ProtocolVersion>
negotiateProtocolVersion(boost::beast::string_view const& versions);
negotiateProtocolVersion(std::string_view const& versions);
/** The list of all the protocol versions we support. */
std::string const&

View File

@@ -34,6 +34,7 @@
#include <algorithm>
#include <memory>
#include <stdexcept>
#include <string_view>
namespace xrpl {
@@ -227,7 +228,7 @@ ServerHandler::onHandoff(
static inline Json::Output
makeOutput(Session& session)
{
return [&](boost::beast::string_view const& b) { session.write(b.data(), b.size()); };
return [&](std::string_view const& b) { session.write(b.data(), b.size()); };
}
static std::map<std::string, std::string>
@@ -511,11 +512,14 @@ ServerHandler::processSession(std::shared_ptr<Session> const& session, std::shar
makeOutput(*session),
coro,
forwardedFor(session->request()),
[&] {
[&]() -> std::string_view {
auto const iter = session->request().find("X-User");
if (iter != session->request().end())
return iter->value();
return boost::beast::string_view{};
{
auto const val = iter->value();
return std::string_view(val.data(), val.size());
}
return std::string_view{};
}());
if (beast::rfc2616::is_keep_alive(session->request()))