Skip to content

exp op#393

Merged
thedavidmeister merged 1 commit into
mainfrom
2025-08-06-exp
Aug 7, 2025
Merged

exp op#393
thedavidmeister merged 1 commit into
mainfrom
2025-08-06-exp

Conversation

@thedavidmeister
Copy link
Copy Markdown
Contributor

@thedavidmeister thedavidmeister commented Aug 7, 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

    • Re-enabled the "exp" (natural exponential) opcode, making it available as part of the standard operations set.
  • Improvements

    • Upgraded the "exp" operation to use higher-precision decimal floating-point calculations for more accurate results.
  • Bug Fixes

    • Enhanced test coverage and precision for the "exp" operation, including improved handling of edge cases.
  • Tests

    • Updated and expanded tests for the "exp" operation to reflect the new floating-point implementation and increased accuracy.
    • Improved test reliability for the square root operation by excluding invalid input values.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 7, 2025

Walkthrough

The changes re-enable and fully integrate the "exp" (natural exponential) opcode into the standard operations set. This includes updating function pointers, metadata, and tests to use a new decimal floating-point implementation. The test suite and library now utilize updated operand and stack item types, and the exponential function is computed using LibDecimalFloat.

Changes

Cohort / File(s) Change Summary
Standard Ops Integration
src/lib/op/LibAllStandardOps.sol
Re-enables the "exp" opcode: imports LibOpExp, increases opcode count, updates metadata, and function pointer arrays to include the exponential operation using new handler, integrity, and run functions.
Exponential Opcode Implementation
src/lib/op/math/LibOpExp.sol
Migrates the exponential function to use decimal floating point (LibDecimalFloat), updates function signatures to new operand and stack types, changes run to view, adds a new reference function, and removes legacy fixed-point code.
Exponential Opcode Tests
test/src/lib/op/math/LibOpExp.t.sol
Updates and uncomments all tests for the "exp" opcode to use the new decimal float types, operand abstractions, and stack item handling. Test logic and expected values are adapted for higher-precision floating-point calculations and new operand types.
Sqrt Opcode Test Adjustment
test/src/lib/op/math/LibOpSqrt.t.sol
Adds a precondition to the square root test to avoid running with the minimal negative float value. No changes to function signatures or external behavior.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Interpreter
    participant LibAllStandardOps
    participant LibOpExp
    participant LibDecimalFloat

    User->>Interpreter: Executes "exp" opcode
    Interpreter->>LibAllStandardOps: Lookup function pointers for "exp"
    LibAllStandardOps->>LibOpExp: Calls run() with OperandV2 and stackTop
    LibOpExp->>LibDecimalFloat: Computes FLOAT_E.pow(input, logTables)
    LibDecimalFloat-->>LibOpExp: Returns result
    LibOpExp-->>Interpreter: Returns new stackTop with result
    Interpreter-->>User: Returns computation result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • 2025 07 12 mul #355: Re-enables and integrates the "mul" opcode with updates to LibOpMul and its tests, analogous to this PR's handling of the "exp" opcode.
  • e constant #375: Re-enables and integrates the "e" opcode with LibOpE and updates its function pointers and metadata, similar in structure to this PR but for a different math opcode.
  • bump int #391: Re-enables and integrates the "sqrt" opcode and updates LibOpSqrt, modifying the same core files and patterns as this PR but for the square root operation.

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-08-06-exp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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: 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 dad1487 and d64976f.

