packages/contracts-core/contracts/libs/stack/Number.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
/// Number is a compact representation of uint256, that is fit into 16 bits
/// with the maximum relative error under 0.4%.
type Number is uint16;
using NumberLib for Number global;
/// # Number
/// Library for compact representation of uint256 numbers.
/// - Number is stored using mantissa and exponent, each occupying 8 bits.
/// - Numbers under 2**8 are stored as `mantissa` with `exponent = 0xFF`.
/// - Numbers at least 2**8 are approximated as `(256 + mantissa) << exponent`
/// > - `0 <= mantissa < 256`
/// > - `0 <= exponent <= 247` (`256 * 2**248` doesn't fit into uint256)
/// # Number stack layout (from highest bits to lowest)
///
/// | Position | Field | Type | Bytes |
/// | ---------- | -------- | ----- | ----- |
/// | (002..001] | mantissa | uint8 | 1 |
/// | (001..000] | exponent | uint8 | 1 |
library NumberLib {
/// @dev Amount of bits to shift to mantissa field
uint16 private constant SHIFT_MANTISSA = 8;
/// @notice For bwad math (binary wad) we use 2**64 as "wad" unit.
/// @dev We are using not using 10**18 as wad, because it is not stored precisely in NumberLib.
uint256 internal constant BWAD_SHIFT = 64;
uint256 internal constant BWAD = 1 << BWAD_SHIFT;
/// @notice ~0.1% in bwad units.
uint256 internal constant PER_MILLE_SHIFT = BWAD_SHIFT - 10;
uint256 internal constant PER_MILLE = 1 << PER_MILLE_SHIFT;
/// @notice Compresses uint256 number into 16 bits.
function compress(uint256 value) internal pure returns (Number) {
// Find `msb` such as `2**msb <= value < 2**(msb + 1)`
uint256 msb = mostSignificantBit(value);
// We want to preserve 9 bits of precision.
// The highest bit is always 1, so we can skip it.
// The remaining 8 highest bits are stored as mantissa.
if (msb < 8) {
// Value is less than 2**8, so we can use value as mantissa with "-1" exponent.
return _encode(uint8(value), 0xFF);
} else {
// We use `msb - 8` as exponent otherwise. Note that `exponent >= 0`.
unchecked {
uint256 exponent = msb - 8;
// Shifting right by `msb-8` bits will shift the "remaining 8 highest bits" into the 8 lowest bits.
// uint8() will truncate the highest bit.
return _encode(uint8(value >> exponent), uint8(exponent));
}
}
}
/// @notice Decompresses 16 bits number into uint256.
/// @dev The outcome is an approximation of the original number: `(value - value / 256) < number <= value`.
function decompress(Number number) internal pure returns (uint256 value) {
// Isolate 8 highest bits as the mantissa.
uint256 mantissa = Number.unwrap(number) >> SHIFT_MANTISSA;
// This will truncate the highest bits, leaving only the exponent.
uint256 exponent = uint8(Number.unwrap(number));
if (exponent == 0xFF) {
return mantissa;
} else {
unchecked {
return (256 + mantissa) << (exponent);
}
}
}
/// @dev Returns the most significant bit of `x`
/// https://solidity-by-example.org/bitwise/
function mostSignificantBit(uint256 x) internal pure returns (uint256 msb) {
// To find `msb` we determine it bit by bit, starting from the highest one.
// `0 <= msb <= 255`, so we start from the highest bit, 1<<7 == 128.
// If `x` is at least 2**128, then the highest bit of `x` is at least 128.
// solhint-disable no-inline-assembly
assembly {
// `f` is set to 1<<7 if `x >= 2**128` and to 0 otherwise.
let f := shl(7, gt(x, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
// If `x >= 2**128` then set `msb` highest bit to 1 and shift `x` right by 128.
// Otherwise, `msb` remains 0 and `x` remains unchanged.
x := shr(f, x)
msb := or(msb, f)
}
// `x` is now at most 2**128 - 1. Continue the same way, the next highest bit is 1<<6 == 64.
assembly {
// `f` is set to 1<<6 if `x >= 2**64` and to 0 otherwise.
let f := shl(6, gt(x, 0xFFFFFFFFFFFFFFFF))
x := shr(f, x)
msb := or(msb, f)
}
assembly {
// `f` is set to 1<<5 if `x >= 2**32` and to 0 otherwise.
let f := shl(5, gt(x, 0xFFFFFFFF))
x := shr(f, x)
msb := or(msb, f)
}
assembly {
// `f` is set to 1<<4 if `x >= 2**16` and to 0 otherwise.
let f := shl(4, gt(x, 0xFFFF))
x := shr(f, x)
msb := or(msb, f)
}
assembly {
// `f` is set to 1<<3 if `x >= 2**8` and to 0 otherwise.
let f := shl(3, gt(x, 0xFF))
x := shr(f, x)
msb := or(msb, f)
}
assembly {
// `f` is set to 1<<2 if `x >= 2**4` and to 0 otherwise.
let f := shl(2, gt(x, 0xF))
x := shr(f, x)
msb := or(msb, f)
}
assembly {
// `f` is set to 1<<1 if `x >= 2**2` and to 0 otherwise.
let f := shl(1, gt(x, 0x3))
x := shr(f, x)
msb := or(msb, f)
}
assembly {
// `f` is set to 1 if `x >= 2**1` and to 0 otherwise.
let f := gt(x, 0x1)
msb := or(msb, f)
}
}
/// @dev Wraps (mantissa, exponent) pair into Number.
function _encode(uint8 mantissa, uint8 exponent) private pure returns (Number) {
// Casts below are upcasts, so they are safe.
return Number.wrap(uint16(mantissa) << SHIFT_MANTISSA | uint16(exponent));
}
}