mirror of
https://github.com/XRPLF/rippled.git
synced 2026-02-25 00:02:32 +00:00
Compare commits
36 Commits
pratik/Add
...
ximinez/nu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e27249134a | ||
|
|
bdd106d992 | ||
|
|
d79fdec886 | ||
|
|
24cbaf76a5 | ||
|
|
3a805cc646 | ||
|
|
0fd237d707 | ||
|
|
024d05b70c | ||
|
|
ffb3e1da53 | ||
|
|
aef7e5b335 | ||
|
|
e2c09e79d0 | ||
|
|
c6f854bbd8 | ||
|
|
6a1e0b0f5a | ||
|
|
01f5ae0927 | ||
|
|
9b4587f9af | ||
|
|
fbc6f87983 | ||
|
|
0871eb0cb6 | ||
|
|
2ccf132f79 | ||
|
|
6600153958 | ||
|
|
fff73dac51 | ||
|
|
06ff77458a | ||
|
|
f19ecb3b80 | ||
|
|
cc2406bf3f | ||
|
|
30c65320e4 | ||
|
|
569d9ea94e | ||
|
|
02b7bcfa2b | ||
|
|
07c0c320a7 | ||
|
|
d57e37c34b | ||
|
|
154bb65c35 | ||
|
|
111eda22e9 | ||
|
|
f7b6834d2a | ||
|
|
e464adaee6 | ||
|
|
cca92dedca | ||
|
|
3d6f57a4df | ||
|
|
fc29fbe946 | ||
|
|
5e0a8d5c8a | ||
|
|
d27788f12a |
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,4 +14,4 @@ jobs:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@320be44621ca2a080f05aeb15817c44b84518108
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-ab4d1f0" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -42,6 +42,9 @@ gmon.out
|
||||
# Locally patched Conan recipes
|
||||
external/conan-center-index/
|
||||
|
||||
# Local conan directory
|
||||
.conan
|
||||
|
||||
# XCode IDE.
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
@@ -72,5 +75,8 @@ DerivedData
|
||||
/.claude
|
||||
/CLAUDE.md
|
||||
|
||||
# Direnv's directory
|
||||
/.direnv
|
||||
|
||||
# clangd cache
|
||||
/.cache
|
||||
|
||||
@@ -57,6 +57,16 @@ repos:
|
||||
- .git/COMMIT_EDITMSG
|
||||
stages: [commit-msg]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: nix-fmt
|
||||
name: Format Nix files
|
||||
entry: nix --extra-experimental-features 'nix-command flakes' fmt
|
||||
language: system
|
||||
types:
|
||||
- nix
|
||||
pass_filenames: true
|
||||
|
||||
exclude: |
|
||||
(?x)^(
|
||||
external/.*|
|
||||
|
||||
@@ -173,6 +173,9 @@ words:
|
||||
- nftokens
|
||||
- nftpage
|
||||
- nikb
|
||||
- nixfmt
|
||||
- nixos
|
||||
- nixpkgs
|
||||
- nonxrp
|
||||
- noripple
|
||||
- nudb
|
||||
|
||||
2
docs/build/environment.md
vendored
2
docs/build/environment.md
vendored
@@ -3,6 +3,8 @@ environment complete with Git, Python, Conan, CMake, and a C++ compiler.
|
||||
This document exists to help readers set one up on any of the Big Three
|
||||
platforms: Linux, macOS, or Windows.
|
||||
|
||||
As an alternative to system packages, the Nix development shell can be used to provide a development environment. See [using nix development shell](./nix.md) for more details.
|
||||
|
||||
[BUILD.md]: ../../BUILD.md
|
||||
|
||||
## Linux
|
||||
|
||||
95
docs/build/nix.md
vendored
Normal file
95
docs/build/nix.md
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
# Using Nix Development Shell for xrpld Development
|
||||
|
||||
This guide explains how to use Nix to set up a reproducible development environment for xrpld. Using Nix eliminates the need to manually install utilities and ensures consistent tooling across different machines.
|
||||
|
||||
## Benefits of Using Nix
|
||||
|
||||
- **Reproducible environment**: Everyone gets the same versions of tools and compilers
|
||||
- **No system pollution**: Dependencies are isolated and don't affect your system packages
|
||||
- **Multiple compiler versions**: Easily switch between different GCC and Clang versions
|
||||
- **Quick setup**: Get started with a single command
|
||||
- **Works on Linux and macOS**: Consistent experience across platforms
|
||||
|
||||
## Install Nix
|
||||
|
||||
Please follow [the official installation instructions of nix package manager](https://nixos.org/download/) for your system.
|
||||
|
||||
## Entering the Development Shell
|
||||
|
||||
### Basic Usage
|
||||
|
||||
From the root of the xrpld repository, enter the default development shell:
|
||||
|
||||
```bash
|
||||
nix --experimental-features 'nix-command flakes' develop
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
|
||||
- Configure the appropriate compiler for your platform:
|
||||
- **macOS**: Apple Clang (default system compiler)
|
||||
- **Linux**: GCC 15
|
||||
|
||||
The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster.
|
||||
|
||||
> [!TIP]
|
||||
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
|
||||
>
|
||||
> ```bash
|
||||
> mkdir -p ~/.config/nix
|
||||
> echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
|
||||
> ```
|
||||
>
|
||||
> After this, you can simply use `nix develop` instead.
|
||||
|
||||
> [!NOTE]
|
||||
> The examples below assume you've enabled flakes in your config. If you haven't, add `--experimental-features 'nix-command flakes'` after each `nix` command.
|
||||
|
||||
### Choosing a different compiler
|
||||
|
||||
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`.
|
||||
Use `nix flake show` to see all the available development shells.
|
||||
|
||||
Use `nix develop .#no_compiler` to use the compiler from your system.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```bash
|
||||
# Use GCC 14
|
||||
nix develop .#gcc14
|
||||
|
||||
# Use Clang 19
|
||||
nix develop .#clang19
|
||||
|
||||
# Use default for your platform
|
||||
nix develop
|
||||
```
|
||||
|
||||
### Using a different shell
|
||||
|
||||
`nix develop` opens bash by default. If you want to use another shell this could be done by adding `-c` flag. For example:
|
||||
|
||||
```bash
|
||||
nix develop -c zsh
|
||||
```
|
||||
|
||||
## Building xrpld with Nix
|
||||
|
||||
Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.).
|
||||
|
||||
## Automatic Activation with direnv
|
||||
|
||||
[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory.
|
||||
|
||||
## Conan and Prebuilt Packages
|
||||
|
||||
Please note that there is no guarantee that binaries from conan cache will work when using nix. If you encounter any errors, please use `--build '*'` to force conan to compile everything from source:
|
||||
|
||||
```bash
|
||||
conan install .. --output-folder . --build '*' --settings build_type=Release
|
||||
```
|
||||
|
||||
## Updating `flake.lock` file
|
||||
|
||||
To update `flake.lock` to the latest revision use `nix flake update` command.
|
||||
26
flake.lock
generated
Normal file
26
flake.lock
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769461804,
|
||||
"narHash": "sha256-6h5sROT/3CTHvzPy9koKBmoCa2eJKh4fzQK8eYFEgl8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b579d443b37c9c5373044201ea77604e37e748c8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
16
flake.nix
Normal file
16
flake.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
description = "Nix related things for xrpld";
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ nixpkgs, ... }:
|
||||
let
|
||||
forEachSystem = (import ./nix/utils.nix { inherit nixpkgs; }).forEachSystem;
|
||||
in
|
||||
{
|
||||
devShells = forEachSystem (import ./nix/devshell.nix);
|
||||
formatter = forEachSystem ({ pkgs, ... }: pkgs.nixfmt);
|
||||
};
|
||||
}
|
||||
73
include/xrpl/basics/MallocTrim.h
Normal file
73
include/xrpl/basics/MallocTrim.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// cSpell:ignore ptmalloc
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Allocator interaction note:
|
||||
// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that
|
||||
// ptmalloc return free heap pages to the OS.
|
||||
// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or
|
||||
// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect
|
||||
// on the *active* heap. The call is harmless but may not reclaim memory
|
||||
// because those allocators manage their own arenas.
|
||||
// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed
|
||||
// allocations are usually returned to the OS on free regardless of trimming.
|
||||
// - Call at known reclamation points (e.g., after cache sweeps / online delete)
|
||||
// and consider rate limiting to avoid churn.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
struct MallocTrimReport
|
||||
{
|
||||
bool supported{false};
|
||||
int trimResult{-1};
|
||||
std::int64_t rssBeforeKB{-1};
|
||||
std::int64_t rssAfterKB{-1};
|
||||
std::chrono::microseconds durationUs{-1};
|
||||
std::int64_t minfltDelta{-1};
|
||||
std::int64_t majfltDelta{-1};
|
||||
|
||||
[[nodiscard]] std::int64_t
|
||||
deltaKB() const noexcept
|
||||
{
|
||||
if (rssBeforeKB < 0 || rssAfterKB < 0)
|
||||
return 0;
|
||||
return rssAfterKB - rssBeforeKB;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Attempt to return freed memory to the operating system.
|
||||
*
|
||||
* On Linux with glibc malloc, this issues ::malloc_trim(0), which may release
|
||||
* free space from ptmalloc arenas back to the kernel. On other platforms, or if
|
||||
* a different allocator is in use, this function is a no-op and the report will
|
||||
* indicate that trimming is unsupported or had no effect.
|
||||
*
|
||||
* @param tag Identifier for logging/debugging purposes.
|
||||
* @param journal Journal for diagnostic logging.
|
||||
* @return Report containing before/after metrics and the trim result.
|
||||
*
|
||||
* @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded,
|
||||
* calling glibc's malloc_trim may have no effect on the active heap. The
|
||||
* call is harmless but typically does not reclaim memory under those
|
||||
* allocators.
|
||||
*
|
||||
* @note Only memory served from glibc's sbrk/arena heaps is eligible for trim.
|
||||
* Large allocations satisfied via mmap are usually returned on free
|
||||
* independently of trimming.
|
||||
*
|
||||
* @note Intended for use after operations that free significant memory (e.g.,
|
||||
* cache sweeps, ledger cleanup, online delete). Consider rate limiting.
|
||||
*/
|
||||
MallocTrimReport
|
||||
mallocTrim(std::string_view tag, beast::Journal journal);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -7,8 +7,13 @@
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
#endif // !defined(_MSC_VER)
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class Number;
|
||||
@@ -16,18 +21,39 @@ 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
|
||||
* base-10 logarithm (roughly floor(log10(value))), and rem is value with
|
||||
* all factors of 10 removed (i.e., divided by the largest power of 10 that
|
||||
* divides value). 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)
|
||||
{
|
||||
int log = 0;
|
||||
while (value >= 10 && value % 10 == 0)
|
||||
{
|
||||
value /= 10;
|
||||
++log;
|
||||
}
|
||||
if (value == 1)
|
||||
return log;
|
||||
auto const est = logTenEstimate(value);
|
||||
if (est.second == 1)
|
||||
return est.first;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -41,12 +67,10 @@ 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
|
||||
* instantiated to two: one for each scale.
|
||||
* The mantissa_scale enum indicates whether the range is "small" or
|
||||
* "large". This intentionally prevents the creation of any
|
||||
* MantissaRanges representing other values.
|
||||
*
|
||||
* 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
|
||||
@@ -60,8 +84,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 10^18, and a max
|
||||
* value of 10^19-1.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
@@ -73,25 +97,50 @@ struct MantissaRange
|
||||
enum mantissa_scale { small, large };
|
||||
|
||||
explicit constexpr MantissaRange(mantissa_scale scale_)
|
||||
: min(getMin(scale_)), max(min * 10 - 1), log(logTen(min).value_or(-1)), scale(scale_)
|
||||
: max(getMax(scale_))
|
||||
, min(computeMin(max))
|
||||
, referenceMin(getReferenceMin(scale_, min))
|
||||
, log(computeLog(min))
|
||||
, 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");
|
||||
}
|
||||
|
||||
rep 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;
|
||||
int log;
|
||||
mantissa_scale scale;
|
||||
|
||||
private:
|
||||
static constexpr rep
|
||||
getMin(mantissa_scale scale_)
|
||||
getMax(mantissa_scale scale)
|
||||
{
|
||||
switch (scale_)
|
||||
switch (scale)
|
||||
{
|
||||
case small:
|
||||
return 1'000'000'000'000'000ULL;
|
||||
return 9'999'999'999'999'999ULL;
|
||||
case large:
|
||||
return 1'000'000'000'000'000'000ULL;
|
||||
return std::numeric_limits<std::int64_t>::max();
|
||||
default:
|
||||
// Since this can never be called outside a non-constexpr
|
||||
// context, this throw assures that the build fails if an
|
||||
@@ -99,19 +148,59 @@ 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 Representation ----
|
||||
* ---- Internal Operational Representation ----
|
||||
*
|
||||
* Internally, Number is represented with three values:
|
||||
* 1. a bool sign flag,
|
||||
@@ -126,15 +215,21 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
|
||||
*
|
||||
* 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.min, MantissaRange.max].
|
||||
* [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.
|
||||
*
|
||||
* 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. 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.
|
||||
* 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.
|
||||
*
|
||||
* ---- External Interface ----
|
||||
*
|
||||
@@ -147,13 +242,12 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
|
||||
* represent the full range of valid XRP and MPT integer values accurately.
|
||||
*
|
||||
* Note:
|
||||
* 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large"
|
||||
* mantissa range.
|
||||
* 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.
|
||||
* 2. The functions mantissa() and exponent() return the external view of the
|
||||
* 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.
|
||||
* Number value, specifically using a signed 63-bit mantissa.
|
||||
* 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
|
||||
@@ -208,8 +302,7 @@ class Number
|
||||
using rep = std::int64_t;
|
||||
using internalrep = MantissaRange::rep;
|
||||
|
||||
bool negative_{false};
|
||||
internalrep mantissa_{0};
|
||||
rep mantissa_{0};
|
||||
int exponent_{std::numeric_limits<int>::lowest()};
|
||||
|
||||
public:
|
||||
@@ -217,10 +310,6 @@ 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
|
||||
{
|
||||
@@ -298,8 +387,7 @@ public:
|
||||
friend constexpr bool
|
||||
operator==(Number const& x, Number const& y) noexcept
|
||||
{
|
||||
return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ &&
|
||||
x.exponent_ == y.exponent_;
|
||||
return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_;
|
||||
}
|
||||
|
||||
friend constexpr bool
|
||||
@@ -313,8 +401,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.negative_;
|
||||
bool const rneg = y.negative_;
|
||||
bool const lneg = x.mantissa_ < 0;
|
||||
bool const rneg = y.mantissa_ < 0;
|
||||
|
||||
if (lneg != rneg)
|
||||
return lneg;
|
||||
@@ -342,7 +430,7 @@ public:
|
||||
constexpr int
|
||||
signum() const noexcept
|
||||
{
|
||||
return negative_ ? -1 : (mantissa_ ? 1 : 0);
|
||||
return mantissa_ < 0 ? -1 : (mantissa_ ? 1 : 0);
|
||||
}
|
||||
|
||||
Number
|
||||
@@ -381,6 +469,9 @@ 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
|
||||
@@ -445,22 +536,39 @@ 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 == 1'000'000'000'000'000'000ULL);
|
||||
static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL));
|
||||
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(largeRange.log == 18);
|
||||
static_assert(largeRange.min < maxRep);
|
||||
static_assert(largeRange.max > maxRep);
|
||||
// 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.
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -483,11 +591,14 @@ 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;
|
||||
|
||||
@@ -497,14 +608,60 @@ private:
|
||||
Number
|
||||
shiftExponent(int exponentDelta) const;
|
||||
|
||||
// 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.
|
||||
// Safely return the absolute value of a rep (int64) mantissa as an internalrep (uint64).
|
||||
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(
|
||||
@@ -512,7 +669,8 @@ inline constexpr Number::Number(
|
||||
internalrep mantissa,
|
||||
int exponent,
|
||||
unchecked) noexcept
|
||||
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
|
||||
: mantissa_{negative ? -static_cast<rep>(mantissa) : static_cast<rep>(mantissa)}
|
||||
, exponent_{exponent}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -523,12 +681,6 @@ 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{})
|
||||
{
|
||||
@@ -551,17 +703,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
|
||||
inline constexpr Number::rep
|
||||
Number::mantissa() const noexcept
|
||||
{
|
||||
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);
|
||||
return mantissa_;
|
||||
}
|
||||
|
||||
/** Returns the exponent of the external view of the Number.
|
||||
@@ -572,16 +714,7 @@ Number::mantissa() const noexcept
|
||||
inline constexpr int
|
||||
Number::exponent() const noexcept
|
||||
{
|
||||
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;
|
||||
return exponent_;
|
||||
}
|
||||
|
||||
inline constexpr Number
|
||||
@@ -596,7 +729,7 @@ Number::operator-() const noexcept
|
||||
if (mantissa_ == 0)
|
||||
return Number{};
|
||||
auto x = *this;
|
||||
x.negative_ = !x.negative_;
|
||||
x.mantissa_ = -x.mantissa_;
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -677,42 +810,58 @@ Number::min() noexcept
|
||||
inline Number
|
||||
Number::max() noexcept
|
||||
{
|
||||
return Number{false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
|
||||
return Number{false, range_.get().max, maxExponent, unchecked{}};
|
||||
}
|
||||
|
||||
inline Number
|
||||
Number::lowest() noexcept
|
||||
{
|
||||
return Number{true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}};
|
||||
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);
|
||||
}
|
||||
|
||||
inline bool
|
||||
Number::isnormal() const noexcept
|
||||
{
|
||||
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);
|
||||
return isnormal(range_);
|
||||
}
|
||||
|
||||
template <Integral64 T>
|
||||
std::pair<T, int>
|
||||
Number::normalizeToRange(T minMantissa, T maxMantissa) const
|
||||
{
|
||||
bool negative = negative_;
|
||||
internalrep mantissa = mantissa_;
|
||||
bool negative = mantissa_ < 0;
|
||||
internalrep mantissa = externalToInternal(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);
|
||||
|
||||
auto const sign = negative ? -1 : 1;
|
||||
return std::make_pair(static_cast<T>(sign * mantissa), exponent);
|
||||
// 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);
|
||||
}
|
||||
|
||||
inline constexpr Number
|
||||
|
||||
@@ -232,7 +232,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
|
||||
|
||||
/** The maximum amount of MPTokenIssuance */
|
||||
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
|
||||
static_assert(Number::maxRep >= maxMPTokenAmount);
|
||||
static_assert(Number::largestMantissa >= maxMPTokenAmount);
|
||||
|
||||
/** The maximum length of Data payload */
|
||||
std::size_t constexpr maxDataPayloadLength = 256;
|
||||
|
||||
@@ -539,6 +539,8 @@ 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};
|
||||
|
||||
@@ -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::maxRep >= INITIAL_XRP.drops());
|
||||
static_assert(Number::largestMantissa >= INITIAL_XRP.drops());
|
||||
|
||||
/** Returns true if the amount does not exceed the initial XRP in existence. */
|
||||
inline bool
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -31,7 +32,7 @@ XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo
|
||||
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// Check flags in Credential transactions
|
||||
|
||||
140
nix/devshell.nix
Normal file
140
nix/devshell.nix
Normal file
@@ -0,0 +1,140 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
commonPackages = with pkgs; [
|
||||
ccache
|
||||
cmake
|
||||
conan
|
||||
gcovr
|
||||
git
|
||||
gnumake
|
||||
llvmPackages_21.clang-tools
|
||||
ninja
|
||||
perl # needed for openssl
|
||||
pkg-config
|
||||
pre-commit
|
||||
python314
|
||||
];
|
||||
|
||||
# Supported compiler versions
|
||||
gccVersion = pkgs.lib.range 13 15;
|
||||
clangVersions = pkgs.lib.range 18 21;
|
||||
|
||||
defaultCompiler = if pkgs.stdenv.isDarwin then "apple-clang" else "gcc";
|
||||
defaultGccVersion = pkgs.lib.last gccVersion;
|
||||
defaultClangVersion = pkgs.lib.last clangVersions;
|
||||
|
||||
strToCompilerEnv =
|
||||
compiler: version:
|
||||
(
|
||||
if compiler == "gcc" then
|
||||
let
|
||||
gccPkg = pkgs."gcc${toString version}Stdenv" or null;
|
||||
in
|
||||
if gccPkg != null && builtins.elem version gccVersion then
|
||||
gccPkg
|
||||
else
|
||||
throw "Invalid GCC version: ${toString version}. Must be one of: ${toString gccVersion}"
|
||||
else if compiler == "clang" then
|
||||
let
|
||||
clangPkg = pkgs."llvmPackages_${toString version}".stdenv or null;
|
||||
in
|
||||
if clangPkg != null && builtins.elem version clangVersions then
|
||||
clangPkg
|
||||
else
|
||||
throw "Invalid Clang version: ${toString version}. Must be one of: ${toString clangVersions}"
|
||||
else if compiler == "apple-clang" || compiler == "none" then
|
||||
pkgs.stdenvNoCC
|
||||
else
|
||||
throw "Invalid compiler: ${compiler}. Must be one of: gcc, clang, apple-clang, none"
|
||||
);
|
||||
|
||||
# Helper function to create a shell with a specific compiler
|
||||
makeShell =
|
||||
{
|
||||
compiler ? defaultCompiler,
|
||||
version ? (
|
||||
if compiler == "gcc" then
|
||||
defaultGccVersion
|
||||
else if compiler == "clang" then
|
||||
defaultClangVersion
|
||||
else
|
||||
null
|
||||
),
|
||||
}:
|
||||
let
|
||||
compilerStdEnv = strToCompilerEnv compiler version;
|
||||
|
||||
compilerName =
|
||||
if compiler == "apple-clang" then
|
||||
"clang"
|
||||
else if compiler == "none" then
|
||||
null
|
||||
else
|
||||
compiler;
|
||||
|
||||
gccOnMacWarning =
|
||||
if pkgs.stdenv.isDarwin && compiler == "gcc" then
|
||||
''
|
||||
echo "WARNING: Using GCC on macOS with Conan may not work."
|
||||
echo " Consider using 'nix develop .#clang' or the default shell instead."
|
||||
echo ""
|
||||
''
|
||||
else
|
||||
"";
|
||||
|
||||
compilerVersion =
|
||||
if compilerName != null then
|
||||
''
|
||||
echo "Compiler: "
|
||||
${compilerName} --version
|
||||
''
|
||||
else
|
||||
''
|
||||
echo "No compiler specified - using system compiler"
|
||||
'';
|
||||
|
||||
shellAttrs = {
|
||||
packages = commonPackages;
|
||||
|
||||
shellHook = ''
|
||||
echo "Welcome to xrpld development shell";
|
||||
${gccOnMacWarning}${compilerVersion}
|
||||
'';
|
||||
};
|
||||
in
|
||||
pkgs.mkShell.override { stdenv = compilerStdEnv; } shellAttrs;
|
||||
|
||||
# Generate shells for each compiler version
|
||||
gccShells = builtins.listToAttrs (
|
||||
map (version: {
|
||||
name = "gcc${toString version}";
|
||||
value = makeShell {
|
||||
compiler = "gcc";
|
||||
version = version;
|
||||
};
|
||||
}) gccVersion
|
||||
);
|
||||
|
||||
clangShells = builtins.listToAttrs (
|
||||
map (version: {
|
||||
name = "clang${toString version}";
|
||||
value = makeShell {
|
||||
compiler = "clang";
|
||||
version = version;
|
||||
};
|
||||
}) clangVersions
|
||||
);
|
||||
|
||||
in
|
||||
gccShells
|
||||
// clangShells
|
||||
// {
|
||||
# Default shells
|
||||
default = makeShell { };
|
||||
gcc = makeShell { compiler = "gcc"; };
|
||||
clang = makeShell { compiler = "clang"; };
|
||||
|
||||
# No compiler
|
||||
no-compiler = makeShell { compiler = "none"; };
|
||||
apple-clang = makeShell { compiler = "apple-clang"; };
|
||||
}
|
||||
19
nix/utils.nix
Normal file
19
nix/utils.nix
Normal file
@@ -0,0 +1,19 @@
|
||||
{ nixpkgs }:
|
||||
{
|
||||
forEachSystem =
|
||||
function:
|
||||
nixpkgs.lib.genAttrs
|
||||
[
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
]
|
||||
(
|
||||
system:
|
||||
function {
|
||||
inherit system;
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
}
|
||||
);
|
||||
}
|
||||
157
src/libxrpl/basics/MallocTrim.cpp
Normal file
157
src/libxrpl/basics/MallocTrim.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/MallocTrim.h>
|
||||
|
||||
#include <boost/predef.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <malloc.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Require RUSAGE_THREAD for thread-scoped page fault tracking
|
||||
#ifndef RUSAGE_THREAD
|
||||
#error "MallocTrim rusage instrumentation requires RUSAGE_THREAD on Linux/glibc"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
getRusageThread(struct rusage& ru)
|
||||
{
|
||||
return ::getrusage(RUSAGE_THREAD, &ru) == 0; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// cSpell:ignore statm
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
|
||||
inline int
|
||||
mallocTrimWithPad(std::size_t padBytes)
|
||||
{
|
||||
return ::malloc_trim(padBytes);
|
||||
}
|
||||
|
||||
long
|
||||
parseStatmRSSkB(std::string const& statm)
|
||||
{
|
||||
// /proc/self/statm format: size resident shared text lib data dt
|
||||
// We want the second field (resident) which is in pages
|
||||
std::istringstream iss(statm);
|
||||
long size, resident;
|
||||
if (!(iss >> size >> resident))
|
||||
return -1;
|
||||
|
||||
// Convert pages to KB
|
||||
long const pageSize = ::sysconf(_SC_PAGESIZE);
|
||||
if (pageSize <= 0)
|
||||
return -1;
|
||||
|
||||
return (resident * pageSize) / 1024;
|
||||
}
|
||||
|
||||
#endif // __GLIBC__ && BOOST_OS_LINUX
|
||||
|
||||
} // namespace detail
|
||||
|
||||
MallocTrimReport
|
||||
mallocTrim(std::string_view tag, beast::Journal journal)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
|
||||
MallocTrimReport report;
|
||||
|
||||
#if !(defined(__GLIBC__) && BOOST_OS_LINUX)
|
||||
JLOG(journal.debug()) << "malloc_trim not supported on this platform (tag=" << tag << ")";
|
||||
#else
|
||||
// Keep glibc malloc_trim padding at 0 (default): 12h Mainnet tests across 0/256KB/1MB/16MB
|
||||
// showed no clear, consistent benefit from custom padding—0 provided the best overall balance
|
||||
// of RSS reduction and trim-latency stability without adding a tuning surface.
|
||||
constexpr std::size_t TRIM_PAD = 0;
|
||||
|
||||
report.supported = true;
|
||||
|
||||
if (journal.debug())
|
||||
{
|
||||
auto readFile = [](std::string const& path) -> std::string {
|
||||
std::ifstream ifs(path, std::ios::in | std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
return {};
|
||||
|
||||
// /proc files are often not seekable; read as a stream.
|
||||
std::ostringstream oss;
|
||||
oss << ifs.rdbuf();
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
std::string const tagStr{tag};
|
||||
std::string const statmPath = "/proc/self/statm";
|
||||
|
||||
auto const statmBefore = readFile(statmPath);
|
||||
long const rssBeforeKB = detail::parseStatmRSSkB(statmBefore);
|
||||
|
||||
struct rusage ru0{};
|
||||
bool const have_ru0 = getRusageThread(ru0);
|
||||
|
||||
auto const t0 = std::chrono::steady_clock::now();
|
||||
|
||||
report.trimResult = detail::mallocTrimWithPad(TRIM_PAD);
|
||||
|
||||
auto const t1 = std::chrono::steady_clock::now();
|
||||
|
||||
struct rusage ru1{};
|
||||
bool const have_ru1 = getRusageThread(ru1);
|
||||
|
||||
auto const statmAfter = readFile(statmPath);
|
||||
long const rssAfterKB = detail::parseStatmRSSkB(statmAfter);
|
||||
|
||||
// Populate report fields
|
||||
report.rssBeforeKB = rssBeforeKB;
|
||||
report.rssAfterKB = rssAfterKB;
|
||||
report.durationUs = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0);
|
||||
|
||||
if (have_ru0 && have_ru1)
|
||||
{
|
||||
report.minfltDelta = ru1.ru_minflt - ru0.ru_minflt;
|
||||
report.majfltDelta = ru1.ru_majflt - ru0.ru_majflt;
|
||||
}
|
||||
|
||||
std::int64_t const deltaKB = (rssBeforeKB < 0 || rssAfterKB < 0)
|
||||
? 0
|
||||
: (static_cast<std::int64_t>(rssAfterKB) - static_cast<std::int64_t>(rssBeforeKB));
|
||||
|
||||
JLOG(journal.debug()) << "malloc_trim tag=" << tagStr << " result=" << report.trimResult
|
||||
<< " pad=" << TRIM_PAD << " bytes"
|
||||
<< " rss_before=" << rssBeforeKB << "kB"
|
||||
<< " rss_after=" << rssAfterKB << "kB"
|
||||
<< " delta=" << deltaKB << "kB"
|
||||
<< " duration_us=" << report.durationUs.count()
|
||||
<< " minflt_delta=" << report.minfltDelta
|
||||
<< " majflt_delta=" << report.majfltDelta;
|
||||
}
|
||||
else
|
||||
{
|
||||
report.trimResult = detail::mallocTrimWithPad(TRIM_PAD);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return report;
|
||||
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -9,20 +9,17 @@
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#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")
|
||||
#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)
|
||||
#endif
|
||||
|
||||
using uint128_t = xrpl::detail::uint128_t;
|
||||
using int128_t = xrpl::detail::int128_t;
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -61,9 +58,6 @@ 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
|
||||
@@ -99,7 +93,7 @@ public:
|
||||
round() noexcept;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
template <detail::UnsignedMantissa T>
|
||||
void
|
||||
doRoundUp(
|
||||
bool& negative,
|
||||
@@ -107,22 +101,22 @@ public:
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
std::string location);
|
||||
std::string_view location);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
template <detail::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 location);
|
||||
doRound(rep& drops, std::string_view location);
|
||||
|
||||
private:
|
||||
void
|
||||
doPush(unsigned d) noexcept;
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
template <detail::UnsignedMantissa T>
|
||||
void
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
};
|
||||
@@ -209,7 +203,7 @@ Number::Guard::round() noexcept
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
template <detail::UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::bringIntoRange(
|
||||
bool& negative,
|
||||
@@ -228,13 +222,13 @@ Number::Guard::bringIntoRange(
|
||||
{
|
||||
constexpr Number zero = Number{};
|
||||
|
||||
negative = zero.negative_;
|
||||
negative = false;
|
||||
mantissa = zero.mantissa_;
|
||||
exponent = zero.exponent_;
|
||||
}
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
template <detail::UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundUp(
|
||||
bool& negative,
|
||||
@@ -242,7 +236,7 @@ Number::Guard::doRoundUp(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
std::string location)
|
||||
std::string_view location)
|
||||
{
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||
@@ -250,7 +244,7 @@ Number::Guard::doRoundUp(
|
||||
++mantissa;
|
||||
// Ensure mantissa after incrementing fits within both the
|
||||
// min/maxMantissa range and is a valid "rep".
|
||||
if (mantissa > maxMantissa || mantissa > maxRep)
|
||||
if (mantissa > maxMantissa)
|
||||
{
|
||||
mantissa /= 10;
|
||||
++exponent;
|
||||
@@ -258,10 +252,10 @@ Number::Guard::doRoundUp(
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||
if (exponent > maxExponent)
|
||||
throw std::overflow_error(location);
|
||||
throw std::overflow_error(std::string{location});
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
template <detail::UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundDown(
|
||||
bool& negative,
|
||||
@@ -284,21 +278,22 @@ Number::Guard::doRoundDown(
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
Number::Guard::doRound(rep& drops, std::string location)
|
||||
Number::Guard::doRound(rep& drops, std::string_view location)
|
||||
{
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (drops & 1) == 1))
|
||||
{
|
||||
if (drops >= maxRep)
|
||||
auto const& range = range_.get();
|
||||
if (drops >= range.max)
|
||||
{
|
||||
static_assert(sizeof(internalrep) == sizeof(rep));
|
||||
// This should be impossible, because it's impossible to represent
|
||||
// "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
|
||||
// "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
|
||||
// converting to rep, though the latter might overflow _before_
|
||||
// rounding.
|
||||
throw std::overflow_error(location); // LCOV_EXCL_LINE
|
||||
throw std::overflow_error(std::string{location}); // LCOV_EXCL_LINE
|
||||
}
|
||||
++drops;
|
||||
}
|
||||
@@ -318,23 +313,133 @@ 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;
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
constexpr Number
|
||||
Number::oneSmall()
|
||||
{
|
||||
return Number{false, Number::smallRange.min, -Number::smallRange.log, Number::unchecked{}};
|
||||
return Number{
|
||||
false, Number::smallRange.referenceMin, -Number::smallRange.log, Number::unchecked{}};
|
||||
};
|
||||
|
||||
constexpr Number oneSml = Number::oneSmall();
|
||||
@@ -342,103 +447,89 @@ constexpr Number oneSml = Number::oneSmall();
|
||||
constexpr Number
|
||||
Number::oneLarge()
|
||||
{
|
||||
return Number{false, Number::largeRange.min, -Number::largeRange.log, Number::unchecked{}};
|
||||
return Number{
|
||||
false, Number::largeRange.referenceMin, -Number::largeRange.log, Number::unchecked{}};
|
||||
};
|
||||
|
||||
constexpr Number oneLrg = Number::oneLarge();
|
||||
|
||||
Number
|
||||
Number::one()
|
||||
Number::one(MantissaRange const& range)
|
||||
{
|
||||
if (&range_.get() == &smallRange)
|
||||
if (&range == &smallRange)
|
||||
return oneSml;
|
||||
XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_");
|
||||
XRPL_ASSERT(&range == &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)
|
||||
if (mantissa == 0 || (mantissa < minMantissa && exponent <= minExponent))
|
||||
{
|
||||
mantissa_ = zero.mantissa_;
|
||||
exponent_ = zero.exponent_;
|
||||
negative = zero.negative_;
|
||||
mantissa = zero.mantissa_;
|
||||
exponent = zero.exponent_;
|
||||
negative = false;
|
||||
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 < minMantissa))
|
||||
if ((exponent < minExponent) || (m == 0))
|
||||
{
|
||||
mantissa_ = zero.mantissa_;
|
||||
exponent_ = zero.exponent_;
|
||||
negative = zero.negative_;
|
||||
mantissa = zero.mantissa_;
|
||||
exponent = zero.exponent_;
|
||||
negative = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
XRPL_ASSERT_PARTS(m <= maxMantissa, "xrpl::doNormalize", "intermediate mantissa fits in int64");
|
||||
mantissa = m;
|
||||
|
||||
g.doRoundUp(negative, mantissa, exponent, minMantissa, maxMantissa, "Number::normalize 2");
|
||||
|
||||
g.doRoundUp(negative, mantissa_, exponent_, minMantissa, maxMantissa, "Number::normalize 2");
|
||||
XRPL_ASSERT_PARTS(
|
||||
mantissa_ >= minMantissa && mantissa_ <= maxMantissa,
|
||||
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");
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -477,11 +568,20 @@ 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()
|
||||
{
|
||||
auto const& range = range_.get();
|
||||
normalize(negative_, mantissa_, exponent_, range.min, range.max);
|
||||
normalize(range_);
|
||||
}
|
||||
|
||||
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
||||
@@ -491,21 +591,33 @@ Number
|
||||
Number::shiftExponent(int exponentDelta) const
|
||||
{
|
||||
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized");
|
||||
auto const newExponent = exponent_ + exponentDelta;
|
||||
if (newExponent >= maxExponent)
|
||||
|
||||
Number result = *this;
|
||||
|
||||
result.exponent_ += exponentDelta;
|
||||
|
||||
if (result.exponent_ >= maxExponent)
|
||||
throw std::overflow_error("Number::shiftExponent");
|
||||
if (newExponent < minExponent)
|
||||
if (result.exponent_ < 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;
|
||||
@@ -520,7 +632,8 @@ Number::operator+=(Number const& y)
|
||||
return *this;
|
||||
}
|
||||
|
||||
XRPL_ASSERT(isnormal() && y.isnormal(), "xrpl::Number::operator+=(Number) : is normal");
|
||||
XRPL_ASSERT(
|
||||
isnormal(range) && y.isnormal(range), "xrpl::Number::operator+=(Number) : is normal");
|
||||
// *n = negative
|
||||
// *s = sign
|
||||
// *m = mantissa
|
||||
@@ -528,13 +641,10 @@ Number::operator+=(Number const& y)
|
||||
|
||||
// Need to use uint128_t, because large mantissas can overflow when added
|
||||
// together.
|
||||
bool xn = negative_;
|
||||
uint128_t xm = mantissa_;
|
||||
auto xe = exponent_;
|
||||
auto [xn, xm, xe] = toInternal<uint128_t>(range);
|
||||
|
||||
auto [yn, ym, ye] = y.toInternal<uint128_t>(range);
|
||||
|
||||
bool yn = y.negative_;
|
||||
uint128_t ym = y.mantissa_;
|
||||
auto ye = y.exponent_;
|
||||
Guard g;
|
||||
if (xe < ye)
|
||||
{
|
||||
@@ -559,14 +669,13 @@ 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 || xm > maxRep)
|
||||
if (xm > maxMantissa)
|
||||
{
|
||||
g.push(xm % 10);
|
||||
xm /= 10;
|
||||
@@ -586,7 +695,7 @@ Number::operator+=(Number const& y)
|
||||
xe = ye;
|
||||
xn = yn;
|
||||
}
|
||||
while (xm < minMantissa && xm * 10 <= maxRep)
|
||||
while (xm < minMantissa)
|
||||
{
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
@@ -595,10 +704,8 @@ Number::operator+=(Number const& y)
|
||||
g.doRoundDown(xn, xm, xe, minMantissa);
|
||||
}
|
||||
|
||||
negative_ = xn;
|
||||
mantissa_ = static_cast<internalrep>(xm);
|
||||
exponent_ = xe;
|
||||
normalize();
|
||||
normalize(xn, xm, xe, minMantissa, maxMantissa);
|
||||
fromInternal(xn, xm, xe, &range);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -633,6 +740,8 @@ divu10(uint128_t& u)
|
||||
Number&
|
||||
Number::operator*=(Number const& y)
|
||||
{
|
||||
auto const& range = range_.get();
|
||||
|
||||
constexpr Number zero = Number{};
|
||||
if (*this == zero)
|
||||
return *this;
|
||||
@@ -646,15 +755,11 @@ Number::operator*=(Number const& y)
|
||||
// *m = mantissa
|
||||
// *e = exponent
|
||||
|
||||
bool xn = negative_;
|
||||
auto [xn, xm, xe] = toInternal(range);
|
||||
int xs = xn ? -1 : 1;
|
||||
internalrep xm = mantissa_;
|
||||
auto xe = exponent_;
|
||||
|
||||
bool yn = y.negative_;
|
||||
auto [yn, ym, ye] = y.toInternal(range);
|
||||
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;
|
||||
@@ -664,11 +769,10 @@ 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 || zm > maxRep)
|
||||
while (zm > maxMantissa)
|
||||
{
|
||||
// The following is optimization for:
|
||||
// g.push(static_cast<unsigned>(zm % 10));
|
||||
@@ -685,17 +789,17 @@ Number::operator*=(Number const& y)
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
"Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||||
negative_ = zn;
|
||||
mantissa_ = xm;
|
||||
exponent_ = xe;
|
||||
|
||||
normalize();
|
||||
normalize(zn, xm, xe, minMantissa, maxMantissa);
|
||||
fromInternal(zn, xm, xe, &range);
|
||||
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");
|
||||
@@ -708,17 +812,12 @@ Number::operator/=(Number const& y)
|
||||
// *m = mantissa
|
||||
// *e = exponent
|
||||
|
||||
bool np = negative_;
|
||||
auto [np, nm, ne] = toInternal(range);
|
||||
int ns = (np ? -1 : 1);
|
||||
auto nm = mantissa_;
|
||||
auto ne = exponent_;
|
||||
|
||||
bool dp = y.negative_;
|
||||
auto [dp, dm, de] = y.toInternal(range);
|
||||
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;
|
||||
|
||||
@@ -730,7 +829,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 = Number::getMantissaScale() == MantissaRange::small;
|
||||
bool small = range.scale == 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");
|
||||
|
||||
@@ -780,10 +879,8 @@ Number::operator/=(Number const& y)
|
||||
}
|
||||
}
|
||||
normalize(zn, zm, ze, minMantissa, maxMantissa);
|
||||
negative_ = zn;
|
||||
mantissa_ = static_cast<internalrep>(zm);
|
||||
exponent_ = ze;
|
||||
XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::operator/=", "result is normalized");
|
||||
fromInternal(zn, zm, ze, &range);
|
||||
XRPL_ASSERT_PARTS(isnormal(range), "xrpl::Number::operator/=", "result is normalized");
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -796,10 +893,10 @@ operator rep() const
|
||||
Guard g;
|
||||
if (drops != 0)
|
||||
{
|
||||
if (negative_)
|
||||
if (drops < 0)
|
||||
{
|
||||
g.set_negative();
|
||||
drops = -drops;
|
||||
drops = externalToInternal(drops);
|
||||
}
|
||||
for (; offset < 0; ++offset)
|
||||
{
|
||||
@@ -808,7 +905,7 @@ operator rep() const
|
||||
}
|
||||
for (; offset > 0; --offset)
|
||||
{
|
||||
if (drops > maxRep / 10)
|
||||
if (drops >= largeRange.min)
|
||||
throw std::overflow_error("Number::operator rep() overflow");
|
||||
drops *= 10;
|
||||
}
|
||||
@@ -838,19 +935,22 @@ 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";
|
||||
|
||||
auto exponent = amount.exponent_;
|
||||
auto mantissa = amount.mantissa_;
|
||||
bool const negative = amount.negative_;
|
||||
// The mantissa must have a set number of decimal places for this to work
|
||||
auto [negative, mantissa, exponent] = amount.toInternal(range);
|
||||
|
||||
// Use scientific notation for exponents that are too small or too large
|
||||
auto const rangeLog = Number::mantissaLog();
|
||||
if (((exponent != 0) && ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10)))))
|
||||
auto const rangeLog = range.log;
|
||||
if (((exponent != 0 && amount.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;
|
||||
@@ -858,8 +958,11 @@ to_string(Number const& amount)
|
||||
}
|
||||
std::string ret = negative ? "-" : "";
|
||||
ret.append(std::to_string(mantissa));
|
||||
ret.append(1, 'e');
|
||||
ret.append(std::to_string(exponent));
|
||||
if (exponent != 0)
|
||||
{
|
||||
ret.append(1, 'e');
|
||||
ret.append(std::to_string(exponent));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -943,20 +1046,11 @@ power(Number const& f, unsigned n)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Returns f^(1/d)
|
||||
// Uses Newton–Raphson iterations until the result stops changing
|
||||
// to find the non-negative root of the polynomial g(x) = x^d - f
|
||||
|
||||
// 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)
|
||||
Number::root(MantissaRange const& range, Number f, unsigned d)
|
||||
{
|
||||
constexpr Number zero = Number{};
|
||||
auto const one = Number::one();
|
||||
auto const one = Number::one(range);
|
||||
|
||||
if (f == one || d == 1)
|
||||
return f;
|
||||
@@ -973,21 +1067,28 @@ root(Number f, unsigned d)
|
||||
if (f == zero)
|
||||
return f;
|
||||
|
||||
// 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;
|
||||
auto const [e, di] = [&]() {
|
||||
auto const [negative, mantissa, exponent] = f.toInternal(range);
|
||||
|
||||
XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized");
|
||||
// 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);
|
||||
}();
|
||||
|
||||
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");
|
||||
bool neg = false;
|
||||
if (f < zero)
|
||||
{
|
||||
@@ -1020,15 +1121,33 @@ root(Number f, unsigned d)
|
||||
|
||||
// return r * 10^(e/d) to reverse scaling
|
||||
auto const result = r.shiftExponent(e / di);
|
||||
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root(Number, unsigned)", "result is normalized");
|
||||
XRPL_ASSERT_PARTS(
|
||||
result.isnormal(range), "xrpl::root(Number, unsigned)", "result is normalized");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns f^(1/d)
|
||||
// Uses Newton–Raphson iterations until the result stops changing
|
||||
// to find the non-negative root of the polynomial g(x) = x^d - f
|
||||
|
||||
// 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();
|
||||
auto const one = Number::one(range);
|
||||
|
||||
if (f == one)
|
||||
return f;
|
||||
@@ -1037,12 +1156,18 @@ root2(Number f)
|
||||
if (f == zero)
|
||||
return f;
|
||||
|
||||
// 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");
|
||||
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");
|
||||
|
||||
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
|
||||
auto const D = 105;
|
||||
@@ -1064,7 +1189,7 @@ root2(Number f)
|
||||
|
||||
// return r * 10^(e/2) to reverse scaling
|
||||
auto const result = r.shiftExponent(e / 2);
|
||||
XRPL_ASSERT_PARTS(result.isnormal(), "xrpl::root2(Number)", "result is normalized");
|
||||
XRPL_ASSERT_PARTS(result.isnormal(range), "xrpl::root2(Number)", "result is normalized");
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1074,8 +1199,10 @@ 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();
|
||||
auto const one = Number::one(range);
|
||||
|
||||
if (f == one)
|
||||
return f;
|
||||
@@ -1097,7 +1224,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 root(power(f, n), d);
|
||||
return Number::root(range, power(f, n), d);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -32,9 +32,10 @@ public:
|
||||
test_limits()
|
||||
{
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "test_limits " << to_string(scale);
|
||||
bool caught = false;
|
||||
auto const minMantissa = Number::minMantissa();
|
||||
|
||||
testcase << "test_limits " << to_string(scale) << ", " << minMantissa;
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
Number x = Number{false, minMantissa * 10, 32768, Number::normalized{}};
|
||||
@@ -58,8 +59,9 @@ 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'500, 32000, Number::normalized{}},
|
||||
Number{false, 1'501, 32000, Number::normalized{}},
|
||||
Number{false, minMantissa + 2, 32003, Number::normalized{}},
|
||||
__LINE__);
|
||||
// 9,223,372,036,854,775,808
|
||||
@@ -168,8 +170,12 @@ 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::maxRep}, Number{6, -1}, Number{Number::maxRep / 10, 1}},
|
||||
{Number{Number::maxRep - 1}, Number{1, 0}, Number{Number::maxRep}},
|
||||
{Number{Number::largestMantissa},
|
||||
Number{6, -1},
|
||||
Number{Number::largestMantissa / 10, 1}},
|
||||
{Number{Number::largestMantissa - 1},
|
||||
Number{1, 0},
|
||||
Number{Number::largestMantissa}},
|
||||
// Test extremes
|
||||
{
|
||||
// Each Number operand rounds up, so the actual mantissa is
|
||||
@@ -179,11 +185,18 @@ public:
|
||||
Number{2, 19},
|
||||
},
|
||||
{
|
||||
// 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.
|
||||
// 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.
|
||||
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{}},
|
||||
@@ -272,14 +285,16 @@ 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::maxRep}, Number{6, -1}, Number{Number::maxRep - 1}},
|
||||
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
|
||||
{Number{Number::largestMantissa},
|
||||
Number{6, -1},
|
||||
Number{Number::largestMantissa - 1}},
|
||||
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
|
||||
Number{1, 0},
|
||||
Number{Number::maxRep / 10 + 1, 1}},
|
||||
{Number{false, Number::maxRep + 1, 0, Number::normalized{}},
|
||||
Number{Number::largestMantissa / 10 + 1, 1}},
|
||||
{Number{false, Number::largestMantissa + 1, 0, Number::normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::maxRep}},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::maxRep}},
|
||||
Number{Number::largestMantissa}},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::largestMantissa}},
|
||||
});
|
||||
auto test = [this](auto const& c) {
|
||||
for (auto const& [x, y, z] : c)
|
||||
@@ -302,14 +317,15 @@ public:
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "test_mul " << to_string(scale);
|
||||
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
// Case: Factor 1, Factor 2, Expected product, Line number
|
||||
using Case = std::tuple<Number, Number, Number, int>;
|
||||
auto test = [this](auto const& c) {
|
||||
for (auto const& [x, y, z] : c)
|
||||
for (auto const& [x, y, z, line] : c)
|
||||
{
|
||||
auto const result = x * y;
|
||||
std::stringstream ss;
|
||||
ss << x << " * " << y << " = " << result << ". Expected: " << z;
|
||||
BEAST_EXPECTS(result == z, ss.str());
|
||||
BEAST_EXPECTS(result == z, ss.str() + " line: " + std::to_string(line));
|
||||
}
|
||||
};
|
||||
auto tests = [&](auto const& cSmall, auto const& cLarge) {
|
||||
@@ -319,70 +335,100 @@ 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}},
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{2000000000000000, -15}},
|
||||
Number{2000000000000000, -15},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-2000000000000000, -15}},
|
||||
Number{-2000000000000000, -15},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{2000000000000000, -15}},
|
||||
Number{2000000000000000, -15},
|
||||
__LINE__},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{1000000000000000, -14}},
|
||||
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}},
|
||||
Number{1000000000000000, -14},
|
||||
__LINE__},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
// 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}},
|
||||
Number{9'999'999'999'999'998, 16},
|
||||
__LINE__},
|
||||
});
|
||||
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}},
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999862, -18}},
|
||||
Number{1999999999999999862, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999862, -18}},
|
||||
Number{-1999999999999999862, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999862, -18}},
|
||||
Number{1999999999999999862, -18},
|
||||
__LINE__},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
|
||||
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
|
||||
__LINE__},
|
||||
{Number{1000000000000000000, -32768},
|
||||
Number{1000000000000000000, -32768},
|
||||
Number{0}},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
// 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}},
|
||||
Number{2000000000000000001, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{1414213562373095048, -18},
|
||||
Number{-1999999999999999998, -18}},
|
||||
Number{-1999999999999999998, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{-1414213562373095049, -18},
|
||||
Number{1999999999999999999, -18}},
|
||||
{Number{3214285714285714278, -18}, Number{3111111111111111119, -18}, Number{10, 0}},
|
||||
// Maximum mantissa range - rounds up to 1e19
|
||||
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{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{1, 38}},
|
||||
Number{85'070'591'730'234'615'85, 19},
|
||||
__LINE__},
|
||||
// Maximum int64 range
|
||||
{Number{Number::maxRep, 0},
|
||||
Number{Number::maxRep, 0},
|
||||
Number{85'070'591'730'234'615'85, 19}},
|
||||
{Number{Number::largestMantissa, 0},
|
||||
Number{Number::largestMantissa, 0},
|
||||
Number{85'070'591'730'234'615'85, 19},
|
||||
__LINE__},
|
||||
});
|
||||
tests(cSmall, cLarge);
|
||||
}
|
||||
@@ -390,66 +436,90 @@ public:
|
||||
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " towards_zero";
|
||||
{
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{7}, Number{8}, Number{56}},
|
||||
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999, -15}},
|
||||
Number{1999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999, -15}},
|
||||
Number{-1999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999, -15}},
|
||||
Number{1999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{9999999999999999, -15}},
|
||||
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
|
||||
Number{9999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__}});
|
||||
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}},
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999861, -18}},
|
||||
Number{1999999999999999861, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999861, -18}},
|
||||
Number{-1999999999999999861, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999861, -18}},
|
||||
Number{1999999999999999861, -18},
|
||||
__LINE__},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{false, 9999999999999999579ULL, -18, Number::normalized{}}},
|
||||
Number{false, 9999999999999999579ULL, -18, Number::normalized{}},
|
||||
__LINE__},
|
||||
{Number{1000000000000000000, -32768},
|
||||
Number{1000000000000000000, -32768},
|
||||
Number{0}},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
// 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}},
|
||||
Number{2, 0},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{1414213562373095048, -18},
|
||||
Number{-1999999999999999997, -18}},
|
||||
Number{-1999999999999999997, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{-1414213562373095049, -18},
|
||||
Number{1999999999999999999, -18}},
|
||||
Number{1999999999999999999, -18},
|
||||
__LINE__},
|
||||
{Number{3214285714285714278, -18},
|
||||
Number{3111111111111111119, -18},
|
||||
Number{10, 0}},
|
||||
// Maximum mantissa range - rounds down to maxMantissa/10e1
|
||||
Number{10, 0},
|
||||
__LINE__},
|
||||
// Maximum internal 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{false, maxMantissa / 10 - 1, 20, Number::normalized{}}},
|
||||
Number{85'070'591'730'234'615'84, 19},
|
||||
__LINE__},
|
||||
// Maximum int64 range
|
||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||
{Number{Number::maxRep, 0},
|
||||
Number{Number::maxRep, 0},
|
||||
Number{85'070'591'730'234'615'84, 19}},
|
||||
{Number{Number::largestMantissa, 0},
|
||||
Number{Number::largestMantissa, 0},
|
||||
Number{85'070'591'730'234'615'84, 19},
|
||||
__LINE__},
|
||||
});
|
||||
tests(cSmall, cLarge);
|
||||
}
|
||||
@@ -457,66 +527,90 @@ public:
|
||||
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " downward";
|
||||
{
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{7}, Number{8}, Number{56}},
|
||||
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999, -15}},
|
||||
Number{1999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-2000000000000000, -15}},
|
||||
Number{-2000000000000000, -15},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999, -15}},
|
||||
Number{1999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{9999999999999999, -15}},
|
||||
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
|
||||
Number{9999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__}});
|
||||
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}},
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999861, -18}},
|
||||
Number{1999999999999999861, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999862, -18}},
|
||||
Number{-1999999999999999862, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999861, -18}},
|
||||
Number{1999999999999999861, -18},
|
||||
__LINE__},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}}},
|
||||
Number{false, 9'999'999'999'999'999'579ULL, -18, Number::normalized{}},
|
||||
__LINE__},
|
||||
{Number{1000000000000000000, -32768},
|
||||
Number{1000000000000000000, -32768},
|
||||
Number{0}},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
// 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}},
|
||||
Number{2, 0},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{1414213562373095048, -18},
|
||||
Number{-1999999999999999998, -18}},
|
||||
Number{-1999999999999999998, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{-1414213562373095049, -18},
|
||||
Number{1999999999999999999, -18}},
|
||||
Number{1999999999999999999, -18},
|
||||
__LINE__},
|
||||
{Number{3214285714285714278, -18},
|
||||
Number{3111111111111111119, -18},
|
||||
Number{10, 0}},
|
||||
// Maximum mantissa range - rounds down to maxMantissa/10e1
|
||||
Number{10, 0},
|
||||
__LINE__},
|
||||
// Maximum internal mantissa range - rounds down to
|
||||
// maxMantissa/10-1
|
||||
// 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{false, maxMantissa / 10 - 1, 20, Number::normalized{}}},
|
||||
Number{85'070'591'730'234'615'84, 19},
|
||||
__LINE__},
|
||||
// Maximum int64 range
|
||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||
{Number{Number::maxRep, 0},
|
||||
Number{Number::maxRep, 0},
|
||||
Number{85'070'591'730'234'615'84, 19}},
|
||||
{Number{Number::largestMantissa, 0},
|
||||
Number{Number::largestMantissa, 0},
|
||||
Number{85'070'591'730'234'615'84, 19},
|
||||
__LINE__},
|
||||
});
|
||||
tests(cSmall, cLarge);
|
||||
}
|
||||
@@ -524,66 +618,89 @@ public:
|
||||
testcase << "test_mul " << to_string(Number::getMantissaScale()) << " upward";
|
||||
{
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{7}, Number{8}, Number{56}},
|
||||
{{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{2000000000000000, -15}},
|
||||
Number{2000000000000000, -15},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999, -15}},
|
||||
Number{-1999999999999999, -15},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{2000000000000000, -15}},
|
||||
Number{2000000000000000, -15},
|
||||
__LINE__},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{1000000000000000, -14}},
|
||||
{Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, Number{0}}});
|
||||
Number{1000000000000000, -14},
|
||||
__LINE__},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0},
|
||||
__LINE__}});
|
||||
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}},
|
||||
{Number{7}, Number{8}, Number{56}, __LINE__},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999862, -18}},
|
||||
Number{1999999999999999862, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-1999999999999999861, -18}},
|
||||
Number{-1999999999999999861, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{1999999999999999862, -18}},
|
||||
Number{1999999999999999862, -18},
|
||||
__LINE__},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{999999999999999958, -17}},
|
||||
Number{999999999999999958, -17},
|
||||
__LINE__},
|
||||
{Number{1000000000000000000, -32768},
|
||||
Number{1000000000000000000, -32768},
|
||||
Number{0}},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
// 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}},
|
||||
Number{2000000000000000001, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{1414213562373095048, -18},
|
||||
Number{-1999999999999999997, -18}},
|
||||
Number{-1999999999999999997, -18},
|
||||
__LINE__},
|
||||
{Number{-1414213562373095048, -18},
|
||||
Number{-1414213562373095049, -18},
|
||||
Number{2, 0}},
|
||||
Number{2, 0},
|
||||
__LINE__},
|
||||
{Number{3214285714285714278, -18},
|
||||
Number{3111111111111111119, -18},
|
||||
Number{1000000000000000001, -17}},
|
||||
// Maximum mantissa range - rounds up to minMantissa*10
|
||||
// 1e19*1e19=1e38
|
||||
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{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{false, maxMantissa, 0, Number::normalized{}},
|
||||
Number{1, 38}},
|
||||
Number{85'070'591'730'234'615'85, 19},
|
||||
__LINE__},
|
||||
// Maximum int64 range
|
||||
// 85'070'591'730'234'615'847'396'907'784'232'501'249
|
||||
{Number{Number::maxRep, 0},
|
||||
Number{Number::maxRep, 0},
|
||||
Number{85'070'591'730'234'615'85, 19}},
|
||||
{Number{Number::largestMantissa, 0},
|
||||
Number{Number::largestMantissa, 0},
|
||||
Number{85'070'591'730'234'615'85, 19},
|
||||
__LINE__},
|
||||
});
|
||||
tests(cSmall, cLarge);
|
||||
}
|
||||
@@ -814,6 +931,11 @@ 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}},
|
||||
@@ -825,16 +947,16 @@ public:
|
||||
{Number{0}, 5, Number{0}},
|
||||
{Number{5625, -4}, 2, Number{75, -2}}});
|
||||
auto const cLarge = std::to_array<Case>({
|
||||
{Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}},
|
||||
{Number{false, maxInternalMantissa - 9, -1, Number::normalized{}},
|
||||
2,
|
||||
Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}},
|
||||
{Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}},
|
||||
{Number{false, maxInternalMantissa - 9, 0, Number::normalized{}},
|
||||
2,
|
||||
Number{false, 3'162'277'660'168'379'330, -9, Number::normalized{}}},
|
||||
{Number{Number::maxRep},
|
||||
{Number{Number::largestMantissa},
|
||||
2,
|
||||
Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}},
|
||||
{Number{Number::maxRep},
|
||||
{Number{Number::largestMantissa},
|
||||
4,
|
||||
Number{false, 55'108'98747006743627, -14, Number::normalized{}}},
|
||||
});
|
||||
@@ -883,6 +1005,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
auto const maxInternalMantissa = power(10, Number::mantissaLog()) * 10 - 1;
|
||||
|
||||
auto const cSmall = std::to_array<Number>({
|
||||
Number{2},
|
||||
Number{2'000'000},
|
||||
@@ -892,7 +1016,10 @@ public:
|
||||
Number{5, -1},
|
||||
Number{0},
|
||||
Number{5625, -4},
|
||||
Number{Number::maxRep},
|
||||
Number{Number::largestMantissa},
|
||||
maxInternalMantissa,
|
||||
Number{Number::minMantissa(), 0, Number::unchecked{}},
|
||||
Number{Number::maxMantissa(), 0, Number::unchecked{}},
|
||||
});
|
||||
test(cSmall);
|
||||
bool caught = false;
|
||||
@@ -1243,18 +1370,18 @@ public:
|
||||
case MantissaRange::large:
|
||||
// Test the edges
|
||||
// ((exponent < -(28)) || (exponent > -(8)))))
|
||||
test(Number::min(), "1e-32750");
|
||||
test(Number::min(), "922337203685477581e-32768");
|
||||
test(Number::max(), "9223372036854775807e32768");
|
||||
test(Number::lowest(), "-9223372036854775807e32768");
|
||||
{
|
||||
NumberRoundModeGuard mg(Number::towards_zero);
|
||||
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
|
||||
BEAST_EXPECT(maxMantissa == 9'223'372'036'854'775'807ULL);
|
||||
test(
|
||||
Number{false, maxMantissa, 0, Number::normalized{}}, "9999999999999999990");
|
||||
Number{false, maxMantissa, 0, Number::normalized{}}, "9223372036854775807");
|
||||
test(
|
||||
Number{true, maxMantissa, 0, Number::normalized{}}, "-9999999999999999990");
|
||||
Number{true, maxMantissa, 0, Number::normalized{}}, "-9223372036854775807");
|
||||
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
|
||||
@@ -1490,7 +1617,7 @@ public:
|
||||
Number const initalXrp{INITIAL_XRP};
|
||||
BEAST_EXPECT(initalXrp.exponent() > 0);
|
||||
|
||||
Number const maxInt64{Number::maxRep};
|
||||
Number const maxInt64{Number::largestMantissa};
|
||||
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}));
|
||||
@@ -1507,21 +1634,217 @@ public:
|
||||
Number const initalXrp{INITIAL_XRP};
|
||||
BEAST_EXPECT(initalXrp.exponent() <= 0);
|
||||
|
||||
Number const maxInt64{Number::maxRep};
|
||||
Number const maxInt64{Number::largestMantissa};
|
||||
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 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{}}));
|
||||
{
|
||||
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__);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1552,6 +1875,7 @@ public:
|
||||
test_truncate();
|
||||
testRounding();
|
||||
testInt64();
|
||||
testNormalizeToRange();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
209
src/tests/libxrpl/basics/MallocTrim.cpp
Normal file
209
src/tests/libxrpl/basics/MallocTrim.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
#include <xrpl/basics/MallocTrim.h>
|
||||
|
||||
#include <boost/predef.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
// cSpell:ignore statm
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
namespace xrpl::detail {
|
||||
long
|
||||
parseStatmRSSkB(std::string const& statm);
|
||||
} // namespace xrpl::detail
|
||||
#endif
|
||||
|
||||
TEST(MallocTrimReport, structure)
|
||||
{
|
||||
// Test default construction
|
||||
MallocTrimReport report;
|
||||
EXPECT_EQ(report.supported, false);
|
||||
EXPECT_EQ(report.trimResult, -1);
|
||||
EXPECT_EQ(report.rssBeforeKB, -1);
|
||||
EXPECT_EQ(report.rssAfterKB, -1);
|
||||
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
|
||||
EXPECT_EQ(report.minfltDelta, -1);
|
||||
EXPECT_EQ(report.majfltDelta, -1);
|
||||
EXPECT_EQ(report.deltaKB(), 0);
|
||||
|
||||
// Test deltaKB calculation - memory freed
|
||||
report.rssBeforeKB = 1000;
|
||||
report.rssAfterKB = 800;
|
||||
EXPECT_EQ(report.deltaKB(), -200);
|
||||
|
||||
// Test deltaKB calculation - memory increased
|
||||
report.rssBeforeKB = 500;
|
||||
report.rssAfterKB = 600;
|
||||
EXPECT_EQ(report.deltaKB(), 100);
|
||||
|
||||
// Test deltaKB calculation - no change
|
||||
report.rssBeforeKB = 1234;
|
||||
report.rssAfterKB = 1234;
|
||||
EXPECT_EQ(report.deltaKB(), 0);
|
||||
}
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
TEST(parseStatmRSSkB, standard_format)
|
||||
{
|
||||
using xrpl::detail::parseStatmRSSkB;
|
||||
|
||||
// Test standard format: size resident shared text lib data dt
|
||||
// Assuming 4KB page size: resident=1000 pages = 4000 KB
|
||||
{
|
||||
std::string statm = "25365 1000 2377 0 0 5623 0";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
// Note: actual result depends on system page size
|
||||
// On most systems it's 4KB, so 1000 pages = 4000 KB
|
||||
EXPECT_GT(result, 0);
|
||||
}
|
||||
|
||||
// Test with newline
|
||||
{
|
||||
std::string statm = "12345 2000 1234 0 0 3456 0\n";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_GT(result, 0);
|
||||
}
|
||||
|
||||
// Test with tabs
|
||||
{
|
||||
std::string statm = "12345\t2000\t1234\t0\t0\t3456\t0";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_GT(result, 0);
|
||||
}
|
||||
|
||||
// Test zero resident pages
|
||||
{
|
||||
std::string statm = "25365 0 2377 0 0 5623 0";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, 0);
|
||||
}
|
||||
|
||||
// Test with extra whitespace
|
||||
{
|
||||
std::string statm = " 25365 1000 2377 ";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_GT(result, 0);
|
||||
}
|
||||
|
||||
// Test empty string
|
||||
{
|
||||
std::string statm = "";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, -1);
|
||||
}
|
||||
|
||||
// Test malformed data (only one field)
|
||||
{
|
||||
std::string statm = "25365";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, -1);
|
||||
}
|
||||
|
||||
// Test malformed data (non-numeric)
|
||||
{
|
||||
std::string statm = "abc def ghi";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, -1);
|
||||
}
|
||||
|
||||
// Test malformed data (second field non-numeric)
|
||||
{
|
||||
std::string statm = "25365 abc 2377";
|
||||
long result = parseStatmRSSkB(statm);
|
||||
EXPECT_EQ(result, -1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(mallocTrim, without_debug_logging)
|
||||
{
|
||||
beast::Journal journal{beast::Journal::getNullSink()};
|
||||
|
||||
MallocTrimReport report = mallocTrim("without_debug", journal);
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
EXPECT_EQ(report.supported, true);
|
||||
EXPECT_GE(report.trimResult, 0);
|
||||
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
|
||||
EXPECT_EQ(report.minfltDelta, -1);
|
||||
EXPECT_EQ(report.majfltDelta, -1);
|
||||
#else
|
||||
EXPECT_EQ(report.supported, false);
|
||||
EXPECT_EQ(report.trimResult, -1);
|
||||
EXPECT_EQ(report.rssBeforeKB, -1);
|
||||
EXPECT_EQ(report.rssAfterKB, -1);
|
||||
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
|
||||
EXPECT_EQ(report.minfltDelta, -1);
|
||||
EXPECT_EQ(report.majfltDelta, -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(mallocTrim, empty_tag)
|
||||
{
|
||||
beast::Journal journal{beast::Journal::getNullSink()};
|
||||
MallocTrimReport report = mallocTrim("", journal);
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
EXPECT_EQ(report.supported, true);
|
||||
EXPECT_GE(report.trimResult, 0);
|
||||
#else
|
||||
EXPECT_EQ(report.supported, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(mallocTrim, with_debug_logging)
|
||||
{
|
||||
struct DebugSink : public beast::Journal::Sink
|
||||
{
|
||||
DebugSink() : Sink(beast::severities::kDebug, false)
|
||||
{
|
||||
}
|
||||
void
|
||||
write(beast::severities::Severity, std::string const&) override
|
||||
{
|
||||
}
|
||||
void
|
||||
writeAlways(beast::severities::Severity, std::string const&) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
DebugSink sink;
|
||||
beast::Journal journal{sink};
|
||||
|
||||
MallocTrimReport report = mallocTrim("debug_test", journal);
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
EXPECT_EQ(report.supported, true);
|
||||
EXPECT_GE(report.trimResult, 0);
|
||||
EXPECT_GE(report.durationUs.count(), 0);
|
||||
EXPECT_GE(report.minfltDelta, 0);
|
||||
EXPECT_GE(report.majfltDelta, 0);
|
||||
#else
|
||||
EXPECT_EQ(report.supported, false);
|
||||
EXPECT_EQ(report.trimResult, -1);
|
||||
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
|
||||
EXPECT_EQ(report.minfltDelta, -1);
|
||||
EXPECT_EQ(report.majfltDelta, -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(mallocTrim, repeated_calls)
|
||||
{
|
||||
beast::Journal journal{beast::Journal::getNullSink()};
|
||||
|
||||
// Call malloc_trim multiple times to ensure it's safe
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
MallocTrimReport report = mallocTrim("iteration_" + std::to_string(i), journal);
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
EXPECT_EQ(report.supported, true);
|
||||
EXPECT_GE(report.trimResult, 0);
|
||||
#else
|
||||
EXPECT_EQ(report.supported, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <xrpld/shamap/NodeFamily.h>
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/MallocTrim.h>
|
||||
#include <xrpl/basics/ResolverAsio.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/asio/io_latency_probe.h>
|
||||
@@ -1053,6 +1054,8 @@ public:
|
||||
<< "; size after: " << cachedSLEs_.size();
|
||||
}
|
||||
|
||||
mallocTrim("doSweep", m_journal);
|
||||
|
||||
// Set timer to do another sweep later.
|
||||
setSweepTimer();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user