Skip to content

Conversation

@thedavidmeister
Copy link
Contributor

@thedavidmeister thedavidmeister commented Oct 2, 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 explicit scientific on/off control and custom min/max range for formatting; default scientific bounds exposed and used by the default formatter.
  • Breaking Changes

    • Removed significant-figures limit from formatting API; formatting APIs renamed/reworked to use boolean/range controls instead.
  • Tests

    • Expanded tests for default bounds, range behavior, explicit scientific control, and formatting round-trip integrity.
  • Chores

    • Re-baselined gas snapshot data.

@thedavidmeister thedavidmeister self-assigned this Oct 2, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 2, 2025

Walkthrough

Replaces sig-figs formatting with explicit scientific-mode and range-based formatting: adds default scientific bounds constants, new DecimalFloat.format overloads (bool and range), updates LibFormatDecimalFloat.toDecimalString to accept a boolean scientific flag, updates bindings/tests, and regenerates gas snapshot.

Changes

Cohort / File(s) Summary of edits
Formatting core (contracts)
src/concrete/DecimalFloat.sol, src/lib/format/LibFormatDecimalFloat.sol
Added FORMAT_DEFAULT_SCIENTIFIC_MIN / _MAX; replaced sig-figs-limit API with boolean scientific control; added format(a, bool) and format(a, scientificMin, scientificMax) overloads and removed format(a, uint256); changed toDecimalString signature to bool scientific and simplified related control flow.
Tests (Solidity)
test/src/concrete/DecimalFloat.format.t.sol, test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol
Updated helpers and callers to use boolean scientific or explicit range; added testFormatConstants; enforce scientificMin < scientificMax; removed sig-figs-specific tests; adjusted round-trip assertions and test signatures.
Rust bindings / JS API / tests
crates/float/src/lib.rs, crates/float/src/js_api.rs, test_js/float.test.ts
Added format_default_scientific_min/max constructors and JS exports; replaced format_with_limit with format_with_scientific and format_with_range; updated examples/tests to exercise defaults, explicit scientific control, and range-based formatting.
Gas snapshot
.gas-snapshot
Regenerated gas baselines: many benchmark/test gas entries updated to reflect adjusted tests and formatting changes.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Caller
    participant DecimalFloat
    participant LibFormat

    rect rgb(250,250,255)
    note over Caller,DecimalFloat: Default format()
    Caller->>DecimalFloat: format(a)
    DecimalFloat->>DecimalFloat: scientific = a.lt(FORMAT_DEFAULT_SCIENTIFIC_MIN) || a.gt(FORMAT_DEFAULT_SCIENTIFIC_MAX)
    DecimalFloat->>LibFormat: toDecimalString(a, scientific)
    LibFormat-->>DecimalFloat: formattedString
    DecimalFloat-->>Caller: formattedString
    end

    rect rgb(245,255,245)
    note over Caller,DecimalFloat: Range-based format
    Caller->>DecimalFloat: format(a, scientificMin, scientificMax)
    DecimalFloat->>DecimalFloat: require(scientificMin < scientificMax)
    DecimalFloat->>LibFormat: toDecimalString(a, a.lt(scientificMin) || a.gt(scientificMax))
    LibFormat-->>DecimalFloat: formattedString
    DecimalFloat-->>Caller: formattedString
    end

    rect rgb(255,245,245)
    note over Caller,DecimalFloat: Explicit mode
    Caller->>DecimalFloat: format(a, scientific: bool)
    DecimalFloat->>LibFormat: toDecimalString(a, scientific)
    LibFormat-->>DecimalFloat: formattedString
    DecimalFloat-->>Caller: formattedString
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • simplify float #39 — Modifies LibFormatDecimalFloat.toDecimalString and related formatting tests; closely overlaps this signature/behavior change.
  • switch to the solidity formatter #126 — Changes the JS/Rust formatting bindings (formatWithLimit vs range/scientific) and will likely conflict with this PR’s binding updates.
  • 2025 08 30 format #119 — Alters formatting API and sig-figs logic; related to replacing sigFigsLimit with scientific/range controls.

