Skip to content

parseMinor's '+ 0.5' rounding: the clang-tidy check that was pointing at it no longer does, and the question is still open #678

Description

@Yaraslaut

What happened

#663's fix (PR #676) bounds bankgui::fmt::parseMinor's input so an out-of-range amount is rejected instead of being converted out of range. To do that it splits the expression:

-    return static_cast<std::int64_t>(major * scale + 0.5);
+    const double minor = (major * scale) + 0.5;
+    if (!(minor < kMinorUnitsBound)) { return std::nullopt; }
+    return static_cast<std::int64_t>(minor);

The rounding behaviour is unchanged. What did change is that the line no longer matches bugprone-incorrect-roundings, which was reporting on it before:

$ clang-tidy -p build/clang-debug --extra-arg=-std=c++23 \
      --extra-arg=-Wno-missing-include-dirs --quiet \
      examples/bank/gui/controllers/Format.hpp      # pre-fix content, on 563502ab
/.../examples/bank/gui/controllers/Format.hpp:76:38: error: casting (double + 0.5) to integer leads to incorrect rounding; consider using lround (#include <cmath>) instead [bugprone-incorrect-roundings,-warnings-as-errors]

So a check that was pointing at this arithmetic has stopped pointing at it, without the question it was raising being answered. #663's filer had already flagged + 0.5 and labelled the concern weak; the triage said to decide it separately rather than fold an unmeasured rounding change into a UB fix, which is what PR #676 did. This is that separate ticket.

The substance of the check's complaint

static_cast<int64_t>(x + 0.5) is not round-half-away-from-zero. x + 0.5 is itself a rounded double operation, so a value just below a half can be rounded up to exactly the next integer by the addition and then truncate to the wrong side. The textbook witness is 0.49999999999999994, the largest double below 0.5: 0.49999999999999994 + 0.5 == 1.0 in IEEE-754 double, so the expression yields 1 where correct rounding yields 0. std::llround does not have that behaviour.

Whether any such value is reachable here is the open question, and it is a narrower one than the general case, because x is always major * scale for scale a power of ten and major a value QString::toDouble produced from user text. I have not searched for a reachable witness.

Verification status

What would change the verdict

  • Close as invalid if someone shows the input domain cannot produce a witness — e.g. because QString::toDouble output times a power of ten can never land on a double of that shape. That argument needs writing down either way, since it is the thing the current code silently depends on.
  • Close as valid and fix by replacing the expression with std::llround(major * scale) inside the existing bound, plus a test carrying the witness value.
  • Re-open if bugprone-incorrect-roundings is later re-triggered by an unrelated edit to this function, which would mean the split was load-bearing for the suppression rather than incidental.

Found while working #663 (PR #676). Filed, not folded.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VptDWG2fKr2vBnLSJcgzgW

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ladderSubsystem: ladderbugSomething isn't workingtriage: validWell-framed; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions