Skip to content

ceil#96

Merged
thedavidmeister merged 15 commits intomainfrom
2025-08-09-ceil
Aug 11, 2025
Merged

ceil#96
thedavidmeister merged 15 commits intomainfrom
2025-08-09-ceil

Conversation

@thedavidmeister
Copy link
Copy Markdown
Contributor

@thedavidmeister thedavidmeister commented Aug 10, 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 ceiling operation for decimal floats (rounds values up to the nearest integer) and exposed it for external use.
  • Improvements

    • Floor is optimized for integer-aligned values.
    • Core addition path now uses a much larger internal scaling approach, extending dynamic range.
    • Updated gas profiling snapshot metrics across tests.
  • Bug Fixes

    • Adjusted expected numeric results to match revised scaling.
  • Tests

    • Added comprehensive ceil and maximize tests; renamed/expanded and reprofiled many existing tests.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 10, 2025

Walkthrough

Added ceil(Float) and a public DecimalFloat.ceil wrapper; optimized floor(Float). Introduced maximize(...) and increased ADD_MAX_EXPONENT_DIFF, switching the add path to maximize operands. Added maximize and ceil tests, updated many tests to expect maximized-scale outputs, and refreshed gas-snapshot data.

Changes

Cohort / File(s) Change Summary
Gas Snapshot Update
.gas-snapshot
Updated gas snapshot run counts and averaged gas numbers across many tests (measurement/snapshot updates only).
Decimal Float: Ceil & Floor
src/lib/LibDecimalFloat.sol
Added internal ceil(Float); floor(Float) early-return when exponent >= 0; truncation paths still use packLossy.
Public DecimalFloat Wrapper
src/concrete/DecimalFloat.sol
Added public wrapper function ceil(Float a) external pure returns (Float) delegating to library.
Maximize & Add Changes
src/lib/implementation/LibDecimalFloatImplementation.sol
Increased ADD_MAX_EXPONENT_DIFF 37→76; added MAXIMIZED_ZERO_* constants and maximize(int256,int256); updated add to call maximize on operands and align using larger exponent.
Maximize Tests
test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol
New tests validating maximize: correctness, examples, idempotence, boundary handling.
Add Tests (Maximized)
test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol
Replaced normalize→maximize in tests, renamed tests, added checkAdd helper, updated expected coefficients/exponents to maximized scale.
Log10 / Pow10 Tests Updated
test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol, test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol
Updated expected outputs to maximized scale; minor test renames.
Ceil Tests (Library & Deployed)
test/src/lib/LibDecimalFloat.ceil.t.sol, test/src/concrete/DecimalFloat.ceil.t.sol
New LibDecimalFloatCeilTest and DecimalFloatCeilTest covering property examples, boundaries, non-revert, and deployed-vs-library parity.
Pow / Sqrt Test Renames & Adjustments
test/src/lib/LibDecimalFloat.pow.t.sol, test/src/lib/LibDecimalFloat.sqrt.t.sol
Renamed fuzz tests to testRoundTripFuzzPow / testRoundTripFuzzSqrt; updated one sqrt example expected scaling.
Misc Tests: Snapshot / Relocations
test/src/lib/... (multiple)
Numerous test expected-value updates, run-count shifts, and minor renames/relocations in snapshots; no functional API changes.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant LibDecimalFloat
    participant LibDecimalFloatImplementation

    Caller->>LibDecimalFloat: ceil(Float f)
    alt exponent >= 0
        LibDecimalFloat-->>Caller: return f
    else
        LibDecimalFloat->>LibDecimalFloat: unpack characteristic & mantissa
        alt mantissa == 0
            LibDecimalFloat-->>Caller: return f
        else
            LibDecimalFloat->>LibDecimalFloatImplementation: add(char, exp, 1e75, -75) if mantissa>0
            LibDecimalFloatImplementation-->>LibDecimalFloat: adjusted packed result
            LibDecimalFloat-->>Caller: return ceiled Float
        end
    end

    Caller->>LibDecimalFloat: floor(Float f)
    alt exponent >= 0
        LibDecimalFloat-->>Caller: return f
    else
        LibDecimalFloat->>LibDecimalFloat: truncate mantissa, packLossy -> return floored Float
    end

    Caller->>LibDecimalFloatImplementation: add(a, b)
    LibDecimalFloatImplementation->>LibDecimalFloatImplementation: maximize(a)
    LibDecimalFloatImplementation->>LibDecimalFloatImplementation: maximize(b)
    alt exponents differ
        LibDecimalFloatImplementation->>LibDecimalFloatImplementation: align by dividing smaller coeff (10^diff)
    end
    LibDecimalFloatImplementation->>LibDecimalFloatImplementation: sum coefficients (use larger exponent)
    LibDecimalFloatImplementation-->>Caller: return result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • 2025 04 16 tests #29 — Related tests around normalize/idempotency and normalization behavior; touches the same normalization/maximization concerns.
  • wip on consolidating packed #30 — Related changes to decimal-float internals and ADD_MAX_EXPONENT_DIFF/constants; overlaps implementation area.
  • float struct #24 — Prior Float API/ceil/floor work that this PR extends with ceil and public wrapper parity tests.