⛔ Files ignored due to path filters (3)
  • src/generated/Rainterpreter.pointers.sol is excluded by !**/generated/**
  • src/generated/RainterpreterExpressionDeployer.pointers.sol is excluded by !**/generated/**
  • src/generated/RainterpreterParser.pointers.sol is excluded by !**/generated/**
📒 Files selected for processing (4)
  • src/lib/op/LibAllStandardOps.sol (6 hunks)
  • src/lib/op/math/LibOpExp.sol (1 hunks)
  • test/src/lib/op/math/LibOpExp.t.sol (1 hunks)
  • test/src/lib/op/math/LibOpSqrt.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: thedavidmeister
PR: rainlanguage/rain.interpreter#360
File: src/lib/op/erc20/LibOpERC20Allowance.sol:0-0
Timestamp: 2025-07-15T11:31:28.010Z
Learning: In the rainlanguage/rain.interpreter project, forge (Foundry's formatting tool) handles code formatting automatically, so formatting-related suggestions are not actionable.
Learnt from: thedavidmeister
PR: rainlanguage/rain.interpreter#360
File: src/lib/op/erc20/LibOpERC20Allowance.sol:35-36
Timestamp: 2025-07-15T11:31:10.098Z
Learning: In the rain.interpreter codebase, when working with ERC20 tokens that may not implement the optional decimals() function, the preference is to let the call fail explicitly rather than catching errors and defaulting to 18 decimals. This ensures correctness by avoiding potentially incorrect calculations with assumed decimal values.
📚 Learning: in multiplication overflow detection tests like libopuint256multest, when performing sequential mult...
Learnt from: thedavidmeister
PR: rainlanguage/rain.interpreter#368
File: test/src/lib/op/math/uint256/LibOpUint256Mul.t.sol:56-69
Timestamp: 2025-07-17T14:15:14.886Z
Learning: In multiplication overflow detection tests like LibOpUint256MulTest, when performing sequential multiplication (a * b * c * d...), encountering a zero value means the final result will always be zero regardless of subsequent values. Since zero multiplied by any value (including MAX_UINT256) cannot overflow, it's safe and correct to break out of the overflow detection loop early when zero is encountered.

Applied to files:

  • test/src/lib/op/math/LibOpSqrt.t.sol
  • test/src/lib/op/math/LibOpExp.t.sol
📚 Learning: in the rain.interpreter codebase, when working with erc20 tokens that may not implement the optional...
Learnt from: thedavidmeister
PR: rainlanguage/rain.interpreter#360
File: src/lib/op/erc20/LibOpERC20Allowance.sol:35-36
Timestamp: 2025-07-15T11:31:10.098Z
Learning: In the rain.interpreter codebase, when working with ERC20 tokens that may not implement the optional decimals() function, the preference is to let the call fail explicitly rather than catching errors and defaulting to 18 decimals. This ensures correctness by avoiding potentially incorrect calculations with assumed decimal values.

Applied to files:

  • src/lib/op/math/LibOpExp.sol
📚 Learning: in rain interpreter stack operations like libopevery, when the output position (stacktop) is set to ...
Learnt from: thedavidmeister
PR: rainlanguage/rain.interpreter#381
File: src/lib/op/logic/LibOpEvery.sol:24-46
Timestamp: 2025-07-27T22:56:57.928Z
Learning: In Rain interpreter stack operations like LibOpEvery, when the output position (stackTop) is set to coincide with an input item's position on the stack, explicit writing may not be needed if the desired output value is already at that position. The function can return the pointer to that position directly, leveraging the existing stack layout.

Applied to files:

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

23-34: Consider edge case handling for exponential function

The implementation delegates all computation to LibDecimalFloat.FLOAT_E.pow without any input validation. Consider whether edge cases need explicit handling:

  • Very large positive inputs that could overflow
  • Very large negative inputs that approach zero
  • Special float values (NaN, infinity)

The change from pure to view is appropriate given the external log tables access.


28-28: Verify log tables address initialization

The LibDecimalFloat.LOG_TABLES_ADDRESS constant comes from an external library and must point to a deployed log-tables contract in every environment (e.g., testnets, mainnet). Please confirm that your library linking or deployment/migration scripts correctly supply this address.

Usage to double-check:

  • src/lib/op/math/LibOpExp.sol at lines 28 & 43
src/lib/op/LibAllStandardOps.sol (1)

79-79: LGTM! Complete integration of exp opcode

The exp opcode has been properly integrated into the standard operations set:

  • Import statement added
  • ALL_STANDARD_OPS_LENGTH correctly incremented
  • All required function pointers (metadata, operand handler, integrity, run) properly configured
  • Alphabetical ordering maintained

Also applies to: 115-115, 276-276, 475-476, 607-607, 718-718

Comment on lines +24 to +26
function testOpExpRun(int224 signedCoefficientA, int32 exponentA, uint16 operandData) public view {
signedCoefficientA = int224(bound(signedCoefficientA, 0, 10000));
exponentA = int32(bound(exponentA, -10, 5));
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 expanding fuzz test bounds and adding negative input tests

The fuzz test bounds are quite restrictive:

  • Coefficient is bounded to [0, 10000] - consider testing negative values as e^x is defined for all real x
  • Exponent range [-10, 5] might miss edge cases with larger magnitudes

Also consider adding explicit test cases for:

  • Negative inputs (e.g., exp(-1), exp(-2))
  • Edge cases near overflow/underflow boundaries
-        signedCoefficientA = int224(bound(signedCoefficientA, 0, 10000));
-        exponentA = int32(bound(exponentA, -10, 5));
+        signedCoefficientA = int224(bound(signedCoefficientA, -100000, 100000));
+        exponentA = int32(bound(exponentA, -20, 10));
📝 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
function testOpExpRun(int224 signedCoefficientA, int32 exponentA, uint16 operandData) public view {
signedCoefficientA = int224(bound(signedCoefficientA, 0, 10000));
exponentA = int32(bound(exponentA, -10, 5));
function testOpExpRun(int224 signedCoefficientA, int32 exponentA, uint16 operandData) public view {
- signedCoefficientA = int224(bound(signedCoefficientA, 0, 10000));
- exponentA = int32(bound(exponentA, -10, 5));
+ signedCoefficientA = int224(bound(signedCoefficientA, -100000, 100000));
+ exponentA = int32(bound(exponentA, -20, 10));
🤖 Prompt for AI Agents
In test/src/lib/op/math/LibOpExp.t.sol around lines 24 to 26, the fuzz test
bounds are too narrow and exclude negative coefficients, limiting test coverage.
Expand the bounds for signedCoefficientA to include negative values, for example
from a negative lower bound to a positive upper bound, and increase the
exponentA range to cover larger magnitudes both positive and negative.
Additionally, add explicit test cases for negative inputs such as exp(-1) and
exp(-2), and include tests targeting edge cases near overflow and underflow
boundaries to ensure robustness.

Comment on lines +40 to +62
function testOpExpEval() external view {
checkHappy("_: exp(0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "e^0");
checkHappy(
"_: exp(1);",
Float.unwrap(LibDecimalFloat.packLossless(271828182845904523536028747135266249770000, -41)),
"e^1"
);
checkHappy(
"_: exp(0.5);",
Float.unwrap(LibDecimalFloat.packLossless(164864091422952261768014373567633124880000, -41)),
"e^0.5"
);
checkHappy(
"_: exp(2);",
Float.unwrap(LibDecimalFloat.packLossless(73901273138361809414411498854106499908000, -40)),
"e^2"
);
checkHappy(
"_: exp(3);",
Float.unwrap(LibDecimalFloat.packLossless(200884548537713570608086241405798749310000, -40)),
"e^3"
);
}
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)

Add test cases for negative inputs

The eval tests only cover positive inputs. Consider adding test cases for negative values to ensure the exponential function handles them correctly:

         checkHappy(
             "_: exp(3);",
             Float.unwrap(LibDecimalFloat.packLossless(200884548537713570608086241405798749310000, -40)),
             "e^3"
         );
+        checkHappy(
+            "_: exp(-1);",
+            Float.unwrap(LibDecimalFloat.packLossless(367897834377610754067267193921935356200000, -42)),
+            "e^-1"
+        );
+        checkHappy(
+            "_: exp(-2);",
+            Float.unwrap(LibDecimalFloat.packLossless(135263130223700958314976198650936531970000, -42)),
+            "e^-2"
+        );
📝 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
function testOpExpEval() external view {
checkHappy("_: exp(0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "e^0");
checkHappy(
"_: exp(1);",
Float.unwrap(LibDecimalFloat.packLossless(271828182845904523536028747135266249770000, -41)),
"e^1"
);
checkHappy(
"_: exp(0.5);",
Float.unwrap(LibDecimalFloat.packLossless(164864091422952261768014373567633124880000, -41)),
"e^0.5"
);
checkHappy(
"_: exp(2);",
Float.unwrap(LibDecimalFloat.packLossless(73901273138361809414411498854106499908000, -40)),
"e^2"
);
checkHappy(
"_: exp(3);",
Float.unwrap(LibDecimalFloat.packLossless(200884548537713570608086241405798749310000, -40)),
"e^3"
);
}
function testOpExpEval() external view {
checkHappy("_: exp(0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "e^0");
checkHappy(
"_: exp(1);",
Float.unwrap(LibDecimalFloat.packLossless(271828182845904523536028747135266249770000, -41)),
"e^1"
);
checkHappy(
"_: exp(0.5);",
Float.unwrap(LibDecimalFloat.packLossless(164864091422952261768014373567633124880000, -41)),
"e^0.5"
);
checkHappy(
"_: exp(2);",
Float.unwrap(LibDecimalFloat.packLossless(73901273138361809414411498854106499908000, -40)),
"e^2"
);
checkHappy(
"_: exp(3);",
Float.unwrap(LibDecimalFloat.packLossless(200884548537713570608086241405798749310000, -40)),
"e^3"
);
+ checkHappy(
+ "_: exp(-1);",
+ Float.unwrap(LibDecimalFloat.packLossless(367897834377610754067267193921935356200000, -42)),
+ "e^-1"
+ );
+ checkHappy(
+ "_: exp(-2);",
+ Float.unwrap(LibDecimalFloat.packLossless(135263130223700958314976198650936531970000, -42)),
+ "e^-2"
+ );
}
🤖 Prompt for AI Agents
In test/src/lib/op/math/LibOpExp.t.sol between lines 40 and 62, the
testOpExpEval function only tests positive inputs for the exponential function.
Add additional test cases with negative input values such as -1, -0.5, and -2 to
verify that the exponential function correctly handles negative exponents. Use
the same checkHappy pattern with appropriate expected results for these negative
inputs.


/// Directly test the runtime logic of LibOpSqrt.
function testOpSqrtRun(Float a) public view {
vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
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.

💡 Verification agent

🧩 Analysis chain

Document why FLOAT_MIN_NEGATIVE_VALUE is excluded from testing

The test excludes LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE from the fuzz inputs. This edge case exclusion could mask potential bugs in the sqrt implementation. Please add a comment explaining why this specific value needs to be excluded, or consider handling it properly in the implementation.


🏁 Script executed:

#!/bin/bash
# Check if FLOAT_MIN_NEGATIVE_VALUE is handled consistently across other math operations
ast-grep --pattern 'FLOAT_MIN_NEGATIVE_VALUE' 

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for all references to FLOAT_MIN_NEGATIVE_VALUE in the repo
rg -n "FLOAT_MIN_NEGATIVE_VALUE" .

# Locate its definition in the LibDecimalFloat contract
rg -n "constant FLOAT_MIN_NEGATIVE_VALUE" -t sol

Length of output: 496


🏁 Script executed:

#!/bin/bash
# Locate the Solidity file defining LibDecimalFloat
rg -l "library LibDecimalFloat" --glob '*.sol'

# Search for FLOAT_MIN_NEGATIVE_VALUE definition in all .sol files
rg -n "FLOAT_MIN_NEGATIVE_VALUE" --glob '*.sol'

Length of output: 473


🏁 Script executed:

#!/bin/bash
# Display the sqrt test with context around the assume
sed -n '1,80p' test/src/lib/op/math/LibOpSqrt.t.sol

# Locate and display the implementation of LibOpSqrt
file=$(fd LibOpSqrt.sol --search-path src)
if [ -n "$file" ]; then
  echo "Implementation file: $file"
  sed -n '1,80p' "$file"
else
  echo "LibOpSqrt.sol not found"
fi

Length of output: 4913


Add a comment explaining why the smallest negative float is excluded

The line in testOpSqrtRun excludes LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE, likely because calling .abs() on the smallest negative value overflows or is undefined. Please document this edge-case in the test (or, if you intend to support it, handle it in the implementation and add a test). For example:

     /// Directly test the runtime logic of LibOpSqrt.
     function testOpSqrtRun(Float a) public view {
-        vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
+        // Exclude the minimum negative float because abs(min) cannot be represented
+        vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
         a = a.abs();
         InterpreterState memory state = opTestDefaultInterpreterState();
         …

• File: test/src/lib/op/math/LibOpSqrt.t.sol
Line: 27

📝 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
vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
/// Directly test the runtime logic of LibOpSqrt.
function testOpSqrtRun(Float a) public view {
- vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
+ // Exclude the minimum negative float because abs(min) cannot be represented
+ vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
a = a.abs();
InterpreterState memory state = opTestDefaultInterpreterState();
🤖 Prompt for AI Agents
In test/src/lib/op/math/LibOpSqrt.t.sol at line 27, add a comment explaining
that the smallest negative float is excluded because taking its absolute value
causes overflow or undefined behavior. This clarifies the reason for the
vm.assume condition and documents the edge case for future reference.

@thedavidmeister thedavidmeister merged commit f7471f5 into main Aug 7, 2025
11 checks passed
This was referenced Aug 7, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Oct 5, 2025
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