Skip to content

Conversation

@thomas-quadratic
Copy link
Contributor

@thomas-quadratic thomas-quadratic commented Jan 23, 2026

PR description

This PR cumulates a few improvements/refactoring for UInt256 modular arithmetics:

  • digits (limbs) are big-endian ordered.
  • digits use longs rather than ints as primitive type.
  • UInt256 is a record storing its digits as fields rather than in an array.
  • division algorithm follows methods from the GMP division paper since widening from long is not possible.
  • fromBytesBE is improved to give better performance on worst cases.

Long limbs are more efficient because they divide by 2 the number of digits, thus reducing the number of steps in arithmetics operations. However, it complexifies the implementation for division as widening is not possible anymore. Fortunately, quotient estimates algorithms that avoids widening exists, see division paper.

Records are chosen to represent fixed-width UInt256 because of potential future support for Vahalla value records, which would require almost no change to the code.

Here are the benchmarks compared to the actual implementation:

Operation Case current (ns/op) new (ns/op) gain (%)
Mod FULL_RANDOM 106.042 84.005 21%
Mod WORST 149.179 95.7 36%

Current status

Currently working and tested ops:

  • AddMod
  • Mod
  • MulMod
  • SMod

Thanks for sending a pull request! Have you done the following?

  • Checked out our contribution guidelines?
  • Considered documentation and added the doc-change-required label to this PR if updates are required.
  • Considered the changelog and included an update if required.
  • For database changes (e.g. KeyValueSegmentIdentifier) considered compatibility and performed forwards and backwards compatibility tests

Locally, you can run these tests to catch failures early:

  • spotless: ./gradlew spotlessApply
  • unit tests: ./gradlew build
  • acceptance tests: ./gradlew acceptanceTest
  • integration tests: ./gradlew integrationTest
  • reference tests: ./gradlew ethereum:referenceTests:referenceTests
  • hive tests: Engine or other RPCs modified?

Before, limbs were stored in little-endian.
But to use Arrays.mismatch to our advantage, it is better to have it big-endian.
This commit makes UInt256.java big-endian in limbs.
We still need to migrate all tests and benchmark.

Signed-off-by: Thomas Zamojski <[email protected]>
Also added tests that were failing and now pass.

Signed-off-by: Thomas Zamojski <[email protected]>
Signed-off-by: Thomas Zamojski <[email protected]>
Small cleaning up of the private methods for addition and compareLimbs.
Should be easier for the compiler.

Signed-off-by: Thomas Zamojski <[email protected]>
UInt256 used int[] for limbs, primarily for simplicity, e.g. having the possibility to widen to long.
However, methods exists to work with long[] and no widening. This commit implements long limbs.

To avoid widening, we do:

  1. add: overflow check
  2. mul: native multiplyHigh (compiled to assembly mulq)
  3. div: more complicated, see the gnump division paper.

Signed-off-by: Thomas Zamojski <[email protected]>
Signed-off-by: Thomas Zamojski <[email protected]>
Signed-off-by: Thomas Zamojski <[email protected]>
@thomas-quadratic thomas-quadratic changed the title Feat/uint256 as record UInt256 long digits record Jan 23, 2026
Signed-off-by: Thomas Zamojski <[email protected]>
Signed-off-by: Thomas Zamojski <[email protected]>
@thomas-quadratic
Copy link
Contributor Author

Updated benchmarks with the new commits that support the other operations: AddMod/MulMod/SMod

Operation Case current (ns/op) new (ns/op) gain (%)
Mod FULL_RANDOM 106.042 84.005 21%
Mod WORST 149.179 95.7 36%
SMod FULL_RANDOM 141.778 101.1 29%
SMod WORST 170.822 101.1 41%
AddMod FULL_RANDOM 179.559 137.073 24%
AddMod WORST 187.805 137.073 27%
MulMod FULL_RANDOM 259.265 186.501 29%
MulMod WORST 344.663 222.37 41%

Signed-off-by: Thomas Zamojski <[email protected]>
@thomas-quadratic
Copy link
Contributor Author

Changing single digit quotient estimates using 3 digits by 2 digits (div3by2) to 2 digits by 1 digit (div2by1).
Improved benchmarks across the board:

Operation Case current (ns/op) new (ns/op) gain (%)
Mod FULL_RANDOM 106.042 81.032 24%
Mod WORST 149.179 86.942 42%
SMod FULL_RANDOM 141.778 97.15 31%
SMod WORST 170.822 97.15 43%
AddMod FULL_RANDOM 179.559 130.147 28%
AddMod WORST 187.805 130.147 31%
MulMod FULL_RANDOM 259.265 177.442 32%
MulMod WORST 344.663 209.105 39%

Signed-off-by: Thomas Zamojski <[email protected]>
@thomas-quadratic thomas-quadratic marked this pull request as ready for review January 30, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants