Compare commits

...

13 Commits

Author SHA1 Message Date
Vladislav Vysokikh
b1670f5614 clang-format fix 2026-06-05 11:54:14 +01:00
Vladislav Vysokikh
6056045f2e refactor: Use operator<=> for Number comparison 2026-06-05 11:40:52 +01:00
Ed Hennis
a489708326 Revert "Remove the fix: Use this commit to show that tests fail without the fix"
This reverts commit c42a478bb48a578aec670fc1c9ea9dc78208f935.
2026-06-04 19:53:33 -04:00
Ed Hennis
8b6b075397 Remove the fix: Use this commit to show that tests fail without the fix 2026-06-04 19:53:32 -04:00
Ed Hennis
cdcae49fdb Include vector
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-04 19:53:32 -04:00
Ed Hennis
763bba2aba Clean up the relationals Number test for readability
- Separate the int values from the non-int values.
2026-06-04 19:53:31 -04:00
Ed Hennis
35521e1065 Make the Relational tests way more comprehensive
- Test combinatorically with a large variety of values.
2026-06-04 19:53:30 -04:00
Ed Hennis
7f2d18f99e clang-format complains about the equality tests 2026-06-04 19:53:29 -04:00
Ed Hennis
4ca1c6d97f Improve readability 2026-06-04 19:53:28 -04:00
Ed Hennis
52c6652a56 Fix the problem 2026-06-04 19:53:27 -04:00
Ed Hennis
7ed000495c Add test cases to testRelationals to show the problem 2026-06-04 19:53:27 -04:00
Ed Hennis
89c38e6220 Revert "Comment out most Number comparisons"
This reverts commit a28e5777ba.
2026-06-04 19:53:26 -04:00
Ed Hennis
00d46c5423 Comment out most Number comparisons 2026-06-04 19:53:25 -04:00
2 changed files with 129 additions and 49 deletions

View File

