Skip to content

2025 08 16 no normal mul#107

Merged
thedavidmeister merged 19 commits intomainfrom
2025-08-16-no-normal-mul
Aug 26, 2025
Merged

2025 08 16 no normal mul#107
thedavidmeister merged 19 commits intomainfrom
2025-08-16-no-normal-mul

Conversation

@thedavidmeister
Copy link
Copy Markdown
Contributor

@thedavidmeister thedavidmeister commented Aug 17, 2025

Motivation

Solution

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

Summary by CodeRabbit

  • New Features
    • Added a higher-precision 512-bit arithmetic path for multiplication and division to improve scaling and range.
  • Bug Fixes
    • Improved zero-handling, sign-safe operations, and overflow-aware scaling to reduce rounding/overflow on extreme inputs.
  • Tests
    • Added and updated exhaustive tests for large-value ops, sign/overflow edge cases, reference comparisons, and new helper behaviors.
  • Documentation
    • Expanded test comments clarifying large-number expectations in power and lookup tests.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 17, 2025

Walkthrough

Adds 512-bit-aware arithmetic to LibDecimalFloatImplementation: introduces mul512 and 512-bit mulDiv, new abs/sign helpers and a lossy reassembly helper, and reworks mul/div to use 512-bit intermediates, scaled-numerator division, and explicit zero handling; tests and a slow reference updated accordingly.

Changes

Cohort / File(s) Summary
Core implementation (512-bit mul/div, abs/sign helpers, scaling)
src/lib/implementation/LibDecimalFloatImplementation.sol
Adds mul512 (high/low 256-bit words) and updates mulDiv to use 512-bit product; adds absUnsignedSignedCoefficient and unabsUnsignedMulOrDivLossy; reworks mul and div to use 512-bit intermediates, threshold-based decimal scaling (1e37/1e18/1e9/1e4 and 1e76/1e75), explicit zero-return constants, and named return values.
Slow test reference (mirror 512-bit scaling approach)
test/lib/LibDecimalFloatSlow.sol
Refactors mulSlow to use absolute values plus mul512+mulDiv scaling; removes prior normalization branches; adds maximizeSlow to iteratively scale mantissa while avoiding overflow; adjusts zero behavior.
Mul unit tests and helpers
test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
Adds checkMul helper and updates/replaces many multiplication tests (including max int256 case), tightens some exponent bounds, and centralizes assertions through helper.
unabsUnsignedMulOrDivLossy tests
test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol
New test suite exercising sign combinations, overflow-triggered scaling (divide-by-10 + exponent increment), and sign application for unabsUnsignedMulOrDivLossy.
Maximize reference test
test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol
Adds testMaximizedReference comparing maximize to LibDecimalFloatSlow.maximizeSlow over bounded inputs.
Pow / pow10 documentation tweaks
test/src/lib/LibDecimalFloat.pow.t.sol, test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol
Expanded explanatory comments in pow/pow10 tests; no functional changes.
Gas snapshot / test-suite rename
.gas-snapshot
Updated gas snapshot data and renamed test-suite class from LibDecimalFloatWithTargetExponentTestLibDecimalFloatImplementationWithTargetExponentTest.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant L as LibDecimalFloatImplementation
  participant H as Abs/Sign Helpers
  participant M as mul512
  participant D as mulDiv

  rect rgb(250,250,255)
  note over L: mul(Ac,Ae,Bc,Be) — 512-bit path
  C->>L: mul(Ac,Ae,Bc,Be)
  alt any coefficient == 0
    L-->>C: MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT
  else
    L->>H: absUnsignedSignedCoefficient(Ac), absUnsignedSignedCoefficient(Bc)
    H-->>L: aAbs, bAbs
    L->>M: mul512(aAbs, bAbs)
    M-->>L: prod1, prod0
    note right of L: derive adjustExponent from prod1 thresholds
    L->>D: mulDiv(aAbs, bAbs, 10^adjustExponent)
    D-->>L: scaledAbsProduct
    L->>H: unabsUnsignedMulOrDivLossy(Ac,Bc,scaledAbsProduct, Ae+Be+adjustExponent)
    H-->>L: signedCoefficient, exponent
    L-->>C: signedCoefficient, exponent
  end
  end
Loading
sequenceDiagram
  autonumber
  participant C as Caller
  participant L as LibDecimalFloatImplementation
  participant H as Abs/Sign Helpers
  participant D as mulDiv

  rect rgb(245,255,245)
  note over L: div(Ac,Ae,Bc,Be) — scaled numerator
  C->>L: div(Ac,Ae,Bc,Be)
  alt Ac == 0
    L-->>C: MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT
  else
    L->>H: absUnsignedSignedCoefficient(Ac), absUnsignedSignedCoefficient(Bc)
    H-->>L: aAbs, bAbs
    note right of L: choose scale 1e76 (fallback 1e75) → adjustExponent
    L->>D: mulDiv(aAbs, scale, bAbs)
    D-->>L: scaledAbsQuotient
    L->>H: unabsUnsignedMulOrDivLossy(Ac,Bc,scaledAbsQuotient, Ae - Be - adjustExponent)
    H-->>L: signedCoefficient, exponent
    L-->>C: signedCoefficient, exponent
  end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • hardyjosh
  • 0xgleb

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-08-16-no-normal-mul

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 87c8c65 and e2d3df9.

