|
| 1 | +// SPDX-License-Identifier: Apache 2 |
| 2 | +pragma solidity ^0.8.19; |
| 3 | + |
| 4 | +// ╭────────────────────────────────────────────────────────────╮ |
| 5 | +// │ Library for compact (uint16) representation of percentages │ |
| 6 | +// ╰────────────────────────────────────────────────────────────╯ |
| 7 | + |
| 8 | +// Represent percentages with 4 decimal digits of precision up to a maximum of 1000 % |
| 9 | +// |
| 10 | +// Uses a 14 bit mantissa / 2 bit (decimal!) exponent split: |
| 11 | +// value = mantissa / 10^(1 + exponent) |
| 12 | +// |
| 13 | +// 2^14 = 16384, i.e. we get 4 full digits of precision and a can also represent 1000 % |
| 14 | +// 2 bits of the exponent are used to shift our decimal point *downwards*(!) |
| 15 | +// thus giving us a range of 0.abcd % to abc.d % (or 1000.00 %) |
| 16 | +// This format is somewhat idiosyncratic and some values have multiple representations: |
| 17 | +// Using (mantissa, exponent) notation: |
| 18 | +// 0.1 % = (1, 0) = 0b00000001100100_00 (or (10, 1) or (100, 2)) |
| 19 | +// 10 % = (100, 0) = 0b00000001100100_00 (or (1000, 1) or (10000, 2)) |
| 20 | +// 432.1 % = (4321, 0) = 0b01000011100001_00 |
| 21 | +// 0.4321 % = (4321, 3) = 0b01000011100001_11 |
| 22 | +// 1000.0 % = (10000, 0) = 0b10011100010000_00 |
| 23 | + |
| 24 | +type Percentage is uint16; |
| 25 | +library PercentageLib { |
| 26 | + uint internal constant BYTE_SIZE = 2; |
| 27 | + |
| 28 | + uint private constant EXPONENT_BITS = 2; |
| 29 | + uint private constant EXPONENT_BASE = 1; |
| 30 | + uint private constant EXPONENT_BITS_MASK = (1 << EXPONENT_BITS) - 1; |
| 31 | + uint private constant MAX_MANTISSA = 1e4; //= 1000 % (if exponent = 0) |
| 32 | + //we essentially use a uint128 like an array of 4 uint24s containing [1e6, 1e5, 1e4, 1e3] as a |
| 33 | + // simple way to save some gas over using EVM exponentiation |
| 34 | + uint private constant BITS_PER_POWER = 3*8; //4 powers, 3 bytes per power of ten, 8 bits per byte |
| 35 | + uint private constant POWERS_OF_TEN = |
| 36 | + (1e6 << 3*BITS_PER_POWER) + |
| 37 | + (1e5 << 2*BITS_PER_POWER) + |
| 38 | + (1e4 << 1*BITS_PER_POWER) + |
| 39 | + (1e3 << 0*BITS_PER_POWER); |
| 40 | + uint private constant POWERS_OF_TEN_MASK = (1 << BITS_PER_POWER) - 1; |
| 41 | + |
| 42 | + error InvalidPercentage(uint16 percentage); |
| 43 | + error InvalidArguments(uint mantissa, uint fractionalDigits); |
| 44 | + |
| 45 | + //to(3141, 3) = 3.141 % |
| 46 | + function to( |
| 47 | + uint value, |
| 48 | + uint fractionalDigits |
| 49 | + ) internal pure returns (Percentage) { unchecked { |
| 50 | + if (value == 0) |
| 51 | + return Percentage.wrap(0); |
| 52 | + |
| 53 | + if (fractionalDigits > 4) |
| 54 | + revert InvalidArguments(value, fractionalDigits); |
| 55 | + |
| 56 | + if (fractionalDigits == 0) { |
| 57 | + value *= 10; |
| 58 | + fractionalDigits = 1; |
| 59 | + } |
| 60 | + |
| 61 | + if (value > MAX_MANTISSA) |
| 62 | + revert InvalidArguments(value, fractionalDigits); |
| 63 | + |
| 64 | + value = (value << EXPONENT_BITS) | (fractionalDigits - 1); |
| 65 | + |
| 66 | + uint16 ret; |
| 67 | + //skip unneccessary cleanup |
| 68 | + assembly ("memory-safe") { ret := value } |
| 69 | + |
| 70 | + return Percentage.wrap(ret); |
| 71 | + }} |
| 72 | + |
| 73 | + function checkedWrap(uint16 percentage) internal pure returns (Percentage) { unchecked { |
| 74 | + if ((percentage >> EXPONENT_BITS) > MAX_MANTISSA) |
| 75 | + revert InvalidPercentage(percentage); |
| 76 | + |
| 77 | + return Percentage.wrap(percentage); |
| 78 | + }} |
| 79 | + |
| 80 | + //we can silently overflow if value > 2^256/MAX_MANTISSA - not worth wasting gas to check |
| 81 | + // if you have values this large you should know what you're doing regardless and can just |
| 82 | + // check that the result is greater than or equal to the input value to detect overflows |
| 83 | + function mulUnchecked( |
| 84 | + Percentage percentage_, |
| 85 | + uint value |
| 86 | + ) internal pure returns (uint) { unchecked { |
| 87 | + uint percentage = Percentage.unwrap(percentage_); |
| 88 | + //negative exponent = 0 -> denominator = 100, ..., negative exponent = 3 -> denominator = 1e5 |
| 89 | + uint negativeExponent = percentage & EXPONENT_BITS_MASK; |
| 90 | + uint shift = negativeExponent * BITS_PER_POWER; |
| 91 | + uint denominator = (POWERS_OF_TEN >> shift) & POWERS_OF_TEN_MASK; |
| 92 | + uint numerator = value * (percentage >> EXPONENT_BITS); |
| 93 | + //the + here can overflow if value is within 2 orders of magnitude of 2^256 |
| 94 | + return numerator/denominator; |
| 95 | + }} |
| 96 | +} |
| 97 | +using PercentageLib for Percentage global; |
0 commit comments