@@ -3,6 +3,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <array>
#include <compare>
#include <cstdint>
#include <functional>
#include <limits>
@@ -401,40 +402,41 @@ public:
x.exponent_ == y.exponent_;
}
friend constexpr bool
operator!=(Number const& x, Number const& y) noexcept
// operator!=, >, <=, >= are synthesized from operator== and operator<=>.
friend constexpr std::strong_ordering
operator<=>(Number const& l, Number const& r) noexcept
{
return !(x == y);
}
bool const lneg = l.negative_;
bool const rneg = r.negative_;
friend constexpr bool
operator<(Number const& x, Number const& y) noexcept
{
// If the two amounts have different signs (zero is treated as positive)
// then the comparison is true iff the left is negative.
bool const lneg = x.negative_;
bool const rneg = y.negative_;
// then the negative one is smaller.
if (lneg != rneg)
return lneg;
{
return lneg ? std::strong_ordering::less : std::strong_ordering::greater;
}
// Both have same sign and the left is zero: the right must be
// greater than 0.
if (x.mantissa_ == 0)
return y.mantissa_ > 0;
// Same sign: compare the unsigned magnitudes |a| <=> |b|. For negative
// values the order is reversed (larger magnitude == smaller value), so
// swap the operands. A negative value is never zero, so the zero checks
// below only ever fire for the non-negative case.
Number const& a = lneg ? r : l;
Number const& b = lneg ? l : r;
// Both have same sign, the right is zero and the left is non-zero.
if (y.mantissa_ == 0)
return false;
// A zero mantissa carries a sentinel exponent, so zero has to be handled
// before the exponents can be compared.
if (a.mantissa_ == 0)
{
return b.mantissa_ == 0 ? std::strong_ordering::equal : std::strong_ordering::less;
}
if (b.mantissa_ == 0)
return std::strong_ordering::greater;
// Both have the same sign, compare by exponents:
if (x.exponent_ > y.exponent_)
return lneg;
if (x.exponent_ < y.exponent_)
return !lneg;
// If equal exponents, compare mantissas
return x.mantissa_ < y.mantissa_;
// Both are non-zero and normalized, so the exponent dominates and the
// mantissa breaks ties.
if (auto const cmp = a.exponent_ <=> b.exponent_; cmp != 0)
return cmp;
return a.mantissa_ <=> b.mantissa_;
}
/** Return the sign of the amount */
@@ -449,24 +451,6 @@ public:
[[nodiscard]] Number
truncate() const noexcept;
friend constexpr bool
operator>(Number const& x, Number const& y) noexcept
{
return y < x;
}
friend constexpr bool
operator<=(Number const& x, Number const& y) noexcept
{
return !(y < x);
}
friend constexpr bool
operator>=(Number const& x, Number const& y) noexcept
{
return !(x < y);
}
friend std::ostream&
operator<<(std::ostream& os, Number const& x)
{

View File

@@ -10,6 +10,7 @@
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/number.hpp>
#include <algorithm>
#include <array>
#include <cctype>
#include <cstdint>
@@ -20,6 +21,8 @@
#include <stdexcept>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
namespace xrpl {
@@ -1386,10 +1389,103 @@ public:
testRelationals()
{
testcase << "test_relationals " << to_string(Number::getMantissaScale());
BEAST_EXPECT(!(Number{100} < Number{10}));
BEAST_EXPECT(Number{100} > Number{10});
BEAST_EXPECT(Number{100} >= Number{10});
BEAST_EXPECT(!(Number{100} <= Number{10}));
{
auto test = [this](auto const& nums) {
BEAST_EXPECT(std::ranges::is_sorted(nums));
for (auto iter1 = nums.begin(); iter1 != nums.end(); ++iter1)
{
auto iter2 = iter1;
for (++iter2; iter2 != nums.end(); ++iter2)
{
Number const& smaller = *iter1;
Number const& larger = *iter2;
std::stringstream ss;
ss << smaller << " < " << larger;
auto const str = ss.str();
// The ==/!= operators use a completely different code path than <, etc.
// This helps detect a breakage in one but not the other. It also helps
// verify that the values are being ordered correctly.
BEAST_EXPECTS(smaller != larger, str + " (!=)");
BEAST_EXPECTS(!(smaller == larger), str + " (==)");
// true results using operator< and derived operators
BEAST_EXPECTS(smaller < larger, str + " (<)");
BEAST_EXPECTS(larger > smaller, str + " (>)");
BEAST_EXPECTS(larger >= smaller, str + " (>=)");
BEAST_EXPECTS(smaller <= larger, str + " (<=)");
// false results using operator< and derived operators
BEAST_EXPECTS(!(larger < smaller), str + " (! <)");
BEAST_EXPECTS(!(smaller > larger), str + " (! >)");
BEAST_EXPECTS(!(smaller >= larger), str + " (! >=)");
BEAST_EXPECTS(!(larger <= smaller), str + " (! <=)");
}
}
};
auto const intNums = [this]() {
// Inequality test cases are built from a list of sorted integers
auto const values =
std::to_array<int>({-100, -50, -20, -10, -1, 0, 1, 10, 20, 50, 100});
// Check this list is sorted before converting it to Numbers.
// That way if any of the other tests fail, we know it's because of code and not the
// source data.
BEAST_EXPECT(std::ranges::is_sorted(values));
std::vector<Number> result;
result.reserve(values.size());
for (auto const v : values)
result.emplace_back(v);
return result;
}();
auto const otherNums = std::to_array<Number>({
Number{-5, 100},
Number{-1, 100},
Number{-7, -10},
Number{-2, -10},
Number{0},
Number{2, -10},
Number{7, -10},
Number{1, 100},
Number{5, 100},
});
test(intNums);
test(otherNums);
}
{
// Equality test cases are <Number, __LINE__>. Number will be compared against itself
using Case = std::pair<Number, int>;
auto const c = std::to_array<Case>({
{700, __LINE__},
{50, __LINE__},
{1, __LINE__},
{0, __LINE__},
{-1, __LINE__},
{-30, __LINE__},
{-600, __LINE__},
});
for (auto const& [n, line] : c)
{
auto const str = to_string(n);
// NOLINTBEGIN(misc-redundant-expression) Explicitly testing operators with
// equivalent values
expect(n == n, str + " ==", __FILE__, line);
expect(!(n != n), str + " !=", __FILE__, line);
expect(!(n < n), str + " < ", __FILE__, line);
expect(!(n > n), str + " >", __FILE__, line);
expect(n >= n, str + " >=", __FILE__, line);
expect(n <= n, str + " <=", __FILE__, line);
// NOLINTEND(misc-redundant-expression)
}
}
}
void