Suggested reviewers

  • hardyjosh
  • findolor
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-08-09-ceil

🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
  • 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 src/utils.ts and explain its main purpose.
    • @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 comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 16

🔭 Outside diff range comments (5)
src/lib/LibDecimalFloat.sol (1)

613-625: floor is incorrect for negative values; truncates toward zero instead of −∞

With negative inputs and non-zero mantissa, characteristic is the truncation toward zero (e.g., floor(-1.2) should be -2, but returns -1). Fix by subtracting 1 when mantissa < 0.

 function floor(Float float) internal pure returns (Float) {
   (int256 signedCoefficient, int256 exponent) = float.unpack();
   // If the exponent is 0 or greater then the float is already an integer.
   if (exponent >= 0) {
     return float;
   }
   (int256 characteristic, int256 mantissa) =
     LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);
-  (Float result, bool lossless) = packLossy(characteristic, exponent);
+  (Float result, bool lossless) = packLossy(characteristic, exponent);
+  // Adjust for negatives: true floor moves to the next more-negative integer.
+  if (mantissa < 0) {
+    result = sub(result, FLOAT_ONE);
+  }
   // Flooring is lossy by definition.
   (lossless, mantissa);
   return result;
 }
src/lib/implementation/LibDecimalFloatImplementation.sol (3)

709-721: Use ADD_MAX_EXPONENT_DIFF instead of literal 76 for rescale bounds

compareRescale uses a hard-coded 76. Replace with ADD_MAX_EXPONENT_DIFF (and ensure overflow paths remain correct).


747-770: Same constant leakage here: replace 76 with ADD_MAX_EXPONENT_DIFF in withTargetExponent()

Keeps the exponent-difference policy consistent from a single source of truth.


785-791: Characteristic/mantissa cutoff (-76) should also reference ADD_MAX_EXPONENT_DIFF

Helps avoid drift if the limit changes again.

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

119-121: Fuzz test cannot be pure: uses vm.assume/bound/maximize/assert

Mark as non-pure (or view) to compile.

