Skip to content

Conversation

@squadgazzz
Copy link
Contributor

@squadgazzz squadgazzz commented Jan 7, 2026

Description

Changes

  • Deduplicated the U256Ext trait implementations
  • Added unit tests for questionable functions
  • Implements suggestions from Extract U256Ext implementations into the numbers crate #4030
    • Start using the https://github.com/recmo/uint lib, which already implements a bunch of functions for alloy::U256.
    • Uses a proper div_ceil for alloy::U256.
    • Implements a safer checked_mul_f64.
    • Migrates from Result to Option for some functions since errors from results are not used in any case.

How to test

New unit tests + existing tests.

@squadgazzz squadgazzz requested a review from a team as a code owner January 7, 2026 20:30
@gemini-code-assist
Copy link

Summary of Changes

Hello @squadgazzz, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the U256Ext trait to enhance the precision and safety of large integer arithmetic operations. It integrates the ruint library to streamline alloy::U256 implementations, introduces a more robust checked_mul_f64 using arbitrary-precision rationals, and provides a proper ceiling division. These changes reduce code duplication, improve error handling by returning Option for conversions, and are thoroughly validated with new unit tests.

Highlights

  • Dependency Update: The ruint library has been introduced to enhance alloy::U256 operations, providing more robust and efficient large integer arithmetic capabilities.
  • Deduplication and Simplification: The U256Ext trait implementations have been refactored, particularly for AlloyU256, by leveraging ruint's features and providing default trait implementations, reducing code duplication.
  • Improved Arithmetic Functions: A proper div_ceil for alloy::U256 has been implemented, and a safer, arbitrary-precision checked_mul_f64 using BigRational is now available, addressing precision and safety concerns.
  • Enhanced Robustness in Conversions: Conversion methods from_big_int, from_big_uint, and from_big_rational now return Option<Self> instead of Result<Self>, providing clearer error handling for potential conversion failures.
  • New Unit Tests: Comprehensive unit tests have been added for checked_ceil_div and checked_mul_f64 across both AlloyU256 and PrimitiveU256 types to validate the new implementations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request is a great improvement. It refactors the U256Ext trait to deduplicate implementations and leverages the ruint library to simplify conversions and improve correctness, especially for checked_mul_f64 and checked_ceil_div for alloy::U256. The addition of unit tests is also very welcome.

I've found a few areas for improvement:

  • Some of the new implementations using .then(...).flatten() could be made more readable with simple if statements.

Overall, excellent work on this refactoring.

@squadgazzz squadgazzz marked this pull request as draft January 8, 2026 13:50
# Conflicts:
#	Cargo.lock
#	crates/number/src/u256_ext.rs
@squadgazzz squadgazzz marked this pull request as ready for review January 8, 2026 15:48
(!other.is_zero()).then(|| self.div_ceil(*other))
}

fn checked_mul_f64(&self, factor: f64) -> Option<Self> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if using BigRational instead of floats would be better/more accurate/less error prone

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But on the callee side, we use f64 everywhere. So it would not be really convenient to first convert it to BigRational or anything else.

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant internally to this function (wasn't very clear, sorry about that)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I assume we can't.
For example, with BigRational::from_float(0.2):

  • Returns something like 3602879701896397/18014398509481984 (the exact rational for f64's approximation of 0.2)
  • This propagates the imprecision through the calculation

Copy link
Contributor

Choose a reason for hiding this comment

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

This propagates the imprecision through the calculation

The calculation already has a very specific loss of precision because we use the SCALE factors to scale up and later scale down. I think doing a quick comparison between the current implementation and big rationals would still be good. If big rationals are okay the code would be a lot simpler.

Copy link
Contributor Author

@squadgazzz squadgazzz Jan 12, 2026

Choose a reason for hiding this comment

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

If I use this BigRational implementation:

fn checked_mul_f64_bigrational(value: &U256, factor: f64) -> Option<U256> {
    use num::Zero;

    if !factor.is_finite() || factor.is_sign_negative() {
        return None;
    }

    if factor.is_zero() {
        return Some(U256::ZERO);
    }

    // Convert f64 to BigRational - this captures the exact binary representation
    let factor_rational = BigRational::from_float(factor)?;

    // Convert U256 to BigRational
    let value_rational = value.to_big_rational();

    // Multiply
    let result_rational = value_rational * factor_rational;

    // Convert back to U256 (truncating)
    U256::from_big_rational(&result_rational)
}

The following test fails

    #[test]
    fn bigrational_is_not_decimal_safe_for_0_1() {
        // Intentional failing test: demonstrates that the BigRational variant
        // does not match decimal intent for a common factor.
        let value = U256::from(100_000_000_000_000_000_000u128); // 1e20
        let factor_str = "0.1";
        let factor = f64::from_str(factor_str).expect("valid factor");
        let scaled = value.checked_mul_f64(factor).expect("scaled");
        let big = checked_mul_f64_bigrational(&value, factor).expect("bigrational");

        assert_eq!(
            scaled.to_string(),
            big.to_string()
        );
    }

, with the error:

  left: "10000000000000000000"
 right: "10000000000000000555"

Copy link
Contributor

Choose a reason for hiding this comment

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

Was not expecting this, than can you leave a comment explaining why we're using using the bigrational approach? Might be useful for future reference

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a doc

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.

4 participants