📒 Files selected for processing (9)
  • .gas-snapshot (7 hunks)
  • src/error/ErrDecimalFloat.sol (1 hunks)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (5 hunks)
  • test/lib/LibCommonResults.sol (1 hunks)
  • test/src/lib/LibDecimalFloat.mixed.t.sol (1 hunks)
  • test/src/lib/LibDecimalFloat.pow.t.sol (1 hunks)
  • test/src/lib/LibDecimalFloat.pow10.t.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol (3 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#59
File: crates/float/src/lib.rs:233-242
Timestamp: 2025-06-17T10:17:56.205Z
Learning: In the rainlanguage/rain.math.float repository, the maintainer 0xgleb prefers to handle documentation additions and improvements in separate issues rather than inline with feature PRs.
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol:15-29
Timestamp: 2025-08-11T14:32:50.439Z
Learning: In test code for the rain.math.float repository, redundant checks may be intentionally kept for clarity and documentation purposes, even when they could be simplified. The maintainer (thedavidmeister) prefers explicit assertions in test code to make the test's intent clear to future readers, prioritizing readability over conciseness.
📚 Learning: 2025-06-17T10:02:01.394Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#64
File: src/concrete/DecimalFloat.sol:0-0
Timestamp: 2025-06-17T10:02:01.394Z
Learning: In the rain.math.float project, the Float type is designed with a specific bit layout: 224-bit signed coefficient and 32-bit signed exponent, represented as 32 bytes total. The LibDecimalFloat.unpack function returns values that are guaranteed to fit within int224 and int32 ranges due to this internal representation, making explicit range checks before casting unnecessary.

Applied to files:

  • test/src/lib/LibDecimalFloat.pow10.t.sol
📚 Learning: 2025-07-24T04:32:14.171Z
Learnt from: rouzwelt
PR: rainlanguage/rain.math.float#83
File: src/concrete/DecimalFloat.sol:248-251
Timestamp: 2025-07-24T04:32:14.171Z
Learning: In the rainlanguage/rain.math.float project, functions in DecimalFloat.sol that return tuples from LibDecimalFloat calls must unpack the tuple into local variables before returning them (rather than returning directly) to maintain compatibility with Slither static analysis checks.

Applied to files:

  • test/src/lib/LibDecimalFloat.mixed.t.sol
🪛 GitHub Actions: Git is clean
src/lib/implementation/LibDecimalFloatImplementation.sol

[error] 145-145: Forge build failed. Solidity error: 'unchecked' blocks cannot be nested. (Error 1941) at LibDecimalFloatImplementation.sol:145:17. Command: 'nix develop -c forge script ./script/BuildPointers.sol'


[error] 146-146: Forge build failed. Solidity error: Undeclared identifier 'denominator' at LibDecimalFloatImplementation.sol:146:37. Command: 'nix develop -c forge script ./script/BuildPointers.sol'

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
🔇 Additional comments (12)
test/lib/LibCommonResults.sol (1)

4-6: All long decimal literals fit within int256 bounds
Verified via script against INT256_MAX (2^255 – 1, 77 digits):

  • ONES: 76 digits
  • THREES_PACKED: 67 digits
  • THREES: 76 digits

No changes required.

src/error/ErrDecimalFloat.sol (1)

34-35: Add mulDiv overflow error: LGTM

This error integrates cleanly with the 512-bit mulDiv path and keeps the error surface coherent. No issues.

.gas-snapshot (1)

1-305: Snapshot refresh aligns with renamed tests and new coverage

The updated entries reflect the renames (e.g., testDiv1Over3Mixed, testExactLookupsPow10) and added tests (testPows). Large deltas (e.g., Log10Packed) are expected with the 512-bit mulDiv path. No action needed.

test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (1)

35-52: Rename to testExactLookupsPow10: LGTM

Matches the gas snapshot and clarifies scope. No functional changes in this block.

test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol (4)

43-49: LGTM: scale shift to 1e76 for 1/3 paths is coherent.

Using THREES at exponent -76 and its negative mirror aligns with the new fixed scale. Good coverage of sign handling.


59-75: LGTM: OOM-related division expectations reflect the new 1e76 scale.

  • 1e18/3 → THREES at -58 looks consistent.
  • (10,0)/(1e38,-37) → 1 encoded as 1e76 at -76.
  • OOMs/10 and 5/2 with matching exponent adjustments also look correct.

82-100: LGTM: Reciprocal checks for (1/9)/(1/3) and its inverse.

Asserting ONES/THREES at -76 and then validating the reciprocal equals 3e76 at -76 is a solid sanity check under the new scaling.


111-129: LGTM: Unnormalized threes division walk covers scale-insensitivity.

The shift from -39 to -76 via j = -76 - di aligns with the 1e76 scaling and exercises precision across a wide range.

test/src/lib/LibDecimalFloat.mixed.t.sol (2)

5-5: Confirm THREES_PACKED availability and intent.

Switch to THREES_PACKED suggests packed representation testing. Ensure LibCommonResults.sol defines THREES_PACKED for the 1e76 regime and that THREES (unpacked) isn’t required elsewhere in this suite.


13-26: LGTM: Mixed div+mul updated to new scaling and packed expectations.

  • Div: (1/3) → THREES_PACKED @ -67 is in line with the mixed test’s encoding.
  • Mul: The large coefficient and -46 exponent expectation look consistent with the upstream scale and packing.
src/lib/implementation/LibDecimalFloatImplementation.sol (2)

288-372: mulDiv implementation is solid and standard; import and overflow path are appropriate.

Good use of 512-by-256 division with memory-safe Yul and a clear overflow revert path via MulDivOverflow. No issues spotted.


544-546: LGTM: inv delegates to div at the new fixed scale.

This keeps inversion consistent with the 1e76 division strategy.

Comment thread src/error/ErrDecimalFloat.sol Outdated
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol Outdated
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol Outdated
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol Outdated
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol Outdated
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol Outdated
Comment thread test/lib/LibCommonResults.sol
Comment thread test/src/lib/LibDecimalFloat.pow.t.sol
Comment thread test/src/lib/LibDecimalFloat.pow10.t.sol
@thedavidmeister thedavidmeister changed the base branch from main to 2025-08-15-minimize-denominator-div August 19, 2025 15:19
@thedavidmeister thedavidmeister changed the base branch from 2025-08-15-minimize-denominator-div to main August 20, 2025 12:29
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/lib/implementation/LibDecimalFloatImplementation.sol (1)

137-137: Fix typo in comment: "aboslute" → "absolute"

-            // mulDiv only works with unsigned integers, so get the aboslute
+            // mulDiv only works with unsigned integers, so get the absolute
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e2d3df9 and 18f7d73.

📒 Files selected for processing (3)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (3 hunks)
  • test/lib/LibDecimalFloatSlow.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T13:52:43.369Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#105
File: src/lib/implementation/LibDecimalFloatImplementation.sol:248-335
Timestamp: 2025-08-18T13:52:43.369Z
Learning: The codebase has a policy of not modifying external code, such as mulDiv implementations adopted from standard libraries like OpenZeppelin, PRB Math, and Solady.

Applied to files:

  • src/lib/implementation/LibDecimalFloatImplementation.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
  • GitHub Check: git-clean
🔇 Additional comments (8)
src/lib/implementation/LibDecimalFloatImplementation.sol (6)

97-109: LGTM! Clean helper for safe absolute value conversion.

The function correctly handles the edge case of type(int256).min which cannot be negated directly.


111-119: LGTM! Correct sign restoration logic.

The XOR-based sign check and conditional negation correctly implements sign restoration for multiplication and division results.


127-176: Robust 512-bit multiplication implementation with proper overflow handling.

The implementation correctly:

  1. Short-circuits for zero inputs
  2. Converts to absolute values for unsigned arithmetic
  3. Uses 512-bit multiplication to handle overflow
  4. Scales down the high word progressively to find the right denominator
  5. Applies the correct sign to the result

The progressive scaling approach (1e37 → 1e18 → 1e9 → 1e4 → single digits) is efficient and avoids unnecessary precision loss.


234-267: Clean division implementation with 1e76 scaling.

The division correctly:

  1. Returns maximized zero for zero dividend
  2. Maximizes operands to e75/e76 range
  3. Uses 1e76 scaling for precision
  4. Handles sign correctly via the helper function

The approach ensures the division won't overflow in mulDiv by keeping operands in the e75/e76 range.


269-281: LGTM! Correctly extracted mul512 from standard mulDiv pattern.

This is the standard 512-bit multiplication technique using the Chinese Remainder Theorem, correctly factored out for reuse.


285-286: LGTM! Updated mulDiv to use the extracted mul512.

Good refactoring to avoid code duplication by reusing the mul512 helper.

test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (1)

63-71: LGTM! Good boundary test for maximum int256 multiplication.

The test correctly validates the edge case of multiplying two maximum int256 values. The expected result is properly computed and the comment indicates external validation via Desmos.

test/lib/LibDecimalFloatSlow.sol (1)

18-20: LGTM! Consistent zero handling with the main implementation.

The test implementation correctly returns (0, 0) for zero inputs, matching the main library's behavior of returning maximized zero constants.

Comment thread test/lib/LibDecimalFloatSlow.sol
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (2)
src/lib/implementation/LibDecimalFloatImplementation.sol (2)

152-156: Typo in comment: “aboslute” → “absolute”.

-            // mulDiv only works with unsigned integers, so get the aboslute
+            // mulDiv only works with unsigned integers, so get the absolute

258-265: Add explicit zero-divisor guard for div.

Currently a zero divisor flows into mulDiv and reverts with MulDivOverflow, which is misleading. Add an explicit revert (matching your error model) before mulDiv.

-            uint256 signedCoefficientBAbs = absUnsignedSignedCoefficient(signedCoefficientB);
+            uint256 signedCoefficientBAbs = absUnsignedSignedCoefficient(signedCoefficientB);
+            if (signedCoefficientBAbs == 0) {
+                // Prefer a dedicated error over mulDiv’s overflow revert.
+                // Define DivisionByZero in ErrDecimalFloat.sol and import it here.
+                revert DivisionByZero();
+            }

I can wire up a DivisionByZero error in ErrDecimalFloat.sol and update imports if you want.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 18f7d73 and 2083180.

📒 Files selected for processing (5)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (3 hunks)
  • test/lib/LibDecimalFloatSlow.sol (1 hunks)
  • test/src/lib/LibDecimalFloat.pow.t.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (3 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-24T04:32:14.171Z
Learnt from: rouzwelt
PR: rainlanguage/rain.math.float#83
File: src/concrete/DecimalFloat.sol:248-251
Timestamp: 2025-07-24T04:32:14.171Z
Learning: In the rainlanguage/rain.math.float project, functions in DecimalFloat.sol that return tuples from LibDecimalFloat calls must unpack the tuple into local variables before returning them (rather than returning directly) to maintain compatibility with Slither static analysis checks.

Applied to files:

  • test/lib/LibDecimalFloatSlow.sol
📚 Learning: 2025-08-18T13:52:43.369Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#105
File: src/lib/implementation/LibDecimalFloatImplementation.sol:248-335
Timestamp: 2025-08-18T13:52:43.369Z
Learning: The codebase has a policy of not modifying external code, such as mulDiv implementations adopted from standard libraries like OpenZeppelin, PRB Math, and Solady.

Applied to files:

  • src/lib/implementation/LibDecimalFloatImplementation.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: git-clean
🔇 Additional comments (5)
test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (3)

15-27: Helper looks good; improves test readability.


29-35: Good targeted edge-case; representation is correct.


129-131: Tighten bounds without over-constraining fuzz space.

You’re bounding to EXPONENT_MAX/2, which is safe but unnecessarily shrinks coverage. Aim for: exponentA + exponentB + ≤ 77 won’t overflow. One pragmatic bound is EXPONENT_MAX - 100 (and +100 for min).

-        exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX / 2);
-        exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX / 2);
+        exponentA = bound(exponentA, EXPONENT_MIN + 100, EXPONENT_MAX - 100);
+        exponentB = bound(exponentB, EXPONENT_MIN + 100, EXPONENT_MAX - 100);