Suggested reviewers

  • hardyjosh
  • 0xgleb

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The title “sig figs by amount” is too vague and does not clearly summarize the primary changes, which center around adding scientific‐notation formatting with default and custom bounds rather than simply altering significant figures. Consider renaming the pull request to clearly reflect the main enhancement, such as “Add scientific notation formatting with default and custom bounds” or “Replace sig figs limit with explicit scientific notation range,” to better convey the key changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-10-02-sig-figs

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d541ba and 2fb222c.

📒 Files selected for processing (4)
  • src/concrete/DecimalFloat.sol (1 hunks)
  • src/lib/format/LibFormatDecimalFloat.sol (1 hunks)
  • test/src/concrete/DecimalFloat.format.t.sol (1 hunks)
  • test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-02T09:33:32.513Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#119
File: test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol:174-175
Timestamp: 2025-09-02T09:33:32.513Z
Learning: The LibFormatDecimalFloat.toDecimalString function in src/lib/format/LibFormatDecimalFloat.sol does not include rounding logic. It formats decimal floats as-is without rounding values based on significant figures limits.

Applied to files:

  • src/lib/format/LibFormatDecimalFloat.sol
  • test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol
  • test/src/concrete/DecimalFloat.format.t.sol
  • src/concrete/DecimalFloat.sol
📚 Learning: 2025-08-21T18:03:40.347Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#107
File: test/lib/LibDecimalFloatSlow.sol:37-45
Timestamp: 2025-08-21T18:03:40.347Z
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:

  • test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol
📚 Learning: 2025-08-29T14:54:24.240Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#115
File: test/src/lib/LibDecimalFloat.decimal.t.sol:122-126
Timestamp: 2025-08-29T14:54:24.240Z
Learning: In test/src/lib/LibDecimalFloat.decimal.t.sol, the fromFixedDecimalLossy function converts uint256(type(int256).max) losslessly because the boundary check is `value > uint256(type(int256).max)`, not `>=`. Values exactly equal to type(int256).max still take the lossless conversion path.

Applied to files:

  • test/src/lib/format/LibFormatDecimalFloat.toDecimalString.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). (2)
  • GitHub Check: git-clean
  • GitHub Check: rainix (ubuntu-latest, rainix-rs-test)
🔇 Additional comments (5)
src/lib/format/LibFormatDecimalFloat.sol (1)

73-88: ...

test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol (3)

17-23: Helper update LGTM

Signature and call site match the new boolean API; assertion is clear.


26-33: Round‑trip helper LGTM

Parses then canonicalizes with the same scientific flag; consistent with “no rounding” formatting. Based on learnings.


94-104: Fuzz round‑trip coverage LGTM

Good bounds, positive/negative symmetry, and canonicalization for both paths.

Also applies to: 107-123

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

13-15: Mirror logic LGTM

Local wrapper faithfully mirrors the production boolean for scientific mode.

Copy link
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/concrete/DecimalFloat.sol (1)

70-78: Address the existing review feedback.

A previous review comment already identified improvements for this function:

  1. Extract the scientific boolean into a local variable for clarity
  2. Update NatSpec to specify that bounds are inclusive

Please refer to the existing review comment for the suggested changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea8c48e and f5a8cf0.

📒 Files selected for processing (2)
  • .gas-snapshot (2 hunks)
  • src/concrete/DecimalFloat.sol (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-02T09:33:32.513Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#119
File: test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol:174-175
Timestamp: 2025-09-02T09:33:32.513Z
Learning: The LibFormatDecimalFloat.toDecimalString function in src/lib/format/LibFormatDecimalFloat.sol does not include rounding logic. It formats decimal floats as-is without rounding values based on significant figures limits.

Applied to files:

  • src/concrete/DecimalFloat.sol
📚 Learning: 2025-08-21T18:03:40.347Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#107
File: test/lib/LibDecimalFloatSlow.sol:37-45
Timestamp: 2025-08-21T18:03:40.347Z
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:

  • .gas-snapshot
📚 Learning: 2025-08-29T14:54:24.240Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#115
File: test/src/lib/LibDecimalFloat.decimal.t.sol:122-126
Timestamp: 2025-08-29T14:54:24.240Z
Learning: In test/src/lib/LibDecimalFloat.decimal.t.sol, the fromFixedDecimalLossy function converts uint256(type(int256).max) losslessly because the boundary check is `value > uint256(type(int256).max)`, not `>=`. Values exactly equal to type(int256).max still take the lossless conversion path.

Applied to files:

  • .gas-snapshot
📚 Learning: 2025-09-28T13:28:35.814Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#144
File: test/src/lib/LibDecimalFloat.pow.t.sol:40-61
Timestamp: 2025-09-28T13:28:35.814Z
Learning: In test/src/lib/LibDecimalFloat.pow.t.sol, the diffLimit tolerance (currently 8.6%) represents a conservative worst-case bound to accommodate precision limitations of table lookup interpolation for fractional exponents in the pow function. The exponentiation-by-squaring approach used for integer parts achieves much tighter precision, but the tolerance must account for the less precise table-based calculations used for fractional exponents.

Applied to files:

  • .gas-snapshot
🔇 Additional comments (3)
.gas-snapshot (1)

1-345: Gas snapshot appropriately updated for new formatting API.

The updated metrics reflect the formatting changes introduced in this PR (range-based and boolean scientific notation formatting). The addition of testFormatConstants at line 13 aligns with the new FORMAT_DEFAULT_SCIENTIFIC_MIN and FORMAT_DEFAULT_SCIENTIFIC_MAX constants.

src/concrete/DecimalFloat.sol (2)

80-87: LGTM!

The boolean interface provides a clean, direct way to control scientific notation formatting when custom logic is needed beyond the range-based approach.


89-95: LGTM!

The default format function correctly delegates to the range-based format using the newly defined constants, providing a sensible default behavior for most use cases.

Copy link
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/concrete/DecimalFloat.sol (1)

71-79: Extract boolean flag and clarify inclusive bounds (duplicate of past review).

The past review's suggestions remain unaddressed:

  1. Readability: Extract bool scientific = a.lt(scientificMin) || a.gt(scientificMax); before calling toDecimalString to improve code clarity.

  2. NatSpec clarity: Update parameter documentation to explicitly state that bounds are inclusive:

    • @param scientificMin The smallest value formatted in non-scientific notation (inclusive).
    • @param scientificMax The largest value formatted in non-scientific notation (inclusive).

The logic is correct—values equal to the bounds will use non-scientific formatting.

Apply this diff:

     function format(Float a, Float scientificMin, Float scientificMax) public pure returns (string memory) {
         require(scientificMin.lt(scientificMax), "scientificMin must be less than scientificMax");
-        return LibFormatDecimalFloat.toDecimalString(a, a.lt(scientificMin) || a.gt(scientificMax));
+        bool scientific = a.lt(scientificMin) || a.gt(scientificMax);
+        return LibFormatDecimalFloat.toDecimalString(a, scientific);
     }

And update the NatSpec:

-    /// @param scientificMin The smallest number that won't be formatted in
-    /// scientific notation.
-    /// @param scientificMax The largest number that won't be formatted in
-    /// scientific notation.
+    /// @param scientificMin The smallest number that won't be formatted in scientific notation (inclusive).
+    /// @param scientificMax The largest number that won't be formatted in scientific notation (inclusive).
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5a8cf0 and 25ea7b3.

📒 Files selected for processing (1)
  • src/concrete/DecimalFloat.sol (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T09:33:32.513Z
Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#119
File: test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol:174-175
Timestamp: 2025-09-02T09:33:32.513Z
Learning: The LibFormatDecimalFloat.toDecimalString function in src/lib/format/LibFormatDecimalFloat.sol does not include rounding logic. It formats decimal floats as-is without rounding values based on significant figures limits.

Applied to files:

  • src/concrete/DecimalFloat.sol
🔇 Additional comments (1)
src/concrete/DecimalFloat.sol (1)

81-88: LGTM: Clean boolean interface.

The boolean overload provides a straightforward raw interface for scientific formatting, appropriately documented and correctly delegating to the underlying library function.

Comment on lines +12 to +20
/// The default minimum value for scientific formatting. 1e-4
// slither-disable-next-line too-many-digits
Float public constant FORMAT_DEFAULT_SCIENTIFIC_MIN =
Float.wrap(0xfffffffc00000000000000000000000000000000000000000000000000000001);

/// The default maximum value for scientific formatting. 1e9
// slither-disable-next-line too-many-digits
Float public constant FORMAT_DEFAULT_SCIENTIFIC_MAX =
Float.wrap(0x0000000900000000000000000000000000000000000000000000000000000001);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Constants have basic NatSpec—consider the optional enhancement from past review.

The constants now include NatSpec comments stating their purpose and numeric values, which is an improvement. However, the past review suggested more detailed documentation explaining the threshold behavior (e.g., "Values below this threshold will use scientific notation"). This enhancement would improve discoverability but is not critical.

🤖 Prompt for AI Agents
In src/concrete/DecimalFloat.sol around lines 12 to 20, the NatSpec for
FORMAT_DEFAULT_SCIENTIFIC_MIN and FORMAT_DEFAULT_SCIENTIFIC_MAX only states
numeric values; update each comment to clearly describe the threshold behavior
(e.g., that values strictly below FORMAT_DEFAULT_SCIENTIFIC_MIN and strictly
above FORMAT_DEFAULT_SCIENTIFIC_MAX will be rendered in scientific notation,
include the intended numeric thresholds like 1e-4 and 1e9 and any
inclusive/exclusive semantics), keep the short summary and numeric example, and
ensure wording is concise and consistent with surrounding documentation.

Comment on lines +90 to 96
/// Exposes `format(Float, Float, Float)` for offchain use.
/// Provides default scientific formatting.
/// @param a The float to format.
/// @return The string representation of the float.
function format(Float a, uint256 sigFigsLimit) external pure returns (string memory) {
return LibFormatDecimalFloat.toDecimalString(a, sigFigsLimit);
function format(Float a) external pure returns (string memory) {
return format(a, FORMAT_DEFAULT_SCIENTIFIC_MIN, FORMAT_DEFAULT_SCIENTIFIC_MAX);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider clarifying the default bounds in NatSpec.

The implementation correctly delegates to the range-based format with default constants. To improve discoverability, consider mentioning the actual default range in the NatSpec documentation (e.g., "Formats using default scientific bounds: 1e-4 to 1e9").

Optional diff:

-    /// Exposes `format(Float, Float, Float)` for offchain use.
-    /// Provides default scientific formatting.
+    /// Formats the float using default scientific notation bounds (1e-4 to 1e9).
+    /// Values outside this range use scientific notation.
     /// @param a The float to format.
     /// @return The string representation of the float.
📝 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
/// Exposes `format(Float, Float, Float)` for offchain use.
/// Provides default scientific formatting.
/// @param a The float to format.
/// @return The string representation of the float.
function format(Float a, uint256 sigFigsLimit) external pure returns (string memory) {
return LibFormatDecimalFloat.toDecimalString(a, sigFigsLimit);
function format(Float a) external pure returns (string memory) {
return format(a, FORMAT_DEFAULT_SCIENTIFIC_MIN, FORMAT_DEFAULT_SCIENTIFIC_MAX);
}
/// Formats the float using default scientific notation bounds (1e-4 to 1e9).
/// Values outside this range use scientific notation.
/// @param a The float to format.
/// @return The string representation of the float.
function format(Float a) external pure returns (string memory) {
return format(a, FORMAT_DEFAULT_SCIENTIFIC_MIN, FORMAT_DEFAULT_SCIENTIFIC_MAX);
}
🤖 Prompt for AI Agents
In src/concrete/DecimalFloat.sol around lines 90 to 96, the NatSpec for the
format(Float) wrapper doesn't state the actual default scientific bounds; update
the function comment to explicitly mention the default range (e.g., "Formats
using default scientific bounds: 1e-4 to 1e9") so callers can discover the
defaults without reading constants, keeping the implementation unchanged.

@thedavidmeister thedavidmeister merged commit 6ed2641 into main Oct 10, 2025
10 checks passed
@github-actions
Copy link

@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
Contributor

coderabbitai bot commented Oct 10, 2025

Tip

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

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.

3 participants