While the original plan was to keep uint256 as a part of the primitives module, there has been interest in using the uint256 code now and the primitives module will not be complete and ready for release for quite some time. Also, after further discussion, the general preference is for a separate module that lives at math/uint256 for this code since the primitives module will ultimately be squarely aimed at Decred-specific things while math/uint256 is more generally useful, including many projects outside of Decred as well. Thus, this moves the new uint256 package from the internal staging area to math/uint256 where it will serve as a base for a new math/uint256 module release. Finally, it updates all relevant paths and package README.md accordingly.
13 KiB
uint256
Fixed Precision Unsigned 256-bit Integer Arithmetic
This package implements highly optimized allocation free fixed precision unsigned 256-bit integer arithmetic.
The following is a brief overview of the main features and benefits:
- Strong focus on performance and correctness
- Every operation is faster than the stdlib
big.Intequivalent and most operations, including the primary math operations, are significantly faster (see performance comparison benchmarks)
- Every operation is faster than the stdlib
- Allocation free
- All non-formatting operations with the specialized type are allocation free
- Supports boolean comparison, bitwise logic, and bitwise shift operations
- All operations are performed modulo 2^256
- Ergonomic API with unary-style arguments as well as some binary variants
- eg:
n.SetUint64(1).Lsh(64).SubUint64(1)is equivalent ton = 1<<64 - 1 - eg:
n.Div2(n1, n2)is equivalent ton = n1/n2
- eg:
- Conversion-free support for interoperation with native
uint64integers - Direct conversion to and from little and big endian byte arrays
- Full support for formatted output and common base conversions
- Producing formatted output uses fewer allocations than
big.Int
- Producing formatted output uses fewer allocations than
- 100% test coverage
- Comprehensive benchmarks
The primary big.Int operations that are NOT currently provided:
- Modular arithmetic with moduli other than 2^256
- Signed arithmetic
- A caller could fairly easily implement it with 2's complement if desired
since
Negateis provided
- A caller could fairly easily implement it with 2's complement if desired
since
- Remainders from division
These operations may be implemented in the future if the need arises.
Note About Standard Library math/big.Int Conversions
This package provides convenience methods for converting to and from standard
library big.Ints, however, callers should make an active effort to avoid that
unless it is absolutely necessary as conversion to big.Int requires an
unavoidable allocation in most circumstances and big.Int is also slower for
every operation, often to a significant degree. Further, big.Ints often cause
further allocations while performing arithmetic, notably multiplication and
division.
Regarding the aforementioned allocations, standard library big.Ints internally
require heap allocations to operate, so conversion to a new big.Int
necessarily causes an allocation. This means the ToBig method will always
incur at least one allocation.
One feature of big.Ints is that they attempt to reuse their internal buffer
when possible, so the number of allocations due to conversion can sometimes be
reduced if reusing the same variable is an option. This package provides a
PutBig method which allows callers to reuse an existing big.Int for this
purpose.
Do note however that even when reusing a big.Int, it will naturally still
require an allocation for the internal buffer unless it has already previously
allocated one large enough to be reused.
Categorized Uint256 Summary
This section summarizes the majority of the available operations provided by category. See the full documentation for additional details about individual methods and additional available methods.
Arithmetic Methods
| Operation | Native Equiv | Methods |
|---|---|---|
| Add Assign | n += x |
Add, AddUint64 |
| Add | n = x + y |
Add2 |
| Subtract Assign | n -= x |
Sub, SubUint64 |
| Subtract | n = x - y |
Sub2 |
| Multiply Assign | n *= x |
Mul, MulUint64 |
| Multiply | n = x * y |
Mul2 |
| Divide Assign | n /= x |
Div |
| Divide | n = x / y |
Div2 |
| Square Assign | n *= n |
Square |
| Square | n = x * x |
SquareVal |
| Negate Assign | n = -n |
Negate |
| Negate | n = -x |
NegateVal |
Comparison Methods
| Operation | Native Equiv | Methods |
|---|---|---|
| Equality | x == y |
Eq, EqUint64 |
| Less Than | x < y |
Lt, LtUint64 |
| Less Or Equal | x <= y |
LtEq, LtEqUint64 |
| Greater Than | x > y |
Gt, GtUint64 |
| Greater Or Equal | x >= y |
GtEq, GtEqUint64 |
| Comparison | n/a | Cmp, CmpUint64 |
Bitwise Methods
| Operation | Native Equiv | Method |
|---|---|---|
| And Assign | n &= x |
And |
| Or Assign | n |= x |
Or |
| Xor Assign | n ^= x |
Xor |
| Not Assign | n = ^x |
Not |
| Left Shift Assign | n <<= x |
Lsh |
| Left Shift | n = x << y |
LshVal |
| Right Shift Assign | n >>= x |
Rsh |
| Right Shift | n = x >> y |
RshVal |
| Bit Length | n/a | BitLen |
Conversion Methods
| Operation | Methods |
|---|---|
| From Big Endian | SetBytes, SetByteSlice |
| To Big Endian | Bytes, PutBytes, PutBytesUnchecked |
| From Little Endian | SetBytesLE, SetByteSliceLE |
| To Little Endian | BytesLE, PutBytesLE, PutBytesUncheckedLE |
From math/big.Int |
SetBig |
To math/big.Int |
ToBig, PutBig |
Misc Convenience Methods
| Operation | Method |
|---|---|
Can represent with uint32? |
IsUint32 |
| Value modulo 2^32 | Uint32 |
Can represent with uint64? |
IsUint64 |
| Value modulo 2^64 | Uint64 |
| Set to 0 | Zero |
| Is equal to zero? | IsZero |
| Is the value odd? | IsOdd |
Output Formatting Methods
| Operation | Method |
|---|---|
| Binary/Octal/Decimal/Hex | Text |
Standard Formatted output (fmt.Formatter) |
Format |
Standard Unformatted output (fmt.Stringer) |
String |
Uint256 Performance Comparison
The following benchmark results demonstrate the performance of most operations
as compared to standard library big.Ints. The benchmarks are from a Ryzen 7
1700 processor and are the result of feeding benchstat 10 iterations of each.
Arithmetic Methods
| Name | big.Int Time/Op |
Uint256 Time/Op |
Delta vs big.Int |
|---|---|---|---|
| Add | 158ns ± 2% | 2ns ± 1% | -98.67% |
| AddUint64 | 44.4ns ± 3% | 3.4ns ± 2% | -92.27% |
| Sub | 53.9ns ± 1% | 2.1ns ± 1% | -96.12% |
| SubUint64 | 44.8ns ± 1% | 3.4ns ± 2% | -92.37% |
| Mul | 419ns ± 1% | 10ns ± 2% | -97.64% |
| MulUint64 | 263ns ± 1% | 4ns ± 1% | -98.30% |
| Square | 418ns ± 0% | 7ns ± 2% | -98.39% |
| Div/num_lt_den | 75.4ns ± 1% | 3.4ns ± 1% | -95.51% |
| Div/num_eq_den | 253ns ± 2% | 4ns ± 3% | -98.56% |
| Div/1_by_1_near | 53.8ns ± 2% | 4.5ns ± 2% | -91.63% |
| Div/1_by_1_far | 31.4ns ± 2% | 14.6ns ± 2% | -53.64% |
| Div/2_by_1_near | 36.9ns ± 1% | 10.1ns ± 2% | -72.63% |
| Div/2_by_1_far | 49.1ns ± 1% | 28.8ns ± 1% | -41.29% |
| Div/3_by_1_near | 43.2ns ± 1% | 13.7ns ± 3% | -68.24% |
| Div/3_by_1_far | 57.0ns ± 1% | 43.6ns ± 1% | -23.59% |
| Div/4_by_1_near | 49.7ns ± 4% | 18.0ns ± 1% | -63.87% |
| Div/4_by_1_far | 65.2ns ± 4% | 57.8ns ± 2% | -11.41% |
| Div/2_by_2_near | 237ns ± 1% | 22ns ± 3% | -90.81% |
| Div/2_by_2_far | 237ns ± 1% | 30ns ± 3% | -87.17% |
| Div/3_by_2_near | 258ns ± 1% | 29ns ± 1% | -88.60% |
| Div/3_by_2_far | 257ns ± 1% | 50ns ± 2% | -80.42% |
| Div/4_by_2_near | 312ns ± 2% | 40ns ± 3% | -87.27% |
| Div/4_by_2_far | 310ns ± 1% | 71ns ± 3% | -77.19% |
| Div/3_by_3_near | 239ns ± 2% | 21ns ± 2% | -91.39% |
| Div/3_by_3_far | 242ns ± 4% | 33ns ± 3% | -86.33% |
| Div/4_by_3_near | 279ns ± 6% | 31ns ± 1% | -89.01% |
| Div/4_by_3_far | 271ns ± 1% | 46ns ± 3% | -82.99% |
| Div/4_by_4_near | 252ns ± 3% | 20ns ± 3% | -91.99% |
| Div/4_by_4_far | 249ns ± 2% | 36ns ± 2% | -85.65% |
| DivRandom | 202ns ± 1% | 23ns ± 1% | -88.43% |
| DivUint64 | 129ns ± 1% | 47ns ± 0% | -63.34% |
| Negate | 47.3ns ± 2% | 1.5ns ± 2% | -96.91% |
Comparison Methods
| Name | big.Int Time/Op |
Uint256 Time/Op |
Delta vs big.Int |
|---|---|---|---|
| Eq | 12.7ns ± 1% | 2.1ns ± 1% | -83.72% |
| Lt | 12.6ns ± 1% | 3.0ns ± 1% | -75.96% |
| Gt | 12.6ns ± 1% | 3.0ns ± 1% | -75.91% |
| Cmp | 12.6ns ± 1% | 7.7ns ± 1% | -39.01% |
| CmpUint64 | 5.93ns ± 2% | 3.70ns ± 1% | -37.60% |
Bitwise Methods
| Name | big.Int Time/Op |
Uint256 Time/Op |
Delta vs big.Int |
|---|---|---|---|
| Lsh/bits_0 | 7.15ns ± 3% | 2.58ns ± 1% | -63.94% |
| Lsh/bits_1 | 14.8ns ± 1% | 4.2ns ± 1% | -71.40% |
| Lsh/bits_64 | 16.7ns ± 1% | 2.7ns ± 1% | -84.00% |
| Lsh/bits_128 | 16.9ns ± 2% | 2.7ns ± 0% | -84.21% |
| Lsh/bits_192 | 16.6ns ± 1% | 2.6ns ± 1% | -84.19% |
| Lsh/bits_255 | 16.3ns ± 2% | 2.8ns ± 2% | -83.11% |
| Lsh/bits_256 | 16.9ns ± 2% | 2.6ns ± 2% | -84.77% |
| Rsh/bits_0 | 8.76ns ± 2% | 2.57ns ± 1% | -70.63% |
| Rsh/bits_1 | 14.4ns ± 2% | 4.3ns ± 2% | -70.28% |
| Rsh/bits_64 | 12.8ns ± 1% | 2.9ns ± 2% | -77.31% |
| Rsh/bits_128 | 11.8ns ± 0% | 2.9ns ± 2% | -75.51% |
| Rsh/bits_192 | 10.5ns ± 2% | 2.6ns ± 1% | -75.17% |
| Rsh/bits_255 | 10.5ns ± 3% | 2.8ns ± 2% | -73.89% |
| Rsh/bits_256 | 5.50ns ± 1% | 2.58ns ± 2% | -53.15% |
| Not | 25.4ns ± 2% | 3.3ns ± 2% | -86.79% |
| Or | 17.9ns ± 5% | 3.4ns ± 6% | -80.94% |
| And | 16.7ns ± 2% | 3.4ns ± 0% | -79.93% |
| Xor | 17.9ns ± 1% | 3.4ns ± 2% | -80.91% |
| BitLen/bits_64 | 2.24ns ± 1% | 1.94ns ± 3% | -13.04% |
| BitLen/bits_128 | 2.25ns ± 2% | 1.96ns ± 2% | -13.17% |
| BitLen/bits_192 | 2.25ns ± 1% | 1.60ns ± 1% | -28.65% |
| BitLen/bits_255 | 2.26ns ± 2% | 1.61ns ± 1% | -29.04% |
Conversion Methods
| Name | big.Int Time/Op |
Uint256 Time/Op |
Delta vs big.Int |
|---|---|---|---|
| SetBytes | 9.09ns ±13% | 3.05ns ± 1% | -66.43% |
| SetBytesLE | 59.9ns ± 4% | 3.1ns ± 2% | -94.76% |
| Bytes | 61.3ns ± 1% | 13.8ns ± 3% | -77.49% |
| BytesLE | 83.5ns ± 2% | 13.9ns ± 2% | -83.32% |
Misc Convenience Methods
| Name | big.Int Time/Op |
Uint256 Time/Op |
Delta vs big.Int |
|---|---|---|---|
| Zero | 2.99ns ± 2% | 1.29ns ± 1% | -56.82% |
| IsZero | 1.78ns ± 0% | 1.63ns ± 2% | -8.23% |
| IsOdd | 3.62ns ± 4% | 1.64ns ± 1% | -54.65% |
Output Formatting Methods
| Name | big.Int Time/Op |
Uint256 Time/Op |
Delta vs big.Int |
|---|---|---|---|
| Text/base_2 | 579ns ± 3% | 496ns ± 2% | -14.37% |
| Text/base_8 | 266ns ± 1% | 227ns ± 1% | -14.58% |
| Text/base_10 | 536ns ± 1% | 458ns ± 2% | -14.58% |
| Text/base_16 | 205ns ± 2% | 180ns ± 4% | -11.90% |
| Format/base_2 | 987ns ±15% | 852ns ± 2% | -13.64% |
| Format/base_8 | 620ns ± 6% | 544ns ± 3% | -12.31% |
| Format/base_10 | 888ns ± 1% | 726ns ± 1% | -18.25% |
| Format/base_16 | 565ns ± 1% | 449ns ± 1% | -20.41% |
Installation and Updating
This package is part of the github.com/decred/dcrd/math/uint256 module. Use
the standard go tooling for working with modules to incorporate it.
Examples
- Basic Usage
Demonstrates calculating the result of a dividing a max unsigned 256-bit integer by a max unsigned 128-bit integer and outputting that result in hex with leading zeros.
License
Package uint256 is licensed under the copyfree ISC License.