Likely an incorrect or invalid review comment.

src/lib/implementation/LibDecimalFloatImplementation.sol (2)

287-300: Good extraction of mul512 for reuse.

Abstracting the 512-bit multiply improves clarity and reuse, while keeping mulDiv semantics intact per project policy.


301-379: mulDiv integration looks correct.

Using mul512(high, low) aligns with the standard OpenZeppelin/PRB approach; non-overflow and overflow paths match expectations.

Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread test/lib/LibDecimalFloatSlow.sol
Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol Outdated
Comment thread test/src/lib/LibDecimalFloat.pow.t.sol
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/implementation/LibDecimalFloatImplementation.sol (1)

247-286: Division path: add explicit zero-divisor guard (clearer error semantics)

With signedCoefficientBAbs == 0, the call to mulDiv currently reverts (either panic div-by-zero in the non-overflow branch or MulDivOverflow(x,y,0) when prod1 >= 0). Prefer an explicit check with a dedicated revert to make error semantics predictable and easier to test.

Example:

             uint256 signedCoefficientAAbs = absUnsignedSignedCoefficient(signedCoefficientA);
             uint256 signedCoefficientBAbs = absUnsignedSignedCoefficient(signedCoefficientB);

+            // Explicit division-by-zero guard for clearer error semantics.
+            if (signedCoefficientBAbs == 0) {
+                // Choose an error consistent with your error model; this library imports MulDivOverflow,
+                // but a dedicated DivisionByZero error would be ideal.
+                revert MulDivOverflow(signedCoefficientAAbs, scale, signedCoefficientBAbs);
+            }

If you want a dedicated error DivisionByZero(); I can wire that end-to-end and update tests.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 18f7d73 and 2083180.

📒 Files selected for processing (5)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (3 hunks)
  • test/lib/LibDecimalFloatSlow.sol (1 hunks)
  • test/src/lib/LibDecimalFloat.pow.t.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (3 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T13:52:43.369Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#105
File: src/lib/implementation/LibDecimalFloatImplementation.sol:248-335
Timestamp: 2025-08-18T13:52:43.369Z
Learning: The codebase has a policy of not modifying external code, such as mulDiv implementations adopted from standard libraries like OpenZeppelin, PRB Math, and Solady.

Applied to files:

  • src/lib/implementation/LibDecimalFloatImplementation.sol
🔇 Additional comments (7)
src/lib/implementation/LibDecimalFloatImplementation.sol (2)

287-299: mul512 extraction: good reuse, unchanged semantics

The extracted mul512 matches the canonical CRT-based 512-bit multiply used in mulDiv. This aligns with the repo’s policy of not modifying external algorithms while enabling reuse.


304-304: mulDiv now delegates to mul512: LGTM

Refactor preserves behavior and readability. The rest of mulDiv remains the standard Remco Bloemen implementation.

test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (1)

66-67: Comment improvement acknowledged

The expanded note clarifies provenance and expected magnitude for 10^1234.56789. No code changes; test intent is clearer.

test/src/lib/LibDecimalFloat.pow.t.sol (1)

50-51: Documentation-only: OK

The added comments document the large-exponent case; no behavioral change. Consistent with surrounding tests.

test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (3)

15-27: Helper-based assertions: concise and readable

The checkMul helper tightens assertions and reduces duplication. Signature matches the updated mul return form.


33-34: Sanity check case reflects new scaling path

The 1.3979×0.5 example exercising e76/e66 scale is a good targeted regression for the 512-bit scaling.


129-131: Exponent bounds tightened: good

Bounding to EXPONENT_MAX/2 for non-reverting fuzz is a sensible narrowing given the new path. Keeps test signal strong.

Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread test/lib/LibDecimalFloatSlow.sol
Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
@thedavidmeister thedavidmeister self-assigned this Aug 20, 2025
thedavidmeister and others added 3 commits August 21, 2025 11:39
…0.t.sol

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol (3)

16-30: Simplify and make sign handling explicit in isMaximized

Current logic relies on signed division heuristics; use an absolute-value check for clarity and to avoid any sign-edge confusion.

Apply this diff:

 function isMaximized(int256 signedCoefficient, int256 exponent) internal pure returns (bool) {
   if (signedCoefficient == 0) {
     return exponent == MAXIMIZED_ZERO_EXPONENT && signedCoefficient == MAXIMIZED_ZERO_SIGNED_COEFFICIENT;
   }
-
-  if (signedCoefficient / 1e76 != 0) {
-      return true;
-  }
-
-  if (signedCoefficient / 1e75 == 0) {
-      return false;
-  }
-
-  return true;
+  // A number is "maximized" iff |coefficient| >= 1e75.
+  uint256 absCoef = LibDecimalFloatImplementation.absUnsignedSignedCoefficient(signedCoefficient);
+  return absCoef >= 1e75;
 }

32-38: Misleading comment: this test fuzzes arbitrary inputs, not “normalized” ones

The function exercises maximize() over arbitrary inputs; consider updating the comment to avoid confusion.

Apply this diff:

-    /// Every normalized number is maximized.
+    /// maximize() should always return a maximized pair for any input within exponent bounds.

52-64: Prefer integer scientific notation over fractional literals

Fractional literals like 4.2e76/8.8e75 are compile-time rationals that reduce to integers here, but using integer scientific notation is clearer and avoids accidental non-integer rationals in future edits.

Apply this diff:

-        checkMaximized(42, 0, 4.2e76, -75);
-        checkMaximized(42e74, -74, 4.2e76, -75);
-        checkMaximized(4.2e76, -75, 4.2e76, -75);
-        checkMaximized(88, 0, 8.8e75, -74);
-        checkMaximized(88e74, -74, 8.8e75, -74);
+        checkMaximized(42, 0, 42e75, -75);
+        checkMaximized(42e74, -74, 42e75, -75);
+        checkMaximized(42e75, -75, 42e75, -75);
+        checkMaximized(88, 0, 88e74, -74);
+        checkMaximized(88e74, -74, 88e74, -74);
♻️ Duplicate comments (4)
test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (3)

66-68: Provenance and rounding intent clarified — good fix.

The added comments address prior feedback by citing WolframAlpha and stating the truncation intent for the expectation.


59-60: Comment contradicts the asserted value; clarify intent.

The comment states 10^-0.3 ≈ 0.50118723362, but the expected coefficient encodes ≈ 0.50125313283208… (200/399), i.e., the reciprocal of truncated 10^0.3 (1995/1000). Make the comment match the asserted value.

Apply this diff:

-        // 10^-0.3 = 0.50118723362
+        // NOTE: Expect reciprocal of truncated 10^0.3 (1995/1000) => 200/399 ≈ 0.501253132832080200501...

66-68: Pin the exact query and (optionally) retrieval date for reproducibility.

Small doc hardening so future readers can reproduce the reference precisely.

Apply this diff to tighten the comments:

-        // Source: WolframAlpha (10^1234.56789). Full-precision mantissa below is reference only.
+        // Source: WolframAlpha — query: 10^(1234.56789). Full-precision mantissa below is reference only.
         // 10^1234.56789 ≈ 3.69734519948141829344363446069899674413404079870780463377448004811497051583459624644095897334743494067266231680935688534678044962972813724… × 10^1234
-        // Test expectation uses a truncated mantissa (3.6979e76) consistent with library rounding.
+        // Test expectation uses a truncated mantissa (3.6979e76) consistent with the library's mantissa precision/rounding.
+        // (Optional) Retrieved: YYYY-MM-DD
test/lib/LibDecimalFloatSlow.sol (1)

29-39: Potential overflow: 10adjustExponent can wrap if adjustExponent > 77**

You derive adjustExponent from the high limb’s decimal digits. For int256-domain inputs, prod1 < 2^254 ⇒ digits ≤ 77, so 10^adjustExponent fits in uint256. However, this is not enforced; broadening inputs in the future could silently wrap 10^adjustExponent modulo 2^256 inside unchecked, corrupting scaling.

Apply this diff to document and guard the bound:

             (uint256 prod1,) = LibDecimalFloatImplementation.mul512(signedCoefficientAAbs, signedCoefficientBAbs);
 
             uint256 adjustExponent = 0;
             while (prod1 > 0) {
                 prod1 /= 10;
                 adjustExponent++;
             }
+            // Bound: For int256-domain inputs, the high 256-bit limb of the 512-bit product is < 2^254,
+            // hence its decimal digits (adjustExponent) are ≤ 77. Guard against future broadened inputs.
+            assert(adjustExponent <= 77);
 
             uint256 signedCoefficientAbs = LibDecimalFloatImplementation.mulDiv(
                 signedCoefficientAAbs, signedCoefficientBAbs, uint256(10) ** adjustExponent
             );

If you prefer fail-soft instead of assert, clamp adjustExponent = 77 before computing 10**adjustExponent to avoid wrap.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2083180 and 1741771.

📒 Files selected for processing (3)
  • test/lib/LibDecimalFloatSlow.sol (2 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol (2 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: git-clean
🔇 Additional comments (5)
test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol (2)

13-13: Good addition: dedicated slow reference import for cross-checks

Importing LibDecimalFloatSlow enables oracle-style validation without coupling to prod internals. LGTM.


84-93: Reference test adds strong oracle; consider pinning edge exponents

Great to see a reference-based assert. To harden this further, add explicit cases for exponents near EXPONENT_MIN/EXPONENT_MAX to ensure maximizeSlow parity when repeated scaling would push beyond bounds.

If helpful, I can add a small table-driven test covering exponent ∈ {EXPONENT_MIN, EXPONENT_MIN+1, -1, 0, 1, EXPONENT_MAX-1, EXPONENT_MAX} with representative coefficients (0, 1, 1e37, 1e75, type(int256).max, type(int256).min).

test/lib/LibDecimalFloatSlow.sol (3)

18-20: Zero short-circuit consistent with canonical zero representation

Returning (0, 0) here matches the tests’ expectations and simplifies downstream logic. LGTM.


22-22: Unchecked exponent addition may wrap; verify callers bound inputs

exponentA + exponentB occurs under unchecked and can wrap for adversarial inputs. If callers don’t already bound exponents tightly (as tests do), consider adding an assert to catch overflow in the reference path.

I can add a defensive assert here (e.g., checking sign-consistent sum) or extend fuzz bounds in tests to prove no wrap occurs. Want me to push that?


41-46: Sign application and exponent finalization handled correctly

Using unabsUnsignedMulOrDivLossy to apply sign and finalize exponent matches the production pathway and resolves earlier sign/adjustExponent omissions. LGTM.

Comment thread test/lib/LibDecimalFloatSlow.sol
Comment thread test/lib/LibDecimalFloatSlow.sol
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (1)
test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (1)

87-95: Avoid hard-coding the massive mantissa; derive expected via the slow path.

Inline decimal-rational literals this large are fragile and can drift across compiler/tooling changes. Compute the expected once using LibDecimalFloatSlow.mulSlow.

Apply this diff:

 function testMulMaxSignedCoefficient() external pure {
-        checkMul(
-            type(int256).max,
-            0,
-            type(int256).max,
-            0,
-            int256(3.3519519824856492748935062495514615318698414551480983444308903609304410075182e76),
-            77
-        );
+        (int256 expectedSignedCoefficient, int256 expectedExponent) =
+            LibDecimalFloatSlow.mulSlow(type(int256).max, 0, type(int256).max, 0);
+        checkMul(
+            type(int256).max,
+            0,
+            type(int256).max,
+            0,
+            expectedSignedCoefficient,
+            expectedExponent
+        );
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1741771 and 294b04a.

📒 Files selected for processing (2)
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (3 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-11T14:30:48.562Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/LibDecimalFloat.ceil.t.sol:43-50
Timestamp: 2025-08-11T14:30:48.562Z
Learning: When reviewing Solidity test files using Forge, verify the actual mutability of helper functions like `bound()` before suggesting changes to function mutability specifiers, as Forge implements many test utilities as pure functions.

Applied to files:

  • test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
📚 Learning: 2025-08-11T14:32:50.439Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol:15-29
Timestamp: 2025-08-11T14:32:50.439Z
Learning: In test code for the rain.math.float repository, redundant checks may be intentionally kept for clarity and documentation purposes, even when they could be simplified. The maintainer (thedavidmeister) prefers explicit assertions in test code to make the test's intent clear to future readers, prioritizing readability over conciseness.

Applied to files:

  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
🔇 Additional comments (12)
test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (8)

15-27: Good reusable harness for asserting mul results.

Concise, pure, and with clear assertion messages. This reduces duplication across tests and keeps expectations tight.


29-42: Sign handling cases look correct and self-documenting.

Covers (-1)*(-1), (-1)1, and 1(-1) explicitly; good clarity for future readers.


75-79: Identity case reads clean and correct.

Covers the simplest non-zero baseline.


81-85: Deterministic large-int multiply is correct and helpful for regression.

This is a good anchor for “no-rescale” paths.


100-105: Good fuzz coverage with bounded exponent sum.

Constraining to [-127, 127] guards against exponent-sum overflow and matches typical decimal exponent limits.


107-110: LGTM: 1e18 × 1e-19 normalization case.

Compact check that exercises downscaling behavior to negative exponent.


112-120: Gas-only probes are minimal and appropriate.

Discarding the result keeps the gas snapshot focused on mul; no unnecessary assertions.


122-137: Cross-check against slow path with bounded exponents is solid.

Bounding to EXPONENT_MAX/2 avoids sum overflow; alignment with LibDecimalFloatSlow is exactly what we want for confidence in the new 512-bit path.

Is the EXPONENT_MAX/2 bound chosen solely to ensure exponentA+exponentB never exceeds EXPONENT_MAX after normalization? If so, consider a brief comment to lock that rationale in place.

test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol (4)

1-7: Headers and imports are correct.

Matches the rest of the suite’s pragma and forge-std usage.


10-23: Positive×positive without overflow: expectations are precise.

Bounds on a, b, c ensure safe int256 casts; sign and exponent passthrough are asserted cleanly.


44-58: Negative×negative without overflow correctly yields positive sign.

Good mirror of the positive-path expectations.


81-96: Mixed sign without overflow: negative sign expectation is correct.

Covers the asymmetric sign application cleanly.

Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/implementation/LibDecimalFloatImplementation.sol (1)

304-379: Keep mulDiv as an exact upstream copy or clearly mark the minimal deviation.

Replacing the initial assembly with a mul512 call is a benign refactor but technically diverges from upstream. If the project policy is “no local modifications,” consider inlining the 512-bit multiply again, or keep the current form but add a prominent “minimal deviation” comment.

To check for drift against upstream in CI, consider a lightweight guard (e.g., a script that compares this body to a pinned upstream version ignoring whitespace).

♻️ Duplicate comments (3)
src/lib/implementation/LibDecimalFloatImplementation.sol (3)

247-285: Division: add explicit zero-divisor guard (and document 0/0 semantics).

mulDiv reverts with MulDivOverflow on denominator == 0, but callers get a non-obvious error. Add an explicit guard for signedCoefficientB == 0 with a dedicated error, or document that MulDivOverflow doubles as DivisionByZero. Also confirm intended behavior for 0/0 (currently returns 0).

+    /// Reverts when dividing by zero.
+    error DivisionByZero();
@@
-        if (signedCoefficientA == 0) {
+        if (signedCoefficientA == 0) {
             signedCoefficient = MAXIMIZED_ZERO_SIGNED_COEFFICIENT;
             exponent = MAXIMIZED_ZERO_EXPONENT;
         } else {
+            if (signedCoefficientB == 0) {
+                revert DivisionByZero();
+            }
             // Move both coefficients into the e75/e76 range, so that the result

To verify tests cover these cases, run:

#!/bin/bash
# Division by zero / 0÷0 coverage
rg -nP --type=sol -C2 'div\s*\('
rg -nP --type=sol -C2 'DivisionByZero|divide by zero|0\s*/\s*0'

97-109: Unsigned abs helper is correct; add NatSpec clarifying the 2^255 case (naming nit optional).

The implementation handles int256.min correctly. Add a brief NatSpec comment so future readers know it returns 2^255 for int256.min and is intended for unsigned mul/div paths. Optional: a shorter name (e.g., absUintCoefficient) improves readability.

-    function absUnsignedSignedCoefficient(int256 signedCoefficient) internal pure returns (uint256) {
+    /// @dev Returns |signedCoefficient| as uint256. For int256.min, returns 2^255.
+    /// Intended for unsigned mul/div intermediates.
+    function absUnsignedSignedCoefficient(int256 signedCoefficient) internal pure returns (uint256) {

111-132: Fix 2^255 negative-cast overflow and clarify sign test precedence.

When a^b is negative and signedCoefficientAbs == 2^255, casting to int256 overflows. Also, parenthesize the sign test to avoid precedence ambiguity.

-            // Need to minus the coefficient because a and b had different signs.
-            if (a ^ b < 0) {
-                if (signedCoefficientAbs > uint256(type(int256).max) + 1) {
-                    return (-int256(signedCoefficientAbs / 10), exponent + 1);
-                } else {
-                    return (-int256(signedCoefficientAbs), exponent);
-                }
-            } else {
-                if (signedCoefficientAbs > uint256(type(int256).max)) {
-                    return (int256(signedCoefficientAbs / 10), exponent + 1);
-                } else {
-                    return (int256(signedCoefficientAbs), exponent);
-                }
-            }
+            // Apply sign once magnitude is known.
+            bool negative = (a ^ b) < 0;
+            uint256 maxNegMag = uint256(type(int256).max) + 1; // 2^255
+            if (negative) {
+                if (signedCoefficientAbs > maxNegMag) {
+                    return (-int256(signedCoefficientAbs / 10), exponent + 1);
+                } else if (signedCoefficientAbs == maxNegMag) {
+                    return (type(int256).min, exponent);
+                } else {
+                    return (-int256(signedCoefficientAbs), exponent);
+                }
+            } else {
+                if (signedCoefficientAbs > uint256(type(int256).max)) {
+                    return (int256(signedCoefficientAbs / 10), exponent + 1);
+                } else {
+                    return (int256(signedCoefficientAbs), exponent);
+                }
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 294b04a and 37e60df.

📒 Files selected for processing (1)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-21T18:03:40.292Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#107
File: test/lib/LibDecimalFloatSlow.sol:37-45
Timestamp: 2025-08-21T18:03:40.292Z
Learning: In test/lib/LibDecimalFloatSlow.sol, the "slow" implementation is intentionally different from the production implementation to serve as an independent reference for fuzzing tests. The goal is to have two different approaches (expensive loops vs optimized jumps) that produce equivalent results, not identical implementations.

Applied to files:

  • src/lib/implementation/LibDecimalFloatImplementation.sol
📚 Learning: 2025-08-18T13:52:43.369Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#105
File: src/lib/implementation/LibDecimalFloatImplementation.sol:248-335
Timestamp: 2025-08-18T13:52:43.369Z
Learning: The codebase has a policy of not modifying external code, such as mulDiv implementations adopted from standard libraries like OpenZeppelin, PRB Math, and Solady.

Applied to files:

  • src/lib/implementation/LibDecimalFloatImplementation.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
🔇 Additional comments (2)
src/lib/implementation/LibDecimalFloatImplementation.sol (2)

140-149: Zero short-circuit is clear and correct.

Returning the maximized zero tuple avoids unnecessary work and keeps semantics consistent across ops.


185-191: mul result sign/magnitude application defers correctly to the shared helper.

Good reuse: the lossy sign application and single-OOM downscale ensure any 256-bit magnitude fits int256 after at most one divide-by-10.

Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/implementation/LibDecimalFloatImplementation.sol (1)

255-291: Division by zero path: consider explicit guard for clearer error semantics.

Currently B == 0 bubbles up as MulDivOverflow(…, denominator=0). If you want a dedicated error, add a zero-divisor check before mulDiv.

Would you prefer a new error (e.g., DivisionByZero) in ErrDecimalFloat.sol, or keep MulDivOverflow for B==0 to avoid a breaking change?

♻️ Duplicate comments (10)
src/lib/implementation/LibDecimalFloatImplementation.sol (3)

97-109: Add NatSpec clarifying 2^255 mapping for absUnsignedSignedCoefficient.

Document that int256.min maps to 2^255 as uint for unsigned mul/div paths; optional: shorter name like absUintCoefficient.

-    function absUnsignedSignedCoefficient(int256 signedCoefficient) internal pure returns (uint256) {
+    /// @notice Absolute value as uint256 for signed coefficients.
+    /// Maps int256.min to 2^255 to enable unsigned mul/div paths.
+    function absUnsignedSignedCoefficient(int256 signedCoefficient) internal pure returns (uint256) {

163-187: prod1-driven scale selection is bounded and correct. Consider tiny helper for reuse.

Optional extraction (e.g., decDigitsFromHigh) to isolate the staged divisions + loop for readability/reuse.


293-299: Tighten provenance: “OpenZeppelin” + do-not-modify note for mul512.

Keep mul512 aligned with upstream mulDiv provenance and mark it immutable here.

-    /// mul512 from Open Zeppelin.
-    /// Simply part of the original mulDiv function abstracted out for reuse
-    /// elsewhere.
+    /// mul512 from OpenZeppelin (Remco Bloemen), verbatim logic extracted from mulDiv.
+    /// NOTE: Keep this in lockstep with the upstream implementation; do not modify.
+    /// License/provenance: see file header and upstream sources.
test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (3)

50-60: Use normalized-zero constants for clarity and invariants.

Encode the zero representation explicitly.

-        checkMul(0, 0, 0, 0, 0, 0);
+        checkMul(0, 0, 0, 0, NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT);
-        checkMul(0, exponentA, 0, exponentB, 0, 0);
+        checkMul(0, exponentA, 0, exponentB, NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT);

62-72: Mirror normalized-zero constants in asymmetric zero cases.

Keep zero representation consistent across tests.

-        checkMul(0, 0, 1, 0, 0, 0);
+        checkMul(0, 0, 1, 0, NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT);
@@
-        checkMul(1, 0, 0, 0, 0, 0);
+        checkMul(1, 0, 0, 0, NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT);

86-95: Avoid giant literal; derive expected via slow path to prevent drift.

Removes risk from rational-literal conversion across compiler/toolchain changes.

-        checkMul(
-            type(int256).max,
-            0,
-            type(int256).max,
-            0,
-            int256(3.3519519824856492748935062495514615318698414551480983444308903609304410075182e76),
-            77
-        );
+        (int256 actualSignedCoefficient, int256 actualExponent) =
+            LibDecimalFloatImplementation.mul(type(int256).max, 0, type(int256).max, 0);
+        (int256 expectedSignedCoefficient, int256 expectedExponent) =
+            LibDecimalFloatSlow.mulSlow(type(int256).max, 0, type(int256).max, 0);
+        assertEq(actualSignedCoefficient, expectedSignedCoefficient, "signedCoefficient");
+        assertEq(actualExponent, expectedExponent, "exponent");
test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol (4)

25-42: Prefer bound over assume to prevent exponent+1 overflow in overflow branch.

Eliminate the assume-only guard.

-        vm.assume(exponent != type(int256).max); // Prevent overflow in exponent.
+        // Prevent overflow in exponent when adding 1.
+        exponent = bound(exponent, type(int256).min, type(int256).max - 1);

60-79: Same guard improvement for the both-negative overflow case.

Keep tests deterministic without relies-on-assume.

-        vm.assume(exponent != type(int256).max); // Prevent overflow in exponent.
+        // Prevent overflow in exponent when adding 1.
+        exponent = bound(exponent, type(int256).min, type(int256).max - 1);

98-114: Mixed-sign BA overflow: replace assume with bounded exponent.

Aligns with other cases.

-        vm.assume(exponent != type(int256).max); // Prevent overflow in exponent.
+        // Prevent overflow in exponent when adding 1.
+        exponent = bound(exponent, type(int256).min, type(int256).max - 1);

116-135: Mixed-sign AB overflow: same bounded-exponent guard.

Consistent pattern across tests.

-        vm.assume(exponent != type(int256).max); // Prevent overflow in exponent.
+        // Prevent overflow in exponent when adding 1.
+        exponent = bound(exponent, type(int256).min, type(int256).max - 1);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 37e60df and 0281a39.

📒 Files selected for processing (4)
  • .gas-snapshot (2 hunks)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (3 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (3 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-21T18:03:40.292Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#107
File: test/lib/LibDecimalFloatSlow.sol:37-45
Timestamp: 2025-08-21T18:03:40.292Z
Learning: In test/lib/LibDecimalFloatSlow.sol, the "slow" implementation is intentionally different from the production implementation to serve as an independent reference for fuzzing tests. The goal is to have two different approaches (expensive loops vs optimized jumps) that produce equivalent results, not identical implementations.

Applied to files:

  • src/lib/implementation/LibDecimalFloatImplementation.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol
  • .gas-snapshot
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
📚 Learning: 2025-08-18T13:52:43.369Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#105
File: src/lib/implementation/LibDecimalFloatImplementation.sol:248-335
Timestamp: 2025-08-18T13:52:43.369Z
Learning: The codebase has a policy of not modifying external code, such as mulDiv implementations adopted from standard libraries like OpenZeppelin, PRB Math, and Solady.

Applied to files:

  • src/lib/implementation/LibDecimalFloatImplementation.sol
📚 Learning: 2025-08-11T14:30:48.562Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/LibDecimalFloat.ceil.t.sol:43-50
Timestamp: 2025-08-11T14:30:48.562Z
Learning: When reviewing Solidity test files using Forge, verify the actual mutability of helper functions like `bound()` before suggesting changes to function mutability specifiers, as Forge implements many test utilities as pure functions.

Applied to files:

  • test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol
  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
📚 Learning: 2025-08-11T14:32:50.439Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol:15-29
Timestamp: 2025-08-11T14:32:50.439Z
Learning: In test code for the rain.math.float repository, redundant checks may be intentionally kept for clarity and documentation purposes, even when they could be simplified. The maintainer (thedavidmeister) prefers explicit assertions in test code to make the test's intent clear to future readers, prioritizing readability over conciseness.

Applied to files:

  • test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
🔇 Additional comments (10)
.gas-snapshot (4)

25-25: mul μ/regression looks acceptable given 512-bit path; snapshot refresh LGTM.

The slight μ increase for DecimalFloatMulTest likely stems from safer 512-bit intermediates and scaling. Tradeoff is reasonable.


219-224: All references to the old test suite name have been removed — no CI updates required.

I ran the suggested grep and only saw occurrences of the new LibDecimalFloatImplementationWithTargetExponentTest and related withTargetExponent APIs in test and implementation files. There are no lingering references to the old LibDecimalFloatWithTargetExponentTest suite name in scripts, docs, or CI configurations.


272-272: Action Required: Verify Packed Mul Fast Path Performance

The gas snapshot for LibDecimalFloatMulTest:testMulPacked(bytes32,bytes32) shows an average increase from 9630 → 10369 (~7.7%). While this regression is small and may be acceptable, please confirm that your most common inputs still hit the intended fast path and have not fallen back to a slower implementation.

Location: .gas-snapshot at line 272

LibDecimalFloatMulTest:testMulPacked(bytes32,bytes32) (runs: 5104, μ: 9630, ~: 10369)  

Verification Script Attempted:

#!/bin/bash
forge test --match-test 'testMulPacked' --gas-report -vv

– Resulted in forge: command not found, so no gas data could be collected in this environment.

Next Steps:

  1. Run the tests in an environment with Foundry installed (or equivalent):

    # Install or update Foundry if needed
    foundryup
    
    # Rerun only the packed mul tests with gas reporting
    forge test --match-test testMulPacked --gas-report -vv
  2. Compare the before/after gas traces on a representative input corpus to ensure the fast path is still taken. For example, diff the JSON gas reports or use:

    forge snapshot .gas-snapshot gas-report.json
  3. Share the updated gas summary or diffs here to confirm there is no unintended slow path.


230-231: Double-check Log10Packed accuracy after large gas drop

The mean/median gas for testLog10Packed(bytes32) fell sharply from ~1,651,924 to ~1,271,911—a significant change that may indicate different code-paths or reduced precision in your 512-bit refactor. Please re-run your highest-precision and edge-case assertions to ensure no silent accuracy drift.

Locations to review:

  • .gas-snapshot at lines 230–231
  • LibDecimalFloatLog10Test::testLog10Packed(bytes32)

Note: the automatic verification script failed here (forge: command not found). Please run the following in your local Foundry environment and confirm:

# Compare snapshots for log10 before/after
forge snapshot --diff .gas-snapshot --match-test 'Log10|log10'

# Re-run gas reports with full verbosity for log10 tests
forge test -vvvv --match-test 'Log10|log10' --gas-report

Let me know if any tolerances or path-selection logic need tightening.

src/lib/implementation/LibDecimalFloatImplementation.sol (4)

111-138: Sign application handles 2^255 correctly; negative/overflow branches look solid.

The guarded -2^255 case and scale-down on overflow avoid UB. Parenthesized sign test improves clarity.


140-155: Early zero short-circuit is correct and avoids unnecessary mulDiv work.

Clear and minimal state writes to the named returns.


191-196: Finalization via unabsUnsignedMulOrDivLossy with mulDiv-scaled magnitude is correct.

Separation of magnitude and sign, plus exponent carry, is clean.


310-385: mulDiv extraction via mul512 is faithful and side-effect free.

Matches the standard OZ/PRB/Solady pattern including lpotdod factoring and Newton-Raphson inverse.

test/src/lib/implementation/LibDecimalFloatImplementation.mul.t.sol (1)

15-27: Helper consolidates assertions; good for future vectorization.

checkMul keeps tests DRY and focused.

test/src/lib/implementation/LibDecimalFloatImplementation.unabsUnsignedMulOrDivLossy.t.sol (1)

137-150: Exact 2^255 negative path test is valuable; assertions are crisp.

Covers the int256.min special-case and ensures no exponent bump.

Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
@thedavidmeister thedavidmeister merged commit 60d8ce6 into main Aug 26, 2025
8 checks passed
@github-actions
Copy link
Copy Markdown

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 26, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

SIZE=L


🏁 Script executed:

#!/bin/bash
# Get an overview of the total changes in this PR by examining the diff stats
git diff --stat origin/main...HEAD

# Count the total lines changed
git diff --numstat origin/main...HEAD | awk '{files++; added+=$1; deleted+=$2} END {print "Files changed: " files "\nLines added: " added "\nLines deleted: " deleted "\nTotal changes: " added+deleted}'

# Get a summary of the types of files changed
git diff --name-only origin/main...HEAD | head -20

Length of output: 479


SIZE=L


🏁 Script executed:

#!/bin/bash
# Try to get the current branch name and find the correct comparison
git branch -a

# Try to get the diff against main branch directly
git diff main --stat 2>/dev/null || echo "main branch diff failed"

# Try to get the diff against origin/main directly  
git diff origin/main --stat 2>/dev/null || echo "origin/main branch diff failed"

# Get all the changed files
git ls-files --modified --others --exclude-standard

# Check what remotes we have
git remote -v

Length of output: 366


SIZE=L

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants