mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-08 19:26:45 +00:00
Compare commits
6 Commits
ximinez/nu
...
gregtatcam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69ed8b6970 | ||
|
|
4451fa33f6 | ||
|
|
a1d33e98e5 | ||
|
|
2f015e9c65 | ||
|
|
a389f922dd | ||
|
|
79f4ddc4a6 |
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-8abe82e",
|
||||
"image_tag": "sha-63ffdc3",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
@@ -67,7 +67,7 @@
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "debian:bookworm"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-63ffdc3"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "registry.access.redhat.com/ubi9/ubi:latest"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-63ffdc3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
3
.github/workflows/build-nix-images.yml
vendored
3
.github/workflows/build-nix-images.yml
vendored
@@ -22,7 +22,8 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
# Read `on-trigger.yml` for the rationale behind this concurrency group name.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
|
||||
3
.github/workflows/build-packaging-images.yml
vendored
3
.github/workflows/build-packaging-images.yml
vendored
@@ -20,7 +20,8 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
# Read `on-trigger.yml` for the rationale behind this concurrency group name.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@312aaab296060ff89d7f798dcab59f019bea6e02
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
4
.github/workflows/publish-docs.yml
vendored
4
.github/workflows/publish-docs.yml
vendored
@@ -41,13 +41,13 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
@@ -370,7 +370,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
|
||||
6
.github/workflows/reusable-clang-tidy.yml
vendored
6
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -29,14 +29,14 @@ jobs:
|
||||
if: ${{ inputs.check_only_changed }}
|
||||
permissions:
|
||||
contents: read
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@224f3c48d3014d082a1129237b8291ff0b0a331f
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@312aaab296060ff89d7f798dcab59f019bea6e02
|
||||
|
||||
run-clang-tidy:
|
||||
name: Run clang tidy
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-8abe82e"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-63ffdc3"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
25
.github/workflows/reusable-package.yml
vendored
25
.github/workflows/reusable-package.yml
vendored
@@ -68,31 +68,6 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
# Packaging runs in a vanilla distro image, so the tooling has to come
|
||||
# from the distro's archive: debhelper for deb, rpm-build (and the
|
||||
# systemd / find-debuginfo macros it depends on) for rpm. Run this
|
||||
# before actions/checkout so the latter can use git (real history) for
|
||||
# build_pkg.sh's SOURCE_DATE_EPOCH; otherwise it falls back to a tarball
|
||||
# download and the timestamp comes from wall-clock time.
|
||||
- name: Install packaging tooling (deb)
|
||||
if: ${{ matrix.distro == 'debian' }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
debhelper \
|
||||
git
|
||||
|
||||
- name: Install packaging tooling (rpm)
|
||||
if: ${{ matrix.distro == 'rhel' }}
|
||||
run: |
|
||||
dnf install -y --setopt=install_weak_deps=False \
|
||||
git \
|
||||
rpm-build \
|
||||
redhat-rpm-config \
|
||||
systemd-rpm-macros
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
|
||||
2
.github/workflows/reusable-upload-recipe.yml
vendored
2
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,7 +40,7 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
2
.github/workflows/upload-conan-deps.yml
vendored
2
.github/workflows/upload-conan-deps.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -51,43 +51,37 @@ namespace detail {
|
||||
* compile time. Doing it at runtime would be pretty wasteful and
|
||||
* inefficient.
|
||||
*/
|
||||
constexpr std::size_t kUint64Digits = 20;
|
||||
constexpr std::size_t kUint128Digits = 39;
|
||||
|
||||
template <typename T, std::size_t Digits>
|
||||
consteval std::array<T, Digits>
|
||||
constexpr std::size_t kInt64Digits = 20;
|
||||
consteval std::array<std::uint64_t, kInt64Digits>
|
||||
buildPowersOfTen()
|
||||
{
|
||||
std::array<T, Digits> result{};
|
||||
std::array<std::uint64_t, kInt64Digits> result{};
|
||||
|
||||
T power = 1;
|
||||
std::uint64_t power = 1;
|
||||
std::size_t exponent = 0;
|
||||
// end the loop early so it doesn't overflow;
|
||||
for (; exponent < result.size() - 1; ++exponent, power *= 10)
|
||||
{
|
||||
result[exponent] = power;
|
||||
if (power > std::numeric_limits<T>::max() / 10)
|
||||
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is too big");
|
||||
}
|
||||
result[exponent] = power;
|
||||
if (power < std::numeric_limits<T>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is not big enough for the given type");
|
||||
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
|
||||
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T = std::uint64_t, std::size_t Digits = detail::kUint64Digits>
|
||||
constexpr std::array<T, Digits> kPowerOfTenImpl = detail::buildPowersOfTen<T, Digits>();
|
||||
|
||||
constexpr auto kPowerOfTen = kPowerOfTenImpl<std::uint64_t, detail::kUint64Digits>;
|
||||
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
|
||||
|
||||
static_assert(kPowerOfTen[0] == 1);
|
||||
static_assert(kPowerOfTen[1] == 10);
|
||||
static_assert(kPowerOfTen[10] == 10'000'000'000);
|
||||
static_assert(
|
||||
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kUint64Digits - 1);
|
||||
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
|
||||
|
||||
/** MantissaRange defines a range for the mantissa of a normalized Number.
|
||||
*
|
||||
@@ -147,7 +141,7 @@ struct MantissaRange final
|
||||
int const log{getExponent(scale)};
|
||||
rep const min{getMin(scale, log)};
|
||||
rep const max{(min * 10) - 1};
|
||||
CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)};
|
||||
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
|
||||
|
||||
static MantissaRange const&
|
||||
getMantissaRange(MantissaScale scale);
|
||||
@@ -325,8 +319,6 @@ public:
|
||||
static constexpr internalrep kMaxRep = std::numeric_limits<rep>::max();
|
||||
static_assert(kMaxRep == 9'223'372'036'854'775'807);
|
||||
static_assert(-kMaxRep == std::numeric_limits<rep>::min() + 1);
|
||||
static constexpr internalrep kMaxRepUp = ((kMaxRep / 10) + 1) * 10;
|
||||
static_assert(kMaxRepUp == 9'223'372'036'854'775'810ULL);
|
||||
|
||||
// May need to make unchecked private
|
||||
struct Unchecked
|
||||
@@ -551,15 +543,9 @@ private:
|
||||
// changing the values inside the range.
|
||||
static thread_local std::reference_wrapper<MantissaRange const> kRange;
|
||||
|
||||
class Guard;
|
||||
|
||||
void
|
||||
normalize(MantissaRange const& range);
|
||||
|
||||
// Guard has the fields that we need, as well as MantissaRange, so if we have a guard, use that
|
||||
void
|
||||
normalize(Guard const& guard);
|
||||
|
||||
/** Normalize Number components to an arbitrary range.
|
||||
*
|
||||
* min/maxMantissa are parameters because this function is used by both
|
||||
@@ -574,7 +560,7 @@ private:
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix);
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
|
||||
|
||||
template <class T>
|
||||
friend void
|
||||
@@ -584,7 +570,7 @@ private:
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
bool dropped);
|
||||
|
||||
[[nodiscard]] bool
|
||||
@@ -602,6 +588,8 @@ private:
|
||||
// UB, and can vary across compilers.
|
||||
static internalrep
|
||||
externalToInternal(rep mantissa);
|
||||
|
||||
class Guard;
|
||||
};
|
||||
|
||||
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
|
||||
@@ -879,26 +867,6 @@ to_string(MantissaRange::MantissaScale const& scale)
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string
|
||||
to_string(Number::RoundingMode const& round)
|
||||
{
|
||||
switch (round)
|
||||
{
|
||||
enum class RoundingMode { ToNearest, TowardsZero, Downward, Upward };
|
||||
|
||||
case Number::RoundingMode::ToNearest:
|
||||
return "ToNearest";
|
||||
case Number::RoundingMode::TowardsZero:
|
||||
return "TowardsZero";
|
||||
case Number::RoundingMode::Downward:
|
||||
return "Downward";
|
||||
case Number::RoundingMode::Upward:
|
||||
return "Upward";
|
||||
default:
|
||||
throw std::runtime_error("Bad rounding mode");
|
||||
}
|
||||
}
|
||||
|
||||
class SaveNumberRoundMode
|
||||
{
|
||||
Number::RoundingMode mode_;
|
||||
|
||||
@@ -65,7 +65,7 @@ MantissaRange::getRanges()
|
||||
static_assert(kRange.log == 15);
|
||||
static_assert(kRange.min < Number::kMaxRep);
|
||||
static_assert(kRange.max < Number::kMaxRep);
|
||||
static_assert(kRange.cuspRoundingFix == CuspRoundingFix::Disabled);
|
||||
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled);
|
||||
}
|
||||
{
|
||||
[[maybe_unused]]
|
||||
@@ -76,7 +76,7 @@ MantissaRange::getRanges()
|
||||
static_assert(kRange.log == 18);
|
||||
static_assert(kRange.min < Number::kMaxRep);
|
||||
static_assert(kRange.max > Number::kMaxRep);
|
||||
static_assert(kRange.cuspRoundingFix == CuspRoundingFix::Disabled);
|
||||
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled);
|
||||
}
|
||||
{
|
||||
[[maybe_unused]]
|
||||
@@ -87,7 +87,7 @@ MantissaRange::getRanges()
|
||||
static_assert(kRange.log == 18);
|
||||
static_assert(kRange.min < Number::kMaxRep);
|
||||
static_assert(kRange.max > Number::kMaxRep);
|
||||
static_assert(kRange.cuspRoundingFix == CuspRoundingFix::Enabled);
|
||||
static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Enabled);
|
||||
}
|
||||
return map;
|
||||
}();
|
||||
@@ -171,21 +171,7 @@ class Number::Guard
|
||||
std::uint8_t sbit_ : 1 {0}; // the sign of the guard digits
|
||||
|
||||
public:
|
||||
internalrep const minMantissa_;
|
||||
internalrep const maxMantissa_;
|
||||
MantissaRange::CuspRoundingFix const cuspRoundingFix_;
|
||||
|
||||
explicit Guard(
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
: minMantissa_(minMantissa), maxMantissa_(maxMantissa), cuspRoundingFix_(cuspRoundingFix)
|
||||
{
|
||||
}
|
||||
|
||||
explicit Guard(MantissaRange const& range) : Guard(range.min, range.max, range.cuspRoundingFix)
|
||||
{
|
||||
}
|
||||
explicit Guard() = default;
|
||||
|
||||
// set & test the sign bit
|
||||
void
|
||||
@@ -208,10 +194,6 @@ public:
|
||||
unsigned
|
||||
pop() noexcept;
|
||||
|
||||
// if true, there are no digits in the guard, including dropped digits (xbit_)
|
||||
bool
|
||||
empty() const noexcept;
|
||||
|
||||
/** Drop a digit from the mantissa, and increment the exponent, storing the dropped digit in
|
||||
* this Guard.
|
||||
*
|
||||
@@ -224,51 +206,40 @@ public:
|
||||
void
|
||||
doDropDigit(T& mantissa, int& exponent) noexcept;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
doRoundDown(bool& negative, T& mantissa, int& exponent);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
doRound(rep& drops, MantissaRange::CuspRoundingFix cuspRoundingFix, std::string location);
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
void
|
||||
pushOverflow(T const& mantissa, MantissaRange::CuspRoundingFix cuspRoundingFix);
|
||||
|
||||
enum class Round {
|
||||
// The result is exact. No rounding is needed. Only used if cuspRoundingFix is enabled.
|
||||
Exact = -2,
|
||||
// Round down. Since we use integer math, that usually means no change is needed.
|
||||
// Exceptions are for when the result is between kMaxRap and kMaxRepUp (round to kMaxRep),
|
||||
// or after subtraction where _any_ remainder will modify the result. The latter is what
|
||||
// distinguishes Exact from Down.
|
||||
Down = -1,
|
||||
// The result was exactly half-way between two integers. This will round to even.
|
||||
Even = 0,
|
||||
// Round up. Always adds 1 (or subtracts 1 in some cases if cuspRoundingFix is not enabled)
|
||||
Up = 1,
|
||||
};
|
||||
|
||||
// Indicate round direction. See Round enum above.
|
||||
// Indicate round direction: 1 is up, -1 is down, 0 is even
|
||||
// This enables the client to round towards nearest, and on
|
||||
// tie, round towards even.
|
||||
[[nodiscard]] Round
|
||||
[[nodiscard]] int
|
||||
round() const noexcept;
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
doRoundUp(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
std::string location);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
doRoundDown(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
doRound(rep& drops, std::string location) const;
|
||||
|
||||
private:
|
||||
void
|
||||
doPush(unsigned d) noexcept;
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent);
|
||||
bringIntoRange(bool& negative, T& mantissa, int& exponent, internalrep const& minMantissa);
|
||||
};
|
||||
|
||||
inline void
|
||||
@@ -318,12 +289,6 @@ Number::Guard::pop() noexcept
|
||||
return d;
|
||||
}
|
||||
|
||||
inline bool
|
||||
Number::Guard::empty() const noexcept
|
||||
{
|
||||
return digits_ == 0 && !xbit_;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
Number::Guard::doDropDigit(T& mantissa, int& exponent) noexcept
|
||||
@@ -345,99 +310,63 @@ Number::Guard::doDropDigit<uint128_t>(uint128_t& mantissa, int& exponent) noexce
|
||||
++exponent;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
Number::Guard::pushOverflow(T const& mantissa, MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
{
|
||||
XRPL_ASSERT(mantissa <= kMaxRepUp, "xrpl::Number::Guard::doRoundUp : valid mantissa");
|
||||
if (cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled && mantissa > kMaxRep &&
|
||||
mantissa < kMaxRepUp)
|
||||
{
|
||||
// Special case rounding rules for the values between kMaxRep and kMaxRepUp.
|
||||
// Scale the spread between kMaxRep and kMaxRepUp from 1 to 9, and push it onto the guard as
|
||||
// if it was a digit that got removed, but don't remove it. This method is future-proof in
|
||||
// case the number of mantissa bits ever changes. Effects:
|
||||
// * For round to nearest
|
||||
// * if the mantissa is below the midpoint, it'll round "down" to kMaxRepUp
|
||||
// * if above the midpoint, it'll round "down" to kMaxRep
|
||||
// * if can never be exactly at the midpoint, because kMaxRepUp is always even, and
|
||||
// kMaxRep is always odd, so don't worry about it.
|
||||
// * For round upward, will round up to kMaxRepUp for positive values, down for negative.
|
||||
// * For round downward, does the opposite of upward.
|
||||
// * For round toward zero, always rounds down.
|
||||
auto constexpr spread = kMaxRepUp - kMaxRep;
|
||||
static_assert(spread < 10 && spread >= 0);
|
||||
|
||||
auto const diff = mantissa - kMaxRep;
|
||||
auto const digit = (diff * 10) / spread;
|
||||
XRPL_ASSERT(digit > 0 && digit < 10, "xrpld::Number::Guard::xxxx : valid overflow digit");
|
||||
|
||||
// Don't remove the digit from the mantissa, but add it to the guard as if it was.
|
||||
push(digit);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns:
|
||||
// Down if Guard is less than half
|
||||
// Even if Guard is exactly half
|
||||
// Up if Guard is greater than half
|
||||
Number::Guard::Round
|
||||
// -1 if Guard is less than half
|
||||
// 0 if Guard is exactly half
|
||||
// 1 if Guard is greater than half
|
||||
int
|
||||
Number::Guard::round() const noexcept
|
||||
{
|
||||
auto mode = Number::getround();
|
||||
|
||||
if (cuspRoundingFix_ != MantissaRange::CuspRoundingFix::Disabled && empty())
|
||||
{
|
||||
// No remainder
|
||||
return Round::Exact;
|
||||
}
|
||||
|
||||
if (mode == RoundingMode::TowardsZero)
|
||||
return Round::Down;
|
||||
return -1;
|
||||
|
||||
if (mode == RoundingMode::Downward)
|
||||
{
|
||||
if (sbit_)
|
||||
{
|
||||
if (digits_ > 0 || xbit_)
|
||||
return Round::Up;
|
||||
return 1;
|
||||
}
|
||||
return Round::Down;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mode == RoundingMode::Upward)
|
||||
{
|
||||
if (sbit_)
|
||||
return Round::Down;
|
||||
return -1;
|
||||
if (digits_ > 0 || xbit_)
|
||||
return Round::Up;
|
||||
return Round::Down;
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// assume round to nearest if mode is not one of the predefined values
|
||||
if (digits_ > 0x5000'0000'0000'0000)
|
||||
return Round::Up;
|
||||
return 1;
|
||||
if (digits_ < 0x5000'0000'0000'0000)
|
||||
return Round::Down;
|
||||
return -1;
|
||||
if (xbit_)
|
||||
return Round::Up;
|
||||
return Round::Even;
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent)
|
||||
Number::Guard::bringIntoRange(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa)
|
||||
{
|
||||
// Bring mantissa back into the minMantissa / maxMantissa range AFTER
|
||||
// rounding
|
||||
if (mantissa < minMantissa_ &&
|
||||
(cuspRoundingFix_ == MantissaRange::CuspRoundingFix::Disabled || mantissa != 0))
|
||||
if (mantissa < minMantissa)
|
||||
{
|
||||
mantissa *= 10;
|
||||
--exponent;
|
||||
}
|
||||
if (exponent < kMinExponent ||
|
||||
(cuspRoundingFix_ != MantissaRange::CuspRoundingFix::Disabled && mantissa == 0))
|
||||
if (exponent < kMinExponent)
|
||||
{
|
||||
static constexpr Number kZero = Number{};
|
||||
|
||||
@@ -449,17 +378,22 @@ Number::Guard::bringIntoRange(bool& negative, T& mantissa, int& exponent)
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string location)
|
||||
Number::Guard::doRoundUp(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
std::string location)
|
||||
{
|
||||
pushOverflow(mantissa, cuspRoundingFix_);
|
||||
|
||||
auto const r = round();
|
||||
if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1))
|
||||
auto r = round();
|
||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||
{
|
||||
auto const safeToIncrement = [this](auto const& mantissa) {
|
||||
return mantissa < maxMantissa_ && mantissa < kMaxRep;
|
||||
auto const safeToIncrement = [&maxMantissa](auto const& mantissa) {
|
||||
return mantissa < maxMantissa && mantissa < kMaxRep;
|
||||
};
|
||||
if (cuspRoundingFix_ != MantissaRange::CuspRoundingFix::Disabled)
|
||||
if (cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled)
|
||||
{
|
||||
// Ensure mantissa after incrementing fits within both the
|
||||
// min/maxMantissa range and is a valid "rep".
|
||||
@@ -475,20 +409,20 @@ Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string
|
||||
// be impossible to recurse more than once, because once the mantissa is divided by
|
||||
// 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 will have no
|
||||
// chance of bringing it back over.
|
||||
if (mantissa > kMaxRep && mantissa < kMaxRepUp)
|
||||
{
|
||||
mantissa = kMaxRepUp;
|
||||
}
|
||||
else
|
||||
{
|
||||
doDropDigit(mantissa, exponent);
|
||||
XRPL_ASSERT_PARTS(
|
||||
safeToIncrement(mantissa),
|
||||
"xrpl::Number::Guard::doRoundUp",
|
||||
"can't recurse more than once");
|
||||
doRoundUp(negative, mantissa, exponent, location);
|
||||
return;
|
||||
}
|
||||
doDropDigit(mantissa, exponent);
|
||||
XRPL_ASSERT_PARTS(
|
||||
safeToIncrement(mantissa),
|
||||
"xrpl::Number::Guard::doRoundUp",
|
||||
"can't recurse more than once");
|
||||
doRoundUp(
|
||||
negative,
|
||||
mantissa,
|
||||
exponent,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -498,7 +432,7 @@ Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string
|
||||
++mantissa;
|
||||
// Ensure mantissa after incrementing fits within both the
|
||||
// min/maxMantissa range and is a valid "rep".
|
||||
if (mantissa > maxMantissa_ || mantissa > kMaxRep)
|
||||
if (mantissa > maxMantissa || mantissa > kMaxRep)
|
||||
{
|
||||
// Don't use doDropDigit here
|
||||
mantissa /= 10;
|
||||
@@ -506,62 +440,38 @@ Number::Guard::doRoundUp(bool& negative, T& mantissa, int& exponent, std::string
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (
|
||||
cuspRoundingFix_ != MantissaRange::CuspRoundingFix::Disabled && mantissa > kMaxRep &&
|
||||
mantissa < kMaxRepUp)
|
||||
{
|
||||
mantissa = kMaxRep;
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent);
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||
if (exponent > kMaxExponent)
|
||||
Throw<std::overflow_error>(std::string(location));
|
||||
}
|
||||
|
||||
template <UnsignedMantissa T>
|
||||
void
|
||||
Number::Guard::doRoundDown(bool& negative, T& mantissa, int& exponent)
|
||||
Number::Guard::doRoundDown(
|
||||
bool& negative,
|
||||
T& mantissa,
|
||||
int& exponent,
|
||||
internalrep const& minMantissa)
|
||||
{
|
||||
// Do not pushOverflow here.
|
||||
|
||||
auto r = round();
|
||||
if (cuspRoundingFix_ != MantissaRange::CuspRoundingFix::Disabled)
|
||||
if (r == 1 || (r == 0 && (mantissa & 1) == 1))
|
||||
{
|
||||
// If there was any remainder, subtract 1 from the result. This is sufficient to get the
|
||||
// best rounding.
|
||||
XRPL_ASSERT(
|
||||
empty() || mantissa > maxMantissa_,
|
||||
"xrpl::Number::Guard::doRoundDown : mantissa is expected size");
|
||||
if (r != Round::Exact)
|
||||
--mantissa;
|
||||
if (mantissa < minMantissa)
|
||||
{
|
||||
--mantissa;
|
||||
mantissa *= 10;
|
||||
--exponent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r == Round::Up || (r == Round::Even && (mantissa & 1) == 1))
|
||||
{
|
||||
--mantissa;
|
||||
if (mantissa < minMantissa_)
|
||||
{
|
||||
mantissa *= 10;
|
||||
--exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
bringIntoRange(negative, mantissa, exponent);
|
||||
bringIntoRange(negative, mantissa, exponent, minMantissa);
|
||||
}
|
||||
|
||||
// Modify the result to the correctly rounded value
|
||||
void
|
||||
Number::Guard::doRound(
|
||||
rep& drops,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
std::string location)
|
||||
Number::Guard::doRound(rep& drops, std::string location) const
|
||||
{
|
||||
pushOverflow(drops, cuspRoundingFix);
|
||||
|
||||
auto r = round();
|
||||
if (r == Round::Up || (r == Round::Even && (drops & 1) == 1))
|
||||
if (r == 1 || (r == 0 && (drops & 1) == 1))
|
||||
{
|
||||
if (drops >= kMaxRep)
|
||||
{
|
||||
@@ -576,14 +486,6 @@ Number::Guard::doRound(
|
||||
}
|
||||
++drops;
|
||||
}
|
||||
else if (
|
||||
cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled && drops > kMaxRep &&
|
||||
drops < kMaxRepUp)
|
||||
{
|
||||
// This will probably be impossible because this function is not called by mutating
|
||||
// functions, so the Number will already be normalized.
|
||||
drops = kMaxRep;
|
||||
}
|
||||
if (isNegative())
|
||||
drops = -drops;
|
||||
}
|
||||
@@ -628,14 +530,12 @@ doNormalize(
|
||||
int& exponent,
|
||||
MantissaRange::rep const& minMantissa,
|
||||
MantissaRange::rep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
|
||||
bool dropped)
|
||||
{
|
||||
static constexpr auto kMinExponent = Number::kMinExponent;
|
||||
static constexpr auto kMaxExponent = Number::kMaxExponent;
|
||||
auto const repLimit = cuspRoundingFix == MantissaRange::CuspRoundingFix::Disabled
|
||||
? Number::kMaxRep
|
||||
: Number::kMaxRepUp;
|
||||
static constexpr auto kMaxRep = Number::kMaxRep;
|
||||
|
||||
using Guard = Number::Guard;
|
||||
|
||||
@@ -653,7 +553,7 @@ doNormalize(
|
||||
m *= 10;
|
||||
--exponent;
|
||||
}
|
||||
Guard g(minMantissa, maxMantissa, cuspRoundingFix);
|
||||
Guard g;
|
||||
if (negative)
|
||||
g.setNegative();
|
||||
if (dropped)
|
||||
@@ -685,20 +585,27 @@ doNormalize(
|
||||
// 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 > repLimit)
|
||||
if (m > kMaxRep)
|
||||
{
|
||||
if (exponent >= kMaxExponent)
|
||||
throw std::overflow_error("Number::normalize 1.5");
|
||||
g.doDropDigit(m, exponent);
|
||||
}
|
||||
// Before modification, m should be within the min/max range. After
|
||||
// modification, it must be less than repLimit. In other words, the original
|
||||
// value should have been no more than repLimit * 10.
|
||||
// (repLimit * 10 > maxMantissa)
|
||||
XRPL_ASSERT_PARTS(m <= repLimit, "xrpl::doNormalize", "intermediate mantissa fits in limit");
|
||||
// modification, it must be less than kMaxRep. In other words, the original
|
||||
// value should have been no more than kMaxRep * 10.
|
||||
// (kMaxRep * 10 > maxMantissa)
|
||||
XRPL_ASSERT_PARTS(m <= kMaxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64");
|
||||
mantissa = m;
|
||||
|
||||
g.doRoundUp(negative, mantissa, exponent, "Number::normalize 2");
|
||||
g.doRoundUp(
|
||||
negative,
|
||||
mantissa,
|
||||
exponent,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
"Number::normalize 2");
|
||||
XRPL_ASSERT_PARTS(
|
||||
mantissa >= minMantissa && mantissa <= maxMantissa,
|
||||
"xrpl::doNormalize",
|
||||
@@ -713,12 +620,13 @@ Number::normalize<uint128_t>(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled)
|
||||
{
|
||||
// Not used by every compiler version, and thus not necessarily
|
||||
// counted by coverage build
|
||||
// LCOV_EXCL_START
|
||||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false);
|
||||
doNormalize(
|
||||
negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
@@ -730,12 +638,13 @@ Number::normalize<unsigned long long>(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled)
|
||||
{
|
||||
// Not used by every compiler version, and thus not necessarily
|
||||
// counted by coverage build
|
||||
// LCOV_EXCL_START
|
||||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false);
|
||||
doNormalize(
|
||||
negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
@@ -747,27 +656,16 @@ Number::normalize<unsigned long>(
|
||||
int& exponent,
|
||||
internalrep const& minMantissa,
|
||||
internalrep const& maxMantissa,
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFix)
|
||||
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled)
|
||||
{
|
||||
doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFix, false);
|
||||
doNormalize(
|
||||
negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false);
|
||||
}
|
||||
|
||||
void
|
||||
Number::normalize(MantissaRange const& range)
|
||||
{
|
||||
normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFix);
|
||||
}
|
||||
|
||||
void
|
||||
Number::normalize(Guard const& guard)
|
||||
{
|
||||
normalize(
|
||||
negative_,
|
||||
mantissa_,
|
||||
exponent_,
|
||||
guard.minMantissa_,
|
||||
guard.maxMantissa_,
|
||||
guard.cuspRoundingFix_);
|
||||
normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFixEnabled);
|
||||
}
|
||||
|
||||
// Copy the number, but set a new exponent. Because the mantissa doesn't change,
|
||||
@@ -821,19 +719,7 @@ Number::operator+=(Number const& y)
|
||||
bool const yn = y.negative_;
|
||||
uint128_t ym = y.mantissa_;
|
||||
auto ye = y.exponent_;
|
||||
Guard g(kRange);
|
||||
|
||||
auto const& minMantissa = g.minMantissa_;
|
||||
auto const& maxMantissa = g.maxMantissa_;
|
||||
auto const cuspRoundingFix = g.cuspRoundingFix_;
|
||||
|
||||
auto const repLimit =
|
||||
cuspRoundingFix == MantissaRange::CuspRoundingFix::Disabled ? kMaxRep : kMaxRepUp;
|
||||
|
||||
// Bring the exponents of both values into agreement, so the mantissas are on the same scale
|
||||
// and can be added directly together.
|
||||
// Shrink the mantissa and bring the exponent up of the value with the lower exponent. Store any
|
||||
// dropped digits in the Guard.
|
||||
Guard g;
|
||||
if (xe < ye)
|
||||
{
|
||||
if (xn)
|
||||
@@ -853,14 +739,26 @@ Number::operator+=(Number const& y)
|
||||
} while (xe > ye);
|
||||
}
|
||||
|
||||
auto const& range = kRange.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
|
||||
|
||||
if (xn == yn)
|
||||
{
|
||||
xm += ym;
|
||||
if (xm > maxMantissa || xm > repLimit)
|
||||
if (xm > maxMantissa || xm > kMaxRep)
|
||||
{
|
||||
g.doDropDigit(xm, xe);
|
||||
}
|
||||
g.doRoundUp(xn, xm, xe, "Number::addition overflow");
|
||||
g.doRoundUp(
|
||||
xn,
|
||||
xm,
|
||||
xe,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
"Number::addition overflow");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -874,40 +772,19 @@ Number::operator+=(Number const& y)
|
||||
xe = ye;
|
||||
xn = yn;
|
||||
}
|
||||
if (cuspRoundingFix == MantissaRange::CuspRoundingFix::Enabled)
|
||||
while (xm < minMantissa && xm * 10 <= kMaxRep)
|
||||
{
|
||||
// Grow xm/xe and pull digits out of the Guard until it's a little bit larger than
|
||||
// maxMantissa, so that normalize will have enough information to make an accurate
|
||||
// rounding decision, but stop if the Guard empties out, because no rounding will be
|
||||
// necessary. (Normalize will pad it back into range.) Note that if any digits were lost
|
||||
// (xbit), the Guard will never be empty, so xm will get big.
|
||||
auto const upperLimit = static_cast<uint128_t>(minMantissa) * 1000;
|
||||
while (xm < upperLimit && !g.empty())
|
||||
{
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
--xe;
|
||||
}
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
--xe;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Grow xm/xe and pull digits out of the Guard until it's back in range.
|
||||
while (xm < minMantissa && xm * 10 <= repLimit)
|
||||
{
|
||||
xm *= 10;
|
||||
xm -= g.pop();
|
||||
--xe;
|
||||
}
|
||||
}
|
||||
// Round down, based on whether there is any data left in the Guard (depending on
|
||||
// cuspRoundingFix)
|
||||
g.doRoundDown(xn, xm, xe);
|
||||
g.doRoundDown(xn, xm, xe, minMantissa);
|
||||
}
|
||||
|
||||
doNormalize(xn, xm, xe, minMantissa, maxMantissa, cuspRoundingFix, false);
|
||||
negative_ = xn;
|
||||
mantissa_ = static_cast<internalrep>(xm);
|
||||
exponent_ = xe;
|
||||
normalize(range);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -941,28 +818,35 @@ Number::operator*=(Number const& y)
|
||||
auto ze = xe + ye;
|
||||
auto zs = xs * ys;
|
||||
bool zn = (zs == -1);
|
||||
Guard g(kRange);
|
||||
Guard g;
|
||||
if (zn)
|
||||
g.setNegative();
|
||||
|
||||
auto const& maxMantissa = g.maxMantissa_;
|
||||
auto const cuspRoundingFix = g.cuspRoundingFix_;
|
||||
auto const repLimit =
|
||||
cuspRoundingFix == MantissaRange::CuspRoundingFix::Disabled ? kMaxRep : kMaxRepUp;
|
||||
auto const& range = kRange.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
|
||||
|
||||
while (zm > maxMantissa || zm > repLimit)
|
||||
while (zm > maxMantissa || zm > kMaxRep)
|
||||
{
|
||||
g.doDropDigit(zm, ze);
|
||||
}
|
||||
|
||||
xm = static_cast<internalrep>(zm);
|
||||
xe = ze;
|
||||
g.doRoundUp(zn, xm, xe, "Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||||
g.doRoundUp(
|
||||
zn,
|
||||
xm,
|
||||
xe,
|
||||
minMantissa,
|
||||
maxMantissa,
|
||||
cuspRoundingFixEnabled,
|
||||
"Number::multiplication overflow : exponent is " + std::to_string(xe));
|
||||
negative_ = zn;
|
||||
mantissa_ = xm;
|
||||
exponent_ = xe;
|
||||
|
||||
normalize(g);
|
||||
normalize(range);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -998,7 +882,7 @@ Number::operator/=(Number const& y)
|
||||
auto const& range = kRange.get();
|
||||
auto const& minMantissa = range.min;
|
||||
auto const& maxMantissa = range.max;
|
||||
auto const cuspRoundingFix = range.cuspRoundingFix;
|
||||
auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled;
|
||||
|
||||
// Division operates on two large integers (16-digit for small
|
||||
// mantissas, 19-digit for large) using integer math. If the values
|
||||
@@ -1130,14 +1014,14 @@ Number::operator/=(Number const& y)
|
||||
// rounding fix is enabled, flag if there is still
|
||||
// a remainder from stage 2.
|
||||
bool const useTrailingRemainder =
|
||||
cuspRoundingFix != MantissaRange::CuspRoundingFix::Disabled;
|
||||
cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled;
|
||||
if (useTrailingRemainder)
|
||||
{
|
||||
dropped = partialNumerator % dm != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFix, dropped);
|
||||
doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFixEnabled, dropped);
|
||||
negative_ = zp;
|
||||
mantissa_ = static_cast<internalrep>(zm);
|
||||
exponent_ = ze;
|
||||
@@ -1149,11 +1033,9 @@ Number::operator/=(Number const& y)
|
||||
Number::
|
||||
operator rep() const
|
||||
{
|
||||
auto const& range = kRange.get();
|
||||
|
||||
rep drops = mantissa();
|
||||
int offset = exponent();
|
||||
Guard g(kRange);
|
||||
Guard g;
|
||||
if (drops != 0)
|
||||
{
|
||||
if (negative_)
|
||||
@@ -1171,7 +1053,7 @@ operator rep() const
|
||||
throw std::overflow_error("Number::operator rep() overflow");
|
||||
drops *= 10;
|
||||
}
|
||||
g.doRound(drops, range.cuspRoundingFix, "Number::operator rep() rounding overflow");
|
||||
g.doRound(drops, "Number::operator rep() rounding overflow");
|
||||
}
|
||||
return drops;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <boost/endian/conversion.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -45,6 +47,9 @@ STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
|
||||
{
|
||||
MPTID mptID;
|
||||
std::uint32_t sequence = sit.get32();
|
||||
// Preserve the existing LE memcpy result on every host endian.
|
||||
// Wire 04 03 02 01 becomes MPTID bytes 01 02 03 04 on both.
|
||||
sequence = boost::endian::native_to_little(sequence);
|
||||
static_assert(MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
|
||||
memcpy(mptID.data(), &sequence, sizeof(sequence));
|
||||
memcpy(
|
||||
@@ -100,6 +105,9 @@ STIssue::add(Serializer& s) const
|
||||
s.addBitString(noAccount());
|
||||
std::uint32_t sequence = 0;
|
||||
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
|
||||
// Preserve the existing LE ledger bytes on every host endian.
|
||||
// MPTID bytes 01 02 03 04 become wire bytes 04 03 02 01 on both.
|
||||
sequence = boost::endian::native_to_little(sequence);
|
||||
s.add32(sequence);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,20 +43,6 @@ class Number_test : public beast::unit_test::Suite
|
||||
return out;
|
||||
}
|
||||
|
||||
BigInt
|
||||
toBigInt(Number const& n)
|
||||
{
|
||||
BigInt v = n.mantissa();
|
||||
for (int i = 0; i < n.exponent(); ++i)
|
||||
v *= 10;
|
||||
for (int i = 0; i > n.exponent(); --i)
|
||||
{
|
||||
BEAST_EXPECT(v % 10 == 0);
|
||||
v /= 10;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
using dec = boost::multiprecision::cpp_dec_float_50;
|
||||
|
||||
template <class T = dec>
|
||||
@@ -183,37 +169,28 @@ public:
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "test_add " << to_string(scale);
|
||||
|
||||
BEAST_EXPECT(Number::getround() == Number::RoundingMode::ToNearest);
|
||||
|
||||
using Case = std::tuple<Number, Number, Number, int>;
|
||||
auto const cSmall = std::to_array<Case>({
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'066, -15},
|
||||
__LINE__},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{-6'555'555'555'555'555, -29},
|
||||
Number{-1'000'000'000'000'066, -15},
|
||||
__LINE__},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{-9'999'999'999'999'344, -16},
|
||||
__LINE__},
|
||||
{Number{-6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'344, -16},
|
||||
__LINE__},
|
||||
{Number{}, Number{5}, Number{5}, __LINE__},
|
||||
{Number{5}, Number{}, Number{5}, __LINE__},
|
||||
{Number{5'555'555'555'555'555, -32768},
|
||||
Number{-5'555'555'555'555'554, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
{Number{-9'999'999'999'999'999, -31},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'990, -16},
|
||||
__LINE__},
|
||||
});
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'066, -15}},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{-6'555'555'555'555'555, -29},
|
||||
Number{-1'000'000'000'000'066, -15}},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{-9'999'999'999'999'344, -16}},
|
||||
{Number{-6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'344, -16}},
|
||||
{Number{}, Number{5}, Number{5}},
|
||||
{Number{5}, Number{}, Number{5}},
|
||||
{Number{5'555'555'555'555'555, -32768},
|
||||
Number{-5'555'555'555'555'554, -32768},
|
||||
Number{0}},
|
||||
{Number{-9'999'999'999'999'999, -31},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'990, -16}}});
|
||||
auto const cLarge = std::to_array<Case>(
|
||||
// Note that items with extremely large mantissas need to be
|
||||
// calculated, because otherwise they overflow uint64. Items from C
|
||||
@@ -221,57 +198,45 @@ public:
|
||||
{
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'065'556, -18},
|
||||
__LINE__},
|
||||
Number{1'000'000'000'000'065'556, -18}},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{-6'555'555'555'555'555, -29},
|
||||
Number{-1'000'000'000'000'065'556, -18},
|
||||
__LINE__},
|
||||
Number{-1'000'000'000'000'065'556, -18}},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
{Number{-6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{}, Number{5}, Number{5}, __LINE__},
|
||||
{Number{5}, Number{}, Number{5}, __LINE__},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
{Number{}, Number{5}, Number{5}},
|
||||
{Number{5}, Number{}, Number{5}},
|
||||
{Number{5'555'555'555'555'555'000, -32768},
|
||||
Number{-5'555'555'555'555'554'000, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{0}},
|
||||
{Number{-9'999'999'999'999'999, -31},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'990, -16},
|
||||
__LINE__},
|
||||
Number{9'999'999'999'999'990, -16}},
|
||||
// Items from cSmall expanded for the larger mantissa
|
||||
{Number{1'000'000'000'000'000'000, -18},
|
||||
Number{6'555'555'555'555'555'555, -35},
|
||||
Number{1'000'000'000'000'000'066, -18},
|
||||
__LINE__},
|
||||
Number{1'000'000'000'000'000'066, -18}},
|
||||
{Number{-1'000'000'000'000'000'000, -18},
|
||||
Number{-6'555'555'555'555'555'555, -35},
|
||||
Number{-1'000'000'000'000'000'066, -18},
|
||||
__LINE__},
|
||||
Number{-1'000'000'000'000'000'066, -18}},
|
||||
{Number{-1'000'000'000'000'000'000, -18},
|
||||
Number{6'555'555'555'555'555'555, -35},
|
||||
Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
Number{true, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}},
|
||||
{Number{-6'555'555'555'555'555'555, -35},
|
||||
Number{1'000'000'000'000'000'000, -18},
|
||||
Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{}, Number{5}, Number{5}, __LINE__},
|
||||
Number{false, 9'999'999'999'999'999'344ULL, -19, Number::Normalized{}}},
|
||||
{Number{}, Number{5}, Number{5}},
|
||||
{Number{5'555'555'555'555'555'555, -32768},
|
||||
Number{-5'555'555'555'555'555'554, -32768},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{0}},
|
||||
{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{}},
|
||||
__LINE__},
|
||||
{Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}, __LINE__},
|
||||
Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}}},
|
||||
{Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}},
|
||||
// Test extremes
|
||||
{
|
||||
// Each Number operand rounds up, so the actual mantissa is
|
||||
@@ -279,7 +244,6 @@ public:
|
||||
Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}},
|
||||
Number{false, 9'999'999'999'999'999'999ULL, 0, Number::Normalized{}},
|
||||
Number{2, 19},
|
||||
__LINE__,
|
||||
},
|
||||
{
|
||||
// Does not round. Mantissas are going to be > kMaxRep, so if
|
||||
@@ -290,25 +254,21 @@ public:
|
||||
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{}},
|
||||
__LINE__,
|
||||
},
|
||||
});
|
||||
auto const cLargeLegacy = std::to_array<Case>({
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}, __LINE__},
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}},
|
||||
});
|
||||
auto const cLargeCorrected = std::to_array<Case>({
|
||||
{Number{Number::kMaxRep},
|
||||
Number{6, -1},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{(Number::kMaxRep / 10) + 1, 1}},
|
||||
});
|
||||
auto test = [this](auto const& c) {
|
||||
for (auto const& [x, y, z, line] : c)
|
||||
for (auto const& [x, y, z] : c)
|
||||
{
|
||||
auto const result = x + y;
|
||||
std::stringstream ss;
|
||||
ss << x << " + " << y << " = " << result << ". Expected: " << z;
|
||||
expect(result == z, ss.str(), __FILE__, line);
|
||||
BEAST_EXPECTS(result == z, ss.str());
|
||||
}
|
||||
};
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
@@ -348,149 +308,80 @@ public:
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "test_sub " << to_string(scale);
|
||||
|
||||
using Case = std::tuple<Number, Number, Number, int>;
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
auto const cSmall = std::to_array<Case>(
|
||||
{{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{9'999'999'999'999'344, -16},
|
||||
__LINE__},
|
||||
Number{9'999'999'999'999'344, -16}},
|
||||
{Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{-9'999'999'999'999'344, -16},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{-9'999'999'999'999'344, -16}},
|
||||
{Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'001, -15},
|
||||
Number{-1'000'000'000'000'000, -30},
|
||||
__LINE__},
|
||||
Number{-1'000'000'000'000'000, -30}},
|
||||
{Number{1'000'000'000'000'001, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -30},
|
||||
__LINE__}});
|
||||
auto const cLargeAll = std::to_array<Case>(
|
||||
Number{1'000'000'000'000'000, -30}}});
|
||||
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{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
{Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
{Number{1'000'000'000'000'000, -15}, Number{1'000'000'000'000'000, -15}, Number{0}},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'001, -15},
|
||||
Number{-1'000'000'000'000'000, -30},
|
||||
__LINE__},
|
||||
Number{-1'000'000'000'000'000, -30}},
|
||||
{Number{1'000'000'000'000'001, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -30},
|
||||
__LINE__},
|
||||
Number{1'000'000'000'000'000, -30}},
|
||||
// Items from cSmall expanded for the larger mantissa
|
||||
{Number{1'000'000'000'000'000'000, -18},
|
||||
Number{6'555'555'555'555'555'555, -32},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
Number{false, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
{Number{6'555'555'555'555'555'555, -32},
|
||||
Number{1'000'000'000'000'000'000, -18},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}},
|
||||
__LINE__},
|
||||
Number{true, 9'999'999'999'999'344'444ULL, -19, Number::Normalized{}}},
|
||||
{Number{1'000'000'000'000'000'000, -18},
|
||||
Number{1'000'000'000'000'000'000, -18},
|
||||
Number{0},
|
||||
__LINE__},
|
||||
Number{0}},
|
||||
{Number{1'000'000'000'000'000'000, -18},
|
||||
Number{1'000'000'000'000'000'001, -18},
|
||||
Number{-1'000'000'000'000'000'000, -36},
|
||||
__LINE__},
|
||||
Number{-1'000'000'000'000'000'000, -36}},
|
||||
{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},
|
||||
__LINE__},
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}, __LINE__},
|
||||
Number{1'000'000'000'000'000'000, -36}},
|
||||
{Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep - 1}},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1}},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep}},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}},
|
||||
});
|
||||
// Note that items with extremely large mantissas need to be
|
||||
// calculated, because otherwise they overflow uint64. Items from C
|
||||
// with larger mantissa
|
||||
auto const cLargeLegacy = std::to_array<Case>({
|
||||
// Anything larger than kMaxRep rounds up
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 2, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 2, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep},
|
||||
__LINE__},
|
||||
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep}, __LINE__},
|
||||
});
|
||||
auto const cLarge = std::to_array<Case>({
|
||||
// kMaxRep + 1 is below the half-way point, so it rounds down to kMaxRep when the Number
|
||||
// is created.
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{Number::kMaxRep - 1},
|
||||
__LINE__},
|
||||
{Number{false, Number::kMaxRep + 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep - 3},
|
||||
__LINE__},
|
||||
// kMaxRepUp -1 is above the half-way point, so it rounds up to kMaxRepUp when the
|
||||
// Number is created. Subtracting 1 from that rounds up again. A little non-intuitive.
|
||||
{Number{false, Number::kMaxRepUp - 1, 0, Number::Normalized{}},
|
||||
Number{1, 0},
|
||||
Number{(Number::kMaxRep / 10) + 1, 1},
|
||||
__LINE__},
|
||||
// Subtracting 3 gets back down to kMaxRep
|
||||
{Number{false, Number::kMaxRepUp - 1, 0, Number::Normalized{}},
|
||||
Number{3, 0},
|
||||
Number{Number::kMaxRep},
|
||||
__LINE__},
|
||||
// 2^63 is the same as kMaxRep+1
|
||||
{power(2, 63), Number{3, 0}, Number{Number::kMaxRep - 3}, __LINE__},
|
||||
});
|
||||
auto test = [this](auto const& c) {
|
||||
for (auto const& [x, y, z, line] : c)
|
||||
for (auto const& [x, y, z] : c)
|
||||
{
|
||||
auto const result = x - y;
|
||||
std::stringstream ss;
|
||||
ss << x << " - " << y << " = " << result << ". Expected: " << z;
|
||||
expect(result == z, ss.str(), __FILE__, line);
|
||||
BEAST_EXPECTS(result == z, ss.str());
|
||||
}
|
||||
};
|
||||
switch (scale)
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
test(cSmall);
|
||||
break;
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
test(cLargeAll);
|
||||
test(cLargeLegacy);
|
||||
break;
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
test(cLargeAll);
|
||||
test(cLarge);
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECT(false);
|
||||
break;
|
||||
test(cSmall);
|
||||
}
|
||||
else
|
||||
{
|
||||
test(cLarge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1390,38 +1281,38 @@ public:
|
||||
auto const scale = Number::getMantissaScale();
|
||||
testcase << "testToString " << to_string(scale);
|
||||
|
||||
auto test = [this](Number const& n, std::string const& expected, int line) {
|
||||
auto test = [this](Number const& n, std::string const& expected) {
|
||||
auto const result = to_string(n);
|
||||
std::stringstream ss;
|
||||
ss << "to_string(" << result << "). Expected: " << expected;
|
||||
expect(result == expected, ss.str(), __FILE__, line);
|
||||
BEAST_EXPECTS(result == expected, ss.str());
|
||||
};
|
||||
|
||||
test(Number(-2, 0), "-2", __LINE__);
|
||||
test(Number(0, 0), "0", __LINE__);
|
||||
test(Number(2, 0), "2", __LINE__);
|
||||
test(Number(25, -3), "0.025", __LINE__);
|
||||
test(Number(-25, -3), "-0.025", __LINE__);
|
||||
test(Number(25, 1), "250", __LINE__);
|
||||
test(Number(-25, 1), "-250", __LINE__);
|
||||
test(Number(2, 20), "2e20", __LINE__);
|
||||
test(Number(-2, -20), "-2e-20", __LINE__);
|
||||
test(Number(-2, 0), "-2");
|
||||
test(Number(0, 0), "0");
|
||||
test(Number(2, 0), "2");
|
||||
test(Number(25, -3), "0.025");
|
||||
test(Number(-25, -3), "-0.025");
|
||||
test(Number(25, 1), "250");
|
||||
test(Number(-25, 1), "-250");
|
||||
test(Number(2, 20), "2e20");
|
||||
test(Number(-2, -20), "-2e-20");
|
||||
// Test the edges
|
||||
// ((exponent < -(25)) || (exponent > -(5)))))
|
||||
// or ((exponent < -(28)) || (exponent > -(8)))))
|
||||
test(Number(2, -10), "0.0000000002", __LINE__);
|
||||
test(Number(2, -11), "2e-11", __LINE__);
|
||||
test(Number(2, -10), "0.0000000002");
|
||||
test(Number(2, -11), "2e-11");
|
||||
|
||||
test(Number(-2, 10), "-20000000000", __LINE__);
|
||||
test(Number(-2, 11), "-2e11", __LINE__);
|
||||
test(Number(-2, 10), "-20000000000");
|
||||
test(Number(-2, 11), "-2e11");
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
|
||||
test(Number::min(), "1e-32753", __LINE__);
|
||||
test(Number::max(), "9999999999999999e32768", __LINE__);
|
||||
test(Number::lowest(), "-9999999999999999e32768", __LINE__);
|
||||
test(Number::min(), "1e-32753");
|
||||
test(Number::max(), "9999999999999999e32768");
|
||||
test(Number::lowest(), "-9999999999999999e32768");
|
||||
{
|
||||
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
|
||||
|
||||
@@ -1429,136 +1320,62 @@ public:
|
||||
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999);
|
||||
test(
|
||||
Number{false, (maxMantissa * 1000) + 999, -3, Number::Normalized()},
|
||||
"9999999999999999",
|
||||
__LINE__);
|
||||
"9999999999999999");
|
||||
test(
|
||||
Number{true, (maxMantissa * 1000) + 999, -3, Number::Normalized()},
|
||||
"-9999999999999999",
|
||||
__LINE__);
|
||||
"-9999999999999999");
|
||||
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), -3},
|
||||
"9223372036854775",
|
||||
__LINE__);
|
||||
test(Number{std::numeric_limits<std::int64_t>::max(), -3}, "9223372036854775");
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), -3}),
|
||||
"-9223372036854775",
|
||||
__LINE__);
|
||||
"-9223372036854775");
|
||||
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0},
|
||||
"-9223372036854775e3",
|
||||
__LINE__);
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0}, "-9223372036854775e3");
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
|
||||
"9223372036854775e3",
|
||||
__LINE__);
|
||||
"9223372036854775e3");
|
||||
}
|
||||
break;
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
// Test the edges
|
||||
// ((exponent < -(28)) || (exponent > -(8)))))
|
||||
test(Number::min(), "1e-32750", __LINE__);
|
||||
test(Number::max(), "9223372036854775807e32768", __LINE__);
|
||||
test(Number::lowest(), "-9223372036854775807e32768", __LINE__);
|
||||
test(Number::min(), "1e-32750");
|
||||
test(Number::max(), "9223372036854775807e32768");
|
||||
test(Number::lowest(), "-9223372036854775807e32768");
|
||||
{
|
||||
NumberRoundModeGuard const mg(Number::RoundingMode::TowardsZero);
|
||||
|
||||
auto const maxMantissa = Number::maxMantissa();
|
||||
BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL);
|
||||
test(
|
||||
Number{false, maxMantissa, 0, Number::Normalized{}},
|
||||
"9999999999999999990",
|
||||
__LINE__);
|
||||
Number{false, maxMantissa, 0, Number::Normalized{}}, "9999999999999999990");
|
||||
test(
|
||||
Number{true, maxMantissa, 0, Number::Normalized{}},
|
||||
"-9999999999999999990",
|
||||
__LINE__);
|
||||
Number{true, maxMantissa, 0, Number::Normalized{}}, "-9999999999999999990");
|
||||
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0},
|
||||
"9223372036854775807",
|
||||
__LINE__);
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0}, "9223372036854775807");
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0}),
|
||||
"-9223372036854775807",
|
||||
__LINE__);
|
||||
"-9223372036854775807");
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
// Because the absolute value of min() is larger than max(), it
|
||||
// will be scaled down to fit under max(). Since we're
|
||||
// rounding towards zero, the 8 at the end is dropped.
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0},
|
||||
"-9223372036854775800",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
|
||||
"9223372036854775800",
|
||||
__LINE__);
|
||||
break;
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
// Because the absolute value of min() is larger than max(), it
|
||||
// will be rounded down toward max()
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0},
|
||||
"-9223372036854775807",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
|
||||
"9223372036854775807",
|
||||
__LINE__);
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECT(false);
|
||||
break;
|
||||
}
|
||||
// Because the absolute value of min is larger than max, it
|
||||
// will be scaled down to fit under max. Since we're
|
||||
// rounding towards zero, the 8 at the end is dropped.
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::min(), 0},
|
||||
"-9223372036854775800");
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::min(), 0}),
|
||||
"9223372036854775800");
|
||||
}
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
// Rounding to nearest, since the mantissa is bigger than kMaxRep, the 8
|
||||
// will be dropped, and since that is bigger than 5, the result will be
|
||||
// rounded up from 0 to 1.
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1,
|
||||
"9223372036854775810",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
|
||||
"-9223372036854775810",
|
||||
__LINE__);
|
||||
break;
|
||||
case MantissaRange::MantissaScale::Large:
|
||||
// Rounding to nearest, since the mantissa is below the halfway point from
|
||||
// kMaxRep to kMaxRep up, it will be rounded down to kMaxRep
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1,
|
||||
"9223372036854775807",
|
||||
__LINE__);
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
|
||||
"-9223372036854775807",
|
||||
__LINE__);
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECT(false);
|
||||
break;
|
||||
}
|
||||
// Rounding to nearest, since the mantissa is above the halfway point from kMaxRep
|
||||
// to kMaxRep up, it will be rounded up to kMaxRepUp.
|
||||
test(
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0} + 2,
|
||||
"9223372036854775810",
|
||||
__LINE__);
|
||||
Number{std::numeric_limits<std::int64_t>::max(), 0} + 1, "9223372036854775810");
|
||||
test(
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 2),
|
||||
"-9223372036854775810",
|
||||
__LINE__);
|
||||
-(Number{std::numeric_limits<std::int64_t>::max(), 0} + 1),
|
||||
"-9223372036854775810");
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECT(false);
|
||||
@@ -1807,7 +1624,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testEdgeCases()
|
||||
testUpwardRoundsDown()
|
||||
{
|
||||
auto const scale = Number::getMantissaScale();
|
||||
{
|
||||
@@ -1827,18 +1644,21 @@ public:
|
||||
BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue);
|
||||
|
||||
// What Number actually stored.
|
||||
BigInt const storedValue = toBigInt(product);
|
||||
BigInt storedValue = BigInt(product.mantissa());
|
||||
for (int i = 0; i < product.exponent(); ++i)
|
||||
storedValue *= 10;
|
||||
|
||||
BigInt const signedDifference = storedValue - exactProduct;
|
||||
|
||||
log << " a = " << fmt(BigInt(kAValue)) << "\n"
|
||||
log << "\n"
|
||||
<< " a = " << fmt(BigInt(kAValue)) << "\n"
|
||||
<< " b = " << fmt(BigInt(kBValue)) << "\n"
|
||||
<< " exact a*b = " << fmt(exactProduct) << "\n"
|
||||
<< " stored = " << fmt(storedValue) << "\n"
|
||||
<< " stored - exact = " << fmt(signedDifference) << "\n"
|
||||
<< " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n"
|
||||
<< " stored.mantissa = " << product.mantissa() << "\n"
|
||||
<< " stored.exponent = " << product.exponent() << "\n\n";
|
||||
<< " stored.exponent = " << product.exponent() << "\n";
|
||||
log.flush();
|
||||
|
||||
switch (scale)
|
||||
@@ -1911,14 +1731,15 @@ public:
|
||||
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
|
||||
dec const diff = stored - exact;
|
||||
|
||||
log << " a = " << aValue << "\n"
|
||||
log << "\n"
|
||||
<< " a = " << aValue << "\n"
|
||||
<< " b = " << bValue << "\n"
|
||||
<< " exact a/b = " << fmt(exact) << "\n"
|
||||
<< " stored a/b = " << fmt(stored) << "\n"
|
||||
<< " stored - exact = " << fmt(diff)
|
||||
<< " (negative => Upward gave value BELOW truth)\n"
|
||||
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n";
|
||||
log.flush();
|
||||
|
||||
// Upward invariant: stored >= exact. Bug: stored < exact.
|
||||
@@ -1960,14 +1781,15 @@ public:
|
||||
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
|
||||
dec const diff = stored - exact;
|
||||
|
||||
log << " a = " << aValue << "\n"
|
||||
log << "\n"
|
||||
<< " a = " << aValue << "\n"
|
||||
<< " b = " << bValue << "\n"
|
||||
<< " exact a/b = " << fmt(exact) << "\n"
|
||||
<< " stored a/b = " << fmt(stored) << "\n"
|
||||
<< " stored - exact = " << fmt(diff)
|
||||
<< " (positive => Downward gave value ABOVE truth)\n"
|
||||
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n";
|
||||
log.flush();
|
||||
|
||||
// invariant: stored <= exact. Bug: stored > exact.
|
||||
@@ -2016,14 +1838,15 @@ public:
|
||||
dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent());
|
||||
dec const diff = stored - exact;
|
||||
|
||||
log << " a = " << aValue << "\n"
|
||||
log << "\n"
|
||||
<< " a = " << aValue << "\n"
|
||||
<< " b = " << bValue << "\n"
|
||||
<< " exact a/b = " << fmt(exact) << "\n"
|
||||
<< " stored a/b = " << fmt(stored) << "\n"
|
||||
<< " stored - exact = " << fmt(diff)
|
||||
<< " (negative => ToNearest gave value BELOW truth)\n"
|
||||
<< " quotient.mantissa = " << quotient.mantissa() << "\n"
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n\n";
|
||||
<< " quotient.exponent = " << quotient.exponent() << "\n";
|
||||
log.flush();
|
||||
|
||||
// invariant: stored >= exact. Bug: stored < exact.
|
||||
@@ -2046,262 +1869,6 @@ public:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase << "normalization cusp: ToNearest and Downward disagree " << to_string(scale);
|
||||
|
||||
constexpr auto kMaxRep = Number::kMaxRep;
|
||||
|
||||
// Both ToNearest and Downward should round to `below`
|
||||
auto const actual = static_cast<std::uint64_t>(kMaxRep) + 1;
|
||||
Number const below{static_cast<std::int64_t>(kMaxRep), 0};
|
||||
Number const above{
|
||||
false, static_cast<std::uint64_t>(kMaxRep) + 3, 0, Number::Unchecked{}};
|
||||
|
||||
auto construct = [](Number::RoundingMode mode) {
|
||||
NumberRoundModeGuard const roundGuard{mode};
|
||||
return Number(false, actual, 0, Number::Normalized{});
|
||||
};
|
||||
Number const upward = construct(Number::RoundingMode::Upward);
|
||||
|
||||
Number const toNearest = construct(Number::RoundingMode::ToNearest);
|
||||
|
||||
Number const downward = construct(Number::RoundingMode::Downward);
|
||||
|
||||
log << " actual = " << actual << " (kMaxRep + 1)\n"
|
||||
<< " below = " << below << " (kMaxRep, distance 1)\n"
|
||||
<< " above = " << above << " (kMaxRep + 3, distance 2)\n"
|
||||
<< " Upward = " << upward << "\n"
|
||||
<< " ToNearest = " << toNearest << "\n"
|
||||
<< " Downward = " << downward << "\n\n";
|
||||
log.flush();
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
// With the small mantissa, everything rounds up
|
||||
|
||||
// Upward round UP
|
||||
BEAST_EXPECT(upward > above);
|
||||
|
||||
// ToNearest rounds UP when the DOWN neighbor is strictly closer
|
||||
BEAST_EXPECT(toNearest > above);
|
||||
BEAST_EXPECT(toNearest == below);
|
||||
|
||||
// Downward undershoots: it returns a value below `below`
|
||||
BEAST_EXPECT(downward < below);
|
||||
|
||||
// Both should have given the same answer, but they differ
|
||||
BEAST_EXPECT(toNearest > downward);
|
||||
|
||||
break;
|
||||
|
||||
case MantissaRange::MantissaScale::LargeLegacy:
|
||||
// Upward round UP
|
||||
BEAST_EXPECT(upward == above);
|
||||
|
||||
// ToNearest rounds UP when the DOWN neighbor is strictly closer
|
||||
BEAST_EXPECT(toNearest == above);
|
||||
BEAST_EXPECT(toNearest > below);
|
||||
|
||||
// Downward undershoots: it returns a value below `below`
|
||||
BEAST_EXPECT(downward < below);
|
||||
|
||||
// Both should have given the same answer, but they differ
|
||||
BEAST_EXPECT(toNearest > downward);
|
||||
|
||||
break;
|
||||
default:
|
||||
// Covers "Large" and any newly added scales
|
||||
|
||||
// Upward round UP
|
||||
BEAST_EXPECT(upward == above);
|
||||
|
||||
// ToNearest rounds UP when the DOWN neighbor is strictly closer
|
||||
BEAST_EXPECT(toNearest != above);
|
||||
BEAST_EXPECT(toNearest == below);
|
||||
|
||||
// Downward undershoots: it returns a value below `below`
|
||||
BEAST_EXPECT(downward == below);
|
||||
|
||||
// Both should have given the same answer, but they differ
|
||||
BEAST_EXPECT(toNearest == downward);
|
||||
break;
|
||||
}
|
||||
}
|
||||
{
|
||||
testcase << "subtraction rounding " << to_string(scale);
|
||||
|
||||
auto const exp = Number::mantissaLog();
|
||||
Number const a{1LL, exp + 2};
|
||||
Number const b{-(Number{1, exp} + 1)};
|
||||
|
||||
if (scale == MantissaRange::MantissaScale::Small)
|
||||
{
|
||||
BEAST_EXPECT(toBigInt(a) == BigInt{"100000000000000000"});
|
||||
BEAST_EXPECT(toBigInt(b) == BigInt{"-1000000000000001"});
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(toBigInt(a) == BigInt{"100000000000000000000"});
|
||||
BEAST_EXPECT(toBigInt(b) == BigInt{"-1000000000000000001"});
|
||||
}
|
||||
|
||||
auto construct = [&a, &b, this](Number::RoundingMode r) {
|
||||
NumberRoundModeGuard const roundGuard{r};
|
||||
auto const sum = a + b;
|
||||
BigInt const stored = toBigInt(sum);
|
||||
return std::make_pair(r, std::make_pair(stored, sum));
|
||||
};
|
||||
|
||||
auto const bigA = toBigInt(a);
|
||||
auto const bigB = toBigInt(b);
|
||||
BigInt const exact = bigA + bigB;
|
||||
|
||||
auto const sums = [&]() {
|
||||
std::map<Number::RoundingMode, std::pair<BigInt, Number>> sums;
|
||||
sums.emplace(construct(Number::RoundingMode::TowardsZero));
|
||||
sums.emplace(construct(Number::RoundingMode::Upward));
|
||||
sums.emplace(construct(Number::RoundingMode::Downward));
|
||||
sums.emplace(construct(Number::RoundingMode::ToNearest));
|
||||
return sums;
|
||||
}();
|
||||
|
||||
log << " a = " << a << " (" << fmt(bigA) << ")\n b = " << b
|
||||
<< " (" << fmt(bigB) << ")\n exact a + b = " << fmt(exact) << "\n";
|
||||
for (auto const& [r, sum] : sums)
|
||||
{
|
||||
auto const diff = sum.first - exact;
|
||||
auto const rLabel = to_string(r);
|
||||
log << std::string(15 - rLabel.length(), ' ') << rLabel << " = " << fmt(sum.first)
|
||||
<< "\n difference = " << fmt(diff) << "\n";
|
||||
}
|
||||
log << "\n";
|
||||
log.flush();
|
||||
|
||||
for (auto const& [r, sum] : sums)
|
||||
{
|
||||
auto const epsilon = pow10<BigInt>(sum.second.exponent());
|
||||
auto diff = sum.first - exact;
|
||||
auto const rLabel = to_string(r);
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
case MantissaRange::MantissaScale::LargeLegacy: {
|
||||
// Without the fix, all the results but one round up
|
||||
if (r == Number::RoundingMode::Downward)
|
||||
{
|
||||
// Downward works because the Guard sign is negative, and Downward
|
||||
// returns Up instead of Down if negative and there's a remainder,
|
||||
// whereas TowardsZero always returns Down.
|
||||
BEAST_EXPECTS(sum.first < exact, rLabel);
|
||||
BEAST_EXPECTS(diff == -(epsilon - 1), rLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECTS(sum.first > exact, rLabel);
|
||||
BEAST_EXPECTS(diff == 1, rLabel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
BEAST_EXPECT(epsilon == 100);
|
||||
switch (r)
|
||||
{
|
||||
case Number::RoundingMode::Upward:
|
||||
case Number::RoundingMode::ToNearest:
|
||||
BEAST_EXPECTS(sum.first > exact, rLabel);
|
||||
BEAST_EXPECTS(diff == 1, rLabel);
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECTS(sum.first < exact, rLabel);
|
||||
BEAST_EXPECTS(diff == -(epsilon - 1), rLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
auto const offset = 30;
|
||||
testcase << "subtraction rounding offset of " << offset << " " << to_string(scale);
|
||||
|
||||
auto const exp = Number::mantissaLog();
|
||||
Number const a{1LL, exp + offset};
|
||||
Number const b{-1};
|
||||
|
||||
auto construct = [&a, &b, this](Number::RoundingMode r) {
|
||||
NumberRoundModeGuard const roundGuard{r};
|
||||
auto const sum = a + b;
|
||||
BigInt const stored = toBigInt(sum);
|
||||
return std::make_pair(r, std::make_pair(stored, sum));
|
||||
};
|
||||
|
||||
auto const bigA = toBigInt(a);
|
||||
auto const bigB = toBigInt(b);
|
||||
BigInt const exact = bigA + bigB;
|
||||
|
||||
auto const sums = [&]() {
|
||||
std::map<Number::RoundingMode, std::pair<BigInt, Number>> sums;
|
||||
sums.emplace(construct(Number::RoundingMode::TowardsZero));
|
||||
sums.emplace(construct(Number::RoundingMode::Upward));
|
||||
sums.emplace(construct(Number::RoundingMode::Downward));
|
||||
sums.emplace(construct(Number::RoundingMode::ToNearest));
|
||||
return sums;
|
||||
}();
|
||||
|
||||
log << "\n a = " << a << " (" << fmt(bigA) << ")\n b = " << b
|
||||
<< " (" << fmt(bigB) << ")\n exact a + b = " << fmt(exact) << "\n";
|
||||
for (auto const& [r, sum] : sums)
|
||||
{
|
||||
auto const diff = sum.first - exact;
|
||||
auto const rLabel = to_string(r);
|
||||
log << std::string(15 - rLabel.length(), ' ') << rLabel << " = " << fmt(sum.first)
|
||||
<< "\n difference = " << fmt(diff) << "\n";
|
||||
}
|
||||
log.flush();
|
||||
|
||||
switch (scale)
|
||||
{
|
||||
case MantissaRange::MantissaScale::Small:
|
||||
case MantissaRange::MantissaScale::LargeLegacy: {
|
||||
for (auto const& [r, sum] : sums)
|
||||
{
|
||||
if (r == Number::RoundingMode::Downward)
|
||||
{
|
||||
// Downward works because the Guard sign is negative, and Downward
|
||||
// returns Up instead of Down if negative and there's a remainder,
|
||||
// whereas TowardsZero always returns Down.
|
||||
BEAST_EXPECTS(
|
||||
sums.at(Number::RoundingMode::Downward).first < exact,
|
||||
to_string(r));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECTS(sums.at(r).first > exact, to_string(r));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
for (auto const& [r, sum] : sums)
|
||||
{
|
||||
auto const epsilon = pow10<BigInt>(sum.second.exponent());
|
||||
auto diff = sum.first - exact;
|
||||
switch (r)
|
||||
{
|
||||
case Number::RoundingMode::Upward:
|
||||
case Number::RoundingMode::ToNearest:
|
||||
BEAST_EXPECTS(sum.first > exact, to_string(r));
|
||||
BEAST_EXPECTS(diff < epsilon, to_string(r));
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECTS(sum.first < exact, to_string(r));
|
||||
BEAST_EXPECTS(-diff < epsilon, to_string(r));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2332,7 +1899,7 @@ public:
|
||||
testRounding();
|
||||
testInt64();
|
||||
|
||||
testEdgeCases();
|
||||
testUpwardRoundsDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,14 +3,19 @@
|
||||
#include <test/jtx/amount.h> // IWYU pragma: keep
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
class STIssue_test : public beast::unit_test::Suite
|
||||
@@ -137,12 +142,69 @@ public:
|
||||
"000000000000000000000000000000000000000000000002");
|
||||
}
|
||||
|
||||
void
|
||||
testMPTSerialization()
|
||||
{
|
||||
testcase("MPT serialization");
|
||||
using namespace jtx;
|
||||
Account const alice{"alice"};
|
||||
|
||||
struct Vector
|
||||
{
|
||||
std::uint32_t sequence;
|
||||
std::uint32_t legacySequence;
|
||||
};
|
||||
|
||||
// 0x01020304 pins canonical MPTID bytes 01 02 03 04 and
|
||||
// preserved STIssue wire bytes 04 03 02 01 on BE and LE.
|
||||
Vector const vectors[] = {
|
||||
{.sequence = 0x00000001, .legacySequence = 0x01000000},
|
||||
{.sequence = 0x01020304, .legacySequence = 0x04030201},
|
||||
{.sequence = 0xa1b2c3d4, .legacySequence = 0xd4c3b2a1},
|
||||
};
|
||||
|
||||
for (auto const& vector : vectors)
|
||||
{
|
||||
MPTID const mptID = makeMptID(vector.sequence, alice);
|
||||
MPTIssue const issue{mptID};
|
||||
STIssue const stIssue(sfAsset, Asset{issue});
|
||||
|
||||
Serializer actual;
|
||||
stIssue.add(actual);
|
||||
|
||||
// STIssue preserves the existing little-endian validator ledger bytes.
|
||||
Serializer expected;
|
||||
expected.addBitString(alice.id());
|
||||
expected.addBitString(noAccount());
|
||||
{
|
||||
std::array<unsigned char, 4> bytes;
|
||||
auto const seq = boost::endian::native_to_little(vector.sequence);
|
||||
memcpy(bytes.data(), &seq, 4);
|
||||
expected.addRaw(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
BEAST_EXPECTS(strHex(actual) == strHex(expected), strHex(actual));
|
||||
|
||||
// Decoding the preserved wire format must recover the canonical MPTID.
|
||||
SerialIter iter(expected.slice());
|
||||
STIssue const decoded(iter, sfAsset);
|
||||
BEAST_EXPECT(decoded.holds<MPTIssue>());
|
||||
BEAST_EXPECT(decoded.value().get<MPTIssue>().getMptID() == mptID);
|
||||
|
||||
// A decoded ledger value must serialize back to the same bytes.
|
||||
Serializer roundTrip;
|
||||
decoded.add(roundTrip);
|
||||
BEAST_EXPECTS(strHex(roundTrip) == strHex(expected), strHex(roundTrip));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
// compliments other unit tests to ensure complete coverage
|
||||
testConstructor();
|
||||
testCompare();
|
||||
testMPTSerialization();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -53,4 +53,4 @@ foreach(module IN LISTS test_modules)
|
||||
)
|
||||
endforeach()
|
||||
|
||||
gtest_discover_tests(xrpl_tests)
|
||||
gtest_discover_tests(xrpl_tests DISCOVERY_TIMEOUT 60)
|
||||
|
||||
Reference in New Issue
Block a user