-    ) public pure {
+    ) public {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b0352fc and e966d5a.

📒 Files selected for processing (9)
  • .gas-snapshot (3 hunks)
  • src/lib/LibDecimalFloat.sol (2 hunks)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (5 hunks)
  • test/src/lib/LibDecimalFloat.ceil.t.sol (1 hunks)
  • test/src/lib/LibDecimalFloat.pow.t.sol (1 hunks)
  • test/src/lib/LibDecimalFloat.sqrt.t.sol (2 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (4 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol (1 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.pow10.t.sol (1 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.
📚 Learning: 2025-06-16T13:17:28.513Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: src/concrete/DecimalFloat.sol:175-182
Timestamp: 2025-06-16T13:17:28.513Z
Learning: In the rainlanguage/rain.math.float codebase, there's an established naming convention where functions accepting a `Float` type parameter consistently use `float` as the parameter name, even though it shadows the type name. This pattern is used throughout `LibDecimalFloat.sol` and should be maintained for consistency in related contracts like `DecimalFloat.sol`.

Applied to files:

  • src/lib/LibDecimalFloat.sol
  • test/src/lib/LibDecimalFloat.ceil.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:

  • src/lib/LibDecimalFloat.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-sol-test)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • 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 (16)
src/lib/LibDecimalFloat.sol (1)

615-618: Good micro-optimization: early return when already an integer

Early-exiting floor for exponent >= 0 avoids unnecessary work. No functional risk.

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

59-63: Maximized zero constants mirror normalized ones — OK

Values mirror normalized zero; safe and consistent.


278-283: Using maximize before alignment in add() improves stability across OOMs

Maximizing both operands prior to alignment is a sound change and matches the increased exponent-diff tolerance. LGTM.


16-16: Widening ADD_MAX_EXPONENT_DIFF to 76 is reasonable; ensure consistency across code

add() now respects this limit; however other places still use the literal 76. Recommend centralizing to the constant for maintainability.

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

46-47: Interpolated lookup expectation aligns with new scale

The updated expected (≈1.00065e75, -75) is consistent. No issues.

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

128-144: Rename only; behavior unchanged

The fuzz test logic remains intact under the new name. All good.

test/src/lib/LibDecimalFloat.sqrt.t.sol (3)

87-91: Rename LGTM

Renaming testRoundTripFuzz to testRoundTripFuzzSqrt matches conventions in other test contracts. Looks good.


56-57: Inconsistent scaling in sqrt test expectations

The test at test/src/lib/LibDecimalFloat.sqrt.t.sol mixes normalized and maximized representations:

• Lines 54–55 use small-scale outputs (2e3 @ –3, 4e3 @ –3)
• Line 56 expects a maximized coefficient (…0000 @ –66)

Please confirm which scaling convention the sqrt implementation now uses, then make them consistent by either:

  • Converting the first two cases to maximized form, or
  • Reverting the sqrt(16) expectation back to normalized form (e.g., 4e3 @ –3).

70-74: Verify normalize usage for sqrt error path

I wasn’t able to locate the sqrt implementation in the repo, so please confirm whether it still calls normalize(...) before reverting on negative inputs. If it now uses maximize(...) to prepare its revert arguments, update the test accordingly; otherwise, no change is needed.

  • test/src/lib/LibDecimalFloat.sqrt.t.sol (lines 70–74): currently uses
    (int256 signedCoefficientNormalized, int256 exponentNormalized) =
        LibDecimalFloatImplementation.normalize(signedCoefficient, exponent);
    vm.expectRevert(
        abi.encodeWithSelector(Log10Negative.selector, signedCoefficientNormalized, exponentNormalized)
    );
    this.sqrtExternal(a, tables);
    – Switch to LibDecimalFloatImplementation.maximize(...) here if the sqrt implementation encodes the maximized values.
    – Otherwise, keep using normalize(...) to match the current revert behavior.
.gas-snapshot (2)

257-263: Fuzz test rename reflected

Renamed fuzz tests (Pow/Sqrt) are represented here. No action needed.


35-40: Action Required: Verify Updated Gas Snapshot for Ceil Tests

The .gas-snapshot file now includes the new LibDecimalFloatCeilTest:testCeil* entries on lines 35–40. Since the sandbox cannot run forge, please confirm locally that these entries are correct and up to date:

  • File: .gas-snapshot
    Lines: 35–40 — verify the testCeil* gas values

To refresh the snapshot, run locally:

forge test --gas-report
forge snapshot

Once confirmed, commit the regenerated .gas-snapshot to keep tests and benchmarks in sync.

test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol (5)

62-66: Expectation update to maximized result is correct

Adding two ones returns 2e75 @ -75 under maximize. LGTM.


68-72: Pre-maximized add case LGTM

Inputs and expected outputs are consistent with maximize.


77-80: Scaled expectation LGTM

1.11111111e76 @ (-76 + 9) matches maximize semantics for 123456789 + 987654321.


124-128: Switched to maximize in fuzz pre-processing

Good alignment with the implementation shift from normalize to maximize.


201-220: Minor message inconsistency

Subject casing differs between messages ("Signed coefficient mismatch" vs "signed coefficient mismatch"). Consider unifying for consistency.

-        assertEq(signedCoefficient, expectedSignedCoefficient, "signed coefficient mismatch");
+        assertEq(signedCoefficient, expectedSignedCoefficient, "Signed coefficient mismatch");

Likely an incorrect or invalid review comment.

Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread src/lib/implementation/LibDecimalFloatImplementation.sol
Comment thread src/lib/LibDecimalFloat.sol
Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol
Comment thread test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol
Comment on lines +48 to +51
/// For exponents [-76,-1] the ceil is the + 1.
function testCeilInRange(int224 x, int256 exponent) external pure {
exponent = bound(exponent, -76, -1);
int256 scale = int256(10 ** uint256(-exponent));
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.

🧹 Nitpick (assertive)

Clarify docstring for the in-range behavior

Doc is truncated. Suggest explicit statement of the rule used below.

-    /// For exponents [-76,-1] the ceil is the + 1.
+    /// For exponents in [-76, -1], ceil(x) = characteristic + 1 iff mantissa > 0; otherwise ceil(x) = characteristic.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// For exponents [-76,-1] the ceil is the + 1.
function testCeilInRange(int224 x, int256 exponent) external pure {
exponent = bound(exponent, -76, -1);
int256 scale = int256(10 ** uint256(-exponent));
/// For exponents in [-76, -1], ceil(x) = characteristic + 1 iff mantissa > 0; otherwise ceil(x) = characteristic.
function testCeilInRange(int224 x, int256 exponent) external pure {
exponent = bound(exponent, -76, -1);
int256 scale = int256(10 ** uint256(-exponent));
🤖 Prompt for AI Agents
In test/src/lib/LibDecimalFloat.ceil.t.sol around lines 48 to 51, the docstring
for the testCeilInRange function is incomplete and unclear about the behavior
for exponents in the range [-76, -1]. Update the docstring to explicitly state
the rule applied in this range, clarifying that for these exponents, the ceiling
operation results in adding 1 to the value. Make sure the explanation is concise
and fully describes the expected behavior.

Comment thread test/src/lib/LibDecimalFloat.ceil.t.sol
Comment thread test/src/lib/LibDecimalFloat.ceil.t.sol
Comment thread test/src/lib/LibDecimalFloat.ceil.t.sol
Comment thread test/src/lib/LibDecimalFloat.ceil.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: 2

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

296-317: Update function documentation to reflect new maximization behavior

The add() function now returns the sum aligned to the larger exponent using maximize() instead of normalize(). The function header and docstring comments should be updated to clearly state that the result uses maximization (aligned to ~1e75-1e76 range) rather than normalization (1e37-1e38 range), and that the result maintains the larger input's exponent.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e966d5a and b006e6c.

📒 Files selected for processing (2)
  • src/lib/implementation/LibDecimalFloatImplementation.sol (5 hunks)
  • test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol (1 hunks)
🔇 Additional comments (1)
src/lib/implementation/LibDecimalFloatImplementation.sol (1)

128-130: Verify overflow handling consistency in mul
The add implementation now uses maximize to scale operands before combining, but in mul (src/lib/implementation/LibDecimalFloatImplementation.sol lines 127–131) the overflow path still calls normalize. If the intent is to standardize on maximize for both operations, consider replacing those two normalize calls with maximize; otherwise please confirm that falling back to normalize here is intentional.

Locations to review:

  • src/lib/implementation/LibDecimalFloatImplementation.sol:127–131

Proposed change (optional):

 if (didOverflow) {
-   (signedCoefficientA, exponentA) = normalize(signedCoefficientA, exponentA);
-   (signedCoefficientB, exponentB) = normalize(signedCoefficientB, exponentB);
+   (signedCoefficientA, exponentA) = maximize(signedCoefficientA, exponentA);
+   (signedCoefficientB, exponentB) = maximize(signedCoefficientB, exponentB);
    return mul(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
 }

Comment on lines +51 to +64
function testMaximizedExamples() external pure {
checkMaximized(0, 0, 0, 0);
checkMaximized(0, 1, 0, 0);
checkMaximized(1e37, 0, 1e75, -38);
checkMaximized(1e75, 0, 1e75, 0);
checkMaximized(type(int256).max, 0, type(int256).max / 10, 1);
checkMaximized(type(int256).min, 0, type(int256).min / 10, 1);
checkMaximized(42, 0, 42e74, -74);
checkMaximized(42e74, -74, 42e74, -74);

for (int256 i = 76; i >= 0; i--) {
checkMaximized(int256(10 ** uint256(i)), 0, 1e75, i - 75);
}
}
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.

🧹 Nitpick (assertive)

Consider adding tests for ExponentOverflow conditions

The test suite covers normal operation well but doesn't test the ExponentOverflow revert conditions in the maximize function. Consider adding tests that verify the function reverts when exponent overflows occur during scaling.

Example test case:

function testMaximizeExponentOverflow() external {
    // Test overflow when scaling down
    vm.expectRevert();
    LibDecimalFloatImplementation.maximize(1e77, type(int256).max);
    
    // Test underflow when scaling up  
    vm.expectRevert();
    LibDecimalFloatImplementation.maximize(1, type(int256).min);
}
🤖 Prompt for AI Agents
In test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol
around lines 51 to 64, add tests that assert the maximize function reverts on
exponent overflow/underflow; create a new test (e.g.,
testMaximizeExponentOverflow) that calls LibDecimalFloatImplementation.maximize
with values that force an exponent overflow when scaling down (for example a
mantissa >= 1e77 with exponent = type(int256).max) and another call that forces
exponent underflow when scaling up (for example mantissa = 1 with exponent =
type(int256).min), wrapping each call with vm.expectRevert() so the test
verifies the ExponentOverflow revert path.

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

🔭 Outside diff range comments (1)
src/lib/LibDecimalFloat.sol (1)

619-624: Add a mantissa==0 fast path in floor() to avoid unnecessary packing

When exponent < 0 and mantissa == 0 the value is already integral; you can return the original float and skip packLossy.

 (int256 characteristic, int256 mantissa) =
   LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);
+(mantissa); // touch for clarity below
+// If there is no fractional component, return the original float.
+if (mantissa == 0) {
+  return float;
+}
 (Float result, bool lossless) = packLossy(characteristic, exponent);
 // Flooring is lossy by definition.
 (lossless, mantissa);
 return result;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b006e6c and 91d8a77.

📒 Files selected for processing (1)
  • src/lib/LibDecimalFloat.sol (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-06-16T13:17:28.513Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: src/concrete/DecimalFloat.sol:175-182
Timestamp: 2025-06-16T13:17:28.513Z
Learning: In the rainlanguage/rain.math.float codebase, there's an established naming convention where functions accepting a `Float` type parameter consistently use `float` as the parameter name, even though it shadows the type name. This pattern is used throughout `LibDecimalFloat.sol` and should be maintained for consistency in related contracts like `DecimalFloat.sol`.

Applied to files:

  • src/lib/LibDecimalFloat.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:

  • src/lib/LibDecimalFloat.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-test)
  • GitHub Check: git-clean
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
🔇 Additional comments (2)
src/lib/LibDecimalFloat.sol (2)

615-618: Good short-circuit for integer inputs in floor()

Returning the original float when exponent >= 0 is correct and saves gas.


631-642: ceil() early exits look correct and consistent

  • exponent >= 0: already integer → return.
  • mantissa == 0: already integer at negative exponent → return.

Nice gas win and symmetry with floor().

Comment thread src/lib/LibDecimalFloat.sol
Comment thread src/lib/LibDecimalFloat.sol Outdated
thedavidmeister and others added 3 commits August 11, 2025 18:37
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: 3

🔭 Outside diff range comments (1)
src/lib/LibDecimalFloat.sol (1)

619-624: Optional: skip pack when mantissa is zero in floor

When mantissa is 0 the value is already integral; you can mirror the ceil optimization and return the original float to save a pack.

 (int256 characteristic, int256 mantissa) =
   LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);
+// If there's no fractional part, return the original float (already an integer).
+if (mantissa == 0) {
+  return float;
+}
 (Float result, bool lossless) = packLossy(characteristic, exponent);
♻️ Duplicate comments (2)
test/src/lib/LibDecimalFloat.ceil.t.sol (2)

52-53: Clarify in-range rule in docstring

Make the rule explicit for exponents in [-76, -1]: add 1 iff mantissa > 0.

-/// For exponents [-76,-1] the ceil is the + 1.
+/// For exponents in [-76, -1], ceil(x) = characteristic + 1 iff mantissa > 0; otherwise ceil(x) = characteristic.

80-98: Remove duplicate example line

The max example appears twice; the second is redundant.

         checkCeil(type(int224).max, 0, type(int224).max, 0);
         checkCeil(type(int224).min, 0, type(int224).min, 0);
         checkCeil(2.5e37, -37, 3e66, -66);
-        checkCeil(type(int224).max, 0, type(int224).max, 0);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b006e6c and 783dda0.

📒 Files selected for processing (3)
  • .gas-snapshot (3 hunks)
  • src/lib/LibDecimalFloat.sol (2 hunks)
  • test/src/lib/LibDecimalFloat.ceil.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-08-11T14:30:48.527Z
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.527Z
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/LibDecimalFloat.ceil.t.sol
📚 Learning: 2025-06-16T13:17:28.513Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: src/concrete/DecimalFloat.sol:175-182
Timestamp: 2025-06-16T13:17:28.513Z
Learning: In the rainlanguage/rain.math.float codebase, there's an established naming convention where functions accepting a `Float` type parameter consistently use `float` as the parameter name, even though it shadows the type name. This pattern is used throughout `LibDecimalFloat.sol` and should be maintained for consistency in related contracts like `DecimalFloat.sol`.

Applied to files:

  • test/src/lib/LibDecimalFloat.ceil.t.sol
  • src/lib/LibDecimalFloat.sol
📚 Learning: 2025-08-11T14:30:35.222Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#96
File: test/src/lib/LibDecimalFloat.ceil.t.sol:35-39
Timestamp: 2025-08-11T14:30:35.222Z
Learning: In Foundry/Forge test framework, the `bound()` function is pure - it performs mathematical clamping operations without reading contract state, so test functions using `bound()` can be marked as pure if all other operations in the function are also pure.

Applied to files:

  • test/src/lib/LibDecimalFloat.ceil.t.sol
📚 Learning: 2025-08-11T14:30:48.527Z
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.527Z
Learning: In Forge test contracts, the `bound()` function from the Test contract is a pure function that performs mathematical bounds checking without reading state, so test functions using `bound()` can remain `pure`.

Applied to files:

  • test/src/lib/LibDecimalFloat.ceil.t.sol
📚 Learning: 2025-08-11T14:32:50.404Z
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.404Z
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/LibDecimalFloat.ceil.t.sol
📚 Learning: 2025-06-16T13:19:32.009Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: crates/float/src/lib.rs:382-401
Timestamp: 2025-06-16T13:19:32.009Z
Learning: In the rainlanguage/rain.math.float codebase, variable shadowing in property tests like test_lt_eq_gt_with_add is acceptable when it represents a logical progression of the same conceptual value being tested through different states.

Applied to files:

  • test/src/lib/LibDecimalFloat.ceil.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:

  • src/lib/LibDecimalFloat.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, test-wasm-build)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: git-clean
🔇 Additional comments (6)
src/lib/LibDecimalFloat.sol (2)

615-618: Good early return for integer values in floor

Returning early when exponent >= 0 avoids unnecessary characteristic/mantissa work and packing for integers. Nice gas win.


631-657: Ceil logic looks correct and handles signs and extremes

  • Early-return for exponent >= 0 and mantissa == 0 are correct and gas-friendly.
  • For mantissa > 0, adding FLOAT_ONE is the right adjustment; with maximize and ADD_MAX_EXPONENT_DIFF=76, tiny→huge addition behaves as expected for exponents < -76.
  • Parameter name float follows repo convention. All good.
test/src/lib/LibDecimalFloat.ceil.t.sol (3)

12-14: LGTM: follows Float parameter naming convention

Parameter named float adheres to established repo convention. No issues.


16-33: Helper correctness: pack → ceil → unpack with explicit failure logging

The flow is solid and compares via implementation eq. Keeping explicit logs before revert aids debugging.


100-108: Zero behavior checks are precise and comprehensive

Covers different zero encodings (packed with any exponent) and equality; good coverage.

.gas-snapshot (1)

35-40: Gas snapshot entries for ceil look consistent

New ceil tests are captured with sensible gas/μ metrics. Snapshot update looks consistent with added functionality.

Comment thread src/lib/LibDecimalFloat.sol Outdated
Comment thread test/src/lib/LibDecimalFloat.ceil.t.sol
Comment thread test/src/lib/LibDecimalFloat.ceil.t.sol Outdated
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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f07e43e and ee2857b.

📒 Files selected for processing (3)
  • .gas-snapshot (3 hunks)
  • src/concrete/DecimalFloat.sol (1 hunks)
  • test/src/concrete/DecimalFloat.ceil.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-06-16T13:17:28.513Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: src/concrete/DecimalFloat.sol:175-182
Timestamp: 2025-06-16T13:17:28.513Z
Learning: In the rainlanguage/rain.math.float codebase, there's an established naming convention where functions accepting a `Float` type parameter consistently use `float` as the parameter name, even though it shadows the type name. This pattern is used throughout `LibDecimalFloat.sol` and should be maintained for consistency in related contracts like `DecimalFloat.sol`.

Applied to files:

  • src/concrete/DecimalFloat.sol
  • test/src/concrete/DecimalFloat.ceil.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/concrete/DecimalFloat.ceil.t.sol
🪛 GitHub Actions: Git is clean
test/src/concrete/DecimalFloat.ceil.t.sol

[error] git diff --exit-code failed (exit code 1). Detected end-of-file newline issue: 'No newline at end of file' in test/src/concrete/DecimalFloat.ceil.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 (3)
.gas-snapshot (1)

3-3: Manual Verification of Gas Snapshot Update

The sandbox environment cannot execute forge, so please confirm locally that the snapshot was regenerated cleanly with no unintended changes:

• Run the full suite and regenerate the snapshot from a clean state:

forge test -vvv
forge snapshot
git diff -- .gas-snapshot

• Verify that no unexpected diffs appear in .gas-snapshot.

src/concrete/DecimalFloat.sol (1)

169-174: LGTM: ceil wrapper mirrors floor and follows existing patterns.

  • external/pure is correct.
  • Uses library extension method consistently.
  • NatSpec mirrors existing wrappers.

No changes requested.

test/src/concrete/DecimalFloat.ceil.t.sol (1)

18-25: Parity test pattern is solid.

  • External call via this.ceilExternal captures revert data; vm.expectRevert(err) ensures deployed path reverts identically.
  • unwrap comparison checks exact value parity.

No changes requested.

Comment on lines +15 to +17
function testCeilDeployed(Float a) external {
DecimalFloat deployed = new DecimalFloat();

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.

🧹 Nitpick (assertive)

Optional: reuse a single DecimalFloat instance to reduce per-test deployment cost.

Repeated new DecimalFloat() per fuzz case inflates gas/time. Consider a shared instance via setUp() unless test isolation requires fresh deployments.

-        DecimalFloat deployed = new DecimalFloat();
+        // Use a shared instance (initialized in setUp()).
+        DecimalFloat deployed = deployedInstance;

Add once to the contract:

DecimalFloat internal deployedInstance;

function setUp() public {
    deployedInstance = new DecimalFloat();
}
🧰 Tools
🪛 GitHub Actions: Git is clean

[error] git diff --exit-code failed (exit code 1). Detected end-of-file newline issue: 'No newline at end of file' in test/src/concrete/DecimalFloat.ceil.t.sol.

🤖 Prompt for AI Agents
In test/src/concrete/DecimalFloat.ceil.t.sol around lines 15 to 17, avoid
creating a new DecimalFloat() inside each fuzz test; instead add a
contract-level DecimalFloat variable and initialize it once in a setUp()
function, then update testCeilDeployed to use that shared deployedInstance;
ensure visibility is internal or private as needed and keep tests that require
fresh state as exceptions.

Comment thread test/src/concrete/DecimalFloat.ceil.t.sol Outdated
@thedavidmeister thedavidmeister merged commit 72d1fa7 into main Aug 11, 2025
7 checks passed
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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f07e43e and f9bbbe7.

📒 Files selected for processing (4)
  • .gas-snapshot (3 hunks)
  • src/concrete/DecimalFloat.sol (1 hunks)
  • src/lib/LibDecimalFloat.sol (2 hunks)
  • test/src/concrete/DecimalFloat.ceil.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-06-16T13:17:28.513Z
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: src/concrete/DecimalFloat.sol:175-182
Timestamp: 2025-06-16T13:17:28.513Z
Learning: In the rainlanguage/rain.math.float codebase, there's an established naming convention where functions accepting a `Float` type parameter consistently use `float` as the parameter name, even though it shadows the type name. This pattern is used throughout `LibDecimalFloat.sol` and should be maintained for consistency in related contracts like `DecimalFloat.sol`.

Applied to files:

  • src/concrete/DecimalFloat.sol
  • test/src/concrete/DecimalFloat.ceil.t.sol
  • src/lib/LibDecimalFloat.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/concrete/DecimalFloat.ceil.t.sol
  • src/lib/LibDecimalFloat.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: git-clean
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, test-wasm-build)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-static)
  • GitHub Check: rainix (macos-latest, rainix-rs-test)
🔇 Additional comments (4)
.gas-snapshot (1)

3-3: Snapshot updates reflect new ceil coverage; OK

New DecimalFloatCeilTest and LibDecimalFloatCeilTest entries look consistent with adding ceil and related library coverage. No action needed.

Also applies to: 36-41

src/concrete/DecimalFloat.sol (1)

169-174: New ceil wrapper matches existing wrapper patterns

  • External + pure is correct.
  • Using-for delegation aligns with the rest of the contract.
  • Docstring consistent with floor/frac style.

LGTM.

src/lib/LibDecimalFloat.sol (2)

615-618: floor early-return is a solid micro-optimization

Returning early for exponent >= 0 avoids unnecessary characteristic/mantissa/pack work. Safe and gas-friendly.


627-653: Verify exponent-diff headroom for ceil add path

The headroom check in LibDecimalFloatImplementation.add uses

if (alignmentExponentDiff > ADD_MAX_EXPONENT_DIFF) { 
    return (signedCoefficientA, exponentA); 
}

with ADD_MAX_EXPONENT_DIFF = 76. In the ceil path you always add the literal 1 via

LibDecimalFloatImplementation.add(
    characteristic, exponent, 1e75, -75
);

so the maximum possible exponent-gap is

|exponent – (–75)| ≤ 76 
⇔ exponent ∈ [–151, +1]

However:

  • The float’s exponent domain is confined by packLossy/pack to roughly [–75, +76] (any value outside that is clamped or underflows to zero).
  • In practice, a Float never carries an exponent < –75, so |exponent + 75| ≤ 150, and in all actual cases |exponent + 75| ≤ 75: well below the 76 threshold.
  • Test coverage in test/src/lib/LibDecimalFloat.ceil.t.sol exercises exponents down to < –76 and confirms ceil(x)>0 for positive x.

Conclusion: the add(…,1e75,–75) call in ceil can never hit the alignmentExponentDiff > 76 branch given the float’s allowed exponent range.

Minor doc polish for consistency:

@@ src/lib/LibDecimalFloat.sol
-        (Float result, bool lossless) = packLossy(characteristic, exponent);
-        (lossless);
+        // Ceil is lossy by design: sub-unit precision is discarded.
+        (Float result, bool lossless) = packLossy(characteristic, exponent);
+        (lossless);

Comment on lines +15 to +26
function testCeilDeployed(Float a) external {
DecimalFloat deployed = new DecimalFloat();

try this.ceilExternal(a) returns (Float b) {
Float deployedB = deployed.ceil(a);

assertEq(Float.unwrap(b), Float.unwrap(deployedB));
} catch (bytes memory err) {
vm.expectRevert(err);
deployed.ceil(a);
}
}
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.

🧹 Nitpick (assertive)

Good parity test between library and deployed wrapper

  • try/catch with vm.expectRevert(err) correctly asserts revert parity.
  • Success path unwraps and compares raw values, avoiding equality pitfalls.

Consider renaming ceilExternal to internal helper in a separate contract if you later expand tests, but current approach is fine.

🤖 Prompt for AI Agents
test/src/concrete/DecimalFloat.ceil.t.sol lines 15-26: the test is correct and
requires no functional changes; keep the try/catch + vm.expectRevert pattern and
the unwrap comparison as-is for parity checks, but if you plan to expand tests
later consider moving ceilExternal into a small helper contract (rename
ceilExternal to a more descriptive internal helper or create a dedicated test
helper contract) to improve code organization and reuse.

This was referenced Aug 29, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Sep 9, 2025
7 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Jan 31, 2026
4 tasks
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.

1 participant