Skip to content

fix: Handle rounding just above kMaxRep more accurately#7389

Open
ximinez wants to merge 131 commits into
ximinez/number-round-maxrep-downfrom
ximinez/number-round-maxrep
Open

fix: Handle rounding just above kMaxRep more accurately#7389
ximinez wants to merge 131 commits into
ximinez/number-round-maxrep-downfrom
ximinez/number-round-maxrep

Conversation

@ximinez

@ximinez ximinez commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

High Level Overview of Change

  • Treat values in between kMaxRep (2^63-1) and kMaxRepUp (((kMaxRep / 10) + 1) * 10, which is the next multiple of 10 above kMaxRep) as if
    those values were sequential, and values in between were "fractional".
  • This results in values above the midpoint rounding up to kMaxRepUp,
    and below the midpoint to kMaxRep when rounding to nearest. Other
    rounding modes act along the same lines.

Context of Change

ximinez and others added 30 commits May 6, 2026 22:49
- Add helper function, doDropDigit, to wrap the common pattern:
    push(mantissa % 10);
    mantissa /= 10;
    ++exponent;
- Might have been helpful to catch this issue when developing.
- Refactor the Guard decision in withTxnType into createGuards, which
  lives in Rules.cpp. It is physically located near
  setCurrentTransactionRules, and documented to explain that changes
  need to be synchronized.
- Some EscrowToken tests used a hard-coded list of amendments to
  determine whether to expect large mantissa logic. That ignored the
  effects of fixCleanup3_2_0, especially as applied to the previous fix
  affecting preflight, preclaim, etc.
- Add a helper function, useRulesGuards, which will return the same
  decision as createGuards and setCurrentRulesImpl. Use that helper
  function in the relevant tests.
- Also remove an #include that clang-tidy was complaining about.
…maxrepcusp

* XRPLF/develop:
  release: Set version to 3.3.0-b0 (7280)
  refactor: Rename static constants (7120)
  refactor: Use `isFlag` where possible instead of bitwise math (7278)
  ci: Update XRPLF/actions (7281)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ship it

Review by Claude Sonnet 4.6 · Prompt: V15

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues.

Review by Claude Sonnet 4.6 · Prompt: V15

Comment thread src/libxrpl/basics/Number.cpp Outdated
auto constexpr spread = kMaxRepUp - kMaxRep;
static_assert(spread < 10 && spread > 0);
// This should absolutely be impossible
if constexpr (spread == 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit uncertain why this is needed. Both the static_assert and if constexpr would be evaluated at compile time. Am I overlooking a runtime path that could get here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit uncertain why this is needed. Both the static_assert and if constexpr would be evaluated at compile time. Am I overlooking a runtime path that could get here?

I think I was just being overly paranoid. I'll remove the extra if constexpr.

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues.

Review by Claude Sonnet 4.6 · Prompt: V15

@gregtatcam gregtatcam self-requested a review June 17, 2026 16:58

@gregtatcam gregtatcam left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushOverflow() handles the simple integer cases, but it loses information when there are already guard digits. For example, kMaxRep + 1.6 should round up because it is closer to kMaxRepUp than to kMaxRep. Today the code sees the integer part, kMaxRep + 1, turns that into a fake guard digit 3, and pushes it in front of the real guard digit 6. That makes the value look like .36 of the way through the gap instead of 1.6 / 3, so ToNearest rounds down incorrectly. I think the fix is to make the cusp rounding decision use the integer offset and the existing guard digits together, instead of adding a fake digit on top of the current guard state. Here is the unit test, which demonstrates the issue:

        NumberRoundModeGuard const roundGuard{Number::RoundingMode::ToNearest};

        Number const below{static_cast<std::int64_t>(Number::kMaxRep), 0};
        Number const above{false, Number::kMaxRepUp, 0, Number::Normalized{}};

        // Exact value is kMaxRep + 1.6. Since the gap from kMaxRep to kMaxRepUp is 3,
        // this is closer to kMaxRepUp than kMaxRep.
        Number const actual = below + Number{16, -1};

        std::stringstream ss;
        ss << "kMaxRep + 1.6 rounded to " << actual << ". Expected: " << above;
        BEAST_EXPECTS(actual == above, ss.str()); // fails

Comment thread src/libxrpl/basics/Number.cpp Outdated
Comment thread src/libxrpl/basics/Number.cpp Outdated

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues.

Review by Claude Sonnet 4.6 · Prompt: V15

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues.

Review by Claude Sonnet 4.6 · Prompt: V15

@ximinez

ximinez commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

pushOverflow() handles the simple integer cases, but it loses information when there are already guard digits. For example, kMaxRep + 1.6 should round up because it is closer to kMaxRepUp than to kMaxRep. Today the code sees the integer part, kMaxRep + 1, turns that into a fake guard digit 3, and pushes it in front of the real guard digit 6. That makes the value look like .36 of the way through the gap instead of 1.6 / 3, so ToNearest rounds down incorrectly. I think the fix is to make the cusp rounding decision use the integer offset and the existing guard digits together, instead of adding a fake digit on top of the current guard state. Here is the unit test, which demonstrates the issue:

Oh, daaaaaaannnnnngggggg... Good catch.

ximinez added 2 commits June 18, 2026 13:18
… into ximinez/number-round-maxrep

* XRPLF/ximinez/number-round-maxrep-down:
  clang-tidy: Remove unused header
- Remove unnecessary if constexpr check
- Update scaling static_assert
- Remove unnecessary rounding logic from Number::Guard::doRound()
- Handle fractional rounding between kMaxRep and kMaxRepUp

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean changes

Review by Claude Sonnet 4.6 · Prompt: V15

ximinez added 2 commits June 18, 2026 20:15
- Show that old behavior is not affected, and that the new tests fail
  without them.

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

Review by Claude Sonnet 4.6 · Prompt: V15

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues.

Review by Claude Sonnet 4.6 · Prompt: V15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues.

Review by Claude Sonnet 4.6 · Prompt: V15

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DraftRunCI Normally CI does not run on draft PRs. This opts in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants