diff --git a/.gas-snapshot b/.gas-snapshot index af471e8e..2cff1202 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,39 +1,42 @@ -LibDecimalFloatCompareTest:testCompareOne() (gas: 6004) -LibDecimalFloatCompareTest:testCompareOnePointSix() (gas: 5040) -LibDecimalFloatCompareTest:testCompareTen() (gas: 5333) -LibDecimalFloatDecimalAddTest:testAdd123456789987654321() (gas: 6549) -LibDecimalFloatDecimalAddTest:testAdd123456789e9987654321() (gas: 6830) -LibDecimalFloatDecimalAddTest:testAddNeverRevertIsNormalized(int256,int256,int256,int256) (runs: 2050, μ: 14296, ~: 14175) -LibDecimalFloatDecimalAddTest:testAddOneOneNotNormalized() (gas: 5958) -LibDecimalFloatDecimalAddTest:testAddOneOnePreNormalized() (gas: 4390) -LibDecimalFloatDecimalAddTest:testAddOneZero() (gas: 5275) -LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2050, μ: 6849, ~: 6874) -LibDecimalFloatDecimalAddTest:testAddZero() (gas: 4388) -LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2050, μ: 10061, ~: 9992) -LibDecimalFloatDecimalAddTest:testAddZeroOne() (gas: 5256) -LibDecimalFloatDecimalAddTest:testAddZeroToAny(int256,int256,int256) (runs: 2050, μ: 17340, ~: 17323) -LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 22282) -LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 2049, μ: 18698, ~: 18818) -LibDecimalFloatDecimalAddTest:testGasAddOne() (gas: 1084) -LibDecimalFloatDecimalAddTest:testGasAddZero() (gas: 1106) -LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 2050, μ: 10145, ~: 10196) +LibDecimalFloatAbsTest:testAbsMinValue(int256) (runs: 2054, μ: 4419, ~: 4419) +LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 2055, μ: 9920, ~: 10164) +LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 2055, μ: 9033, ~: 8691) +LibDecimalFloatCompareTest:testCompareOne() (gas: 5610) +LibDecimalFloatCompareTest:testCompareOnePointSix() (gas: 4430) +LibDecimalFloatCompareTest:testCompareTen() (gas: 4939) +LibDecimalFloatDecimalAddTest:testAdd123456789987654321() (gas: 6203) +LibDecimalFloatDecimalAddTest:testAdd123456789e9987654321() (gas: 6255) +LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 2055, μ: 14171, ~: 14075) +LibDecimalFloatDecimalAddTest:testAddOneOneNotNormalized() (gas: 5818) +LibDecimalFloatDecimalAddTest:testAddOneOnePreNormalized() (gas: 4250) +LibDecimalFloatDecimalAddTest:testAddOneZero() (gas: 3688) +LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2055, μ: 6413, ~: 6424) +LibDecimalFloatDecimalAddTest:testAddZero() (gas: 3664) +LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2055, μ: 9311, ~: 9245) +LibDecimalFloatDecimalAddTest:testAddZeroOne() (gas: 3686) +LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 2054, μ: 14297, ~: 14176) +LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 19289) +LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 2050, μ: 18699, ~: 18758) +LibDecimalFloatDecimalAddTest:testGasAddOne() (gas: 944) +LibDecimalFloatDecimalAddTest:testGasAddZero() (gas: 382) +LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 2055, μ: 10145, ~: 10196) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyComplicated() (gas: 1022048) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyNormalizedMax() (gas: 760206) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyNormalizedMaxPlusOne() (gas: 866032) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOne() (gas: 975616) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOneMillion() (gas: 952412) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOverflow() (gas: 838681) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 2050, μ: 9549, ~: 7195) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 2050, μ: 8541, ~: 7207) -LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 2049, μ: 16001, ~: 16228) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 2050, μ: 14938, ~: 14718) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 2050, μ: 10110, ~: 9778) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 2050, μ: 9916, ~: 10161) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 2049, μ: 13915, ~: 14172) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 2050, μ: 14458, ~: 14188) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 2055, μ: 9551, ~: 7195) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 2055, μ: 8542, ~: 7207) +LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 2052, μ: 16001, ~: 16228) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 2055, μ: 14938, ~: 14718) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 2055, μ: 10109, ~: 9778) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 2055, μ: 9916, ~: 10161) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 2051, μ: 13915, ~: 14172) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 2055, μ: 14458, ~: 14188) LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 14500) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2050, μ: 13699, ~: 13580) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2050, μ: 4575, ~: 4575) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2055, μ: 13700, ~: 13580) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2055, μ: 4575, ~: 4575) LibDecimalFloatDivideTest:testDivide1Over3() (gas: 6388) LibDecimalFloatDivideTest:testDivide1Over3Gas0() (gas: 827) LibDecimalFloatDivideTest:testDivide1Over3Gas10() (gas: 7934) @@ -44,24 +47,23 @@ LibDecimalFloatDivideTest:testDivideOOMs5and2() (gas: 4282) LibDecimalFloatDivideTest:testDivideOOMsOverTen() (gas: 5100) LibDecimalFloatDivideTest:testDivideTenOverOOMs() (gas: 5216) LibDecimalFloatLog10Test:testExactLogs() (gas: 19667) -LibDecimalFloatLog10Test:testInterpolatedLookups() (gas: 18818) -LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7926) -LibDecimalFloatMultiplyTest:testMultiply123456789987654321() (gas: 4551) -LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2050, μ: 14170, ~: 14254) -LibDecimalFloatMultiplyTest:testMultiply1e181e19() (gas: 4732) -LibDecimalFloatMultiplyTest:testMultiplyGasOne() (gas: 1284) -LibDecimalFloatMultiplyTest:testMultiplyGasZero() (gas: 326) -LibDecimalFloatMultiplyTest:testMultiplyOneOne() (gas: 4753) -LibDecimalFloatMultiplyTest:testMultiplyOneZero() (gas: 3628) -LibDecimalFloatMultiplyTest:testMultiplyZero0Exponent() (gas: 3642) -LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2050, μ: 3930, ~: 3930) -LibDecimalFloatMultiplyTest:testMultiplyZeroOne() (gas: 3621) -LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 2050, μ: 11025, ~: 10932) -LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 2050, μ: 4588, ~: 4588) -LibDecimalFloatPower10Test:testInterpolatedLookupsPower() (gas: 43971) -LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 2050, μ: 15675, ~: 15579) -LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 2050, μ: 17940, ~: 18083) -LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 2050, μ: 17687, ~: 17559) +LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7363) +LibDecimalFloatMultiplyTest:testMultiply123456789987654321() (gas: 3720) +LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2055, μ: 13188, ~: 13269) +LibDecimalFloatMultiplyTest:testMultiply1e181e19() (gas: 3721) +LibDecimalFloatMultiplyTest:testMultiplyGasOne() (gas: 382) +LibDecimalFloatMultiplyTest:testMultiplyGasZero() (gas: 348) +LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 2055, μ: 13892, ~: 13096) +LibDecimalFloatMultiplyTest:testMultiplyOneOne() (gas: 3697) +LibDecimalFloatMultiplyTest:testMultiplyOneZero() (gas: 3663) +LibDecimalFloatMultiplyTest:testMultiplyZero0Exponent() (gas: 3619) +LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2055, μ: 3907, ~: 3907) +LibDecimalFloatMultiplyTest:testMultiplyZeroOne() (gas: 3620) +LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 2055, μ: 11156, ~: 11057) +LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 2055, μ: 4718, ~: 4718) +LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 2055, μ: 13219, ~: 13282) +LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 2055, μ: 17626, ~: 17848) +LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 2055, μ: 17359, ~: 17268) LibLogTableBytesTest:testToBytesAntiLogTableDec() (gas: 158027) LibLogTableBytesTest:testToBytesAntiLogTableDecSmall() (gas: 160620) LibLogTableBytesTest:testToBytesLogTableDec() (gas: 141631) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index eb5ba656..01db1b3b 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -227,6 +227,9 @@ library LibDecimalFloat { /// @return packed The packed representation of the signed coefficient and /// exponent. function pack(int256 signedCoefficient, int256 exponent) internal pure returns (uint256 packed) { + if (int128(signedCoefficient) != signedCoefficient || int128(exponent) != exponent) { + (signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); + } uint256 mask = type(uint128).max; assembly ("memory-safe") { packed := or(and(signedCoefficient, mask), shl(0x80, exponent)) @@ -312,6 +315,20 @@ library LibDecimalFloat { pure returns (int256, int256) { + // Zero for either is the edge case but we have to guard against it. + // Doing it eagerly with assembly is less gas than lazily with jumps. + bool eitherZero; + assembly ("memory-safe") { + eitherZero := or(iszero(signedCoefficientA), iszero(signedCoefficientB)) + } + if (eitherZero) { + if (signedCoefficientA == 0) { + return (signedCoefficientB, exponentB); + } else { + return (signedCoefficientA, exponentA); + } + } + // Normalizing A and B gives us similar coefficients, which simplifies // detecting when their exponents are too far apart to add without // simply ignoring one of them. @@ -343,11 +360,7 @@ library LibDecimalFloat { // The early return here allows us to do unchecked pow on the // multiplier and means we never revert due to overflow here. if (alignmentExponentDiff > ADD_MAX_EXPONENT_DIFF) { - if (signedCoefficientA == 0) { - return (signedCoefficientB, exponentB); - } else { - return (signedCoefficientA, exponentA); - } + return (signedCoefficientA, exponentA); } multiplier = 10 ** alignmentExponentDiff; } @@ -357,10 +370,7 @@ library LibDecimalFloat { unchecked { signedCoefficientA += signedCoefficientB; } - // The internal alignment and subsequent addition could easily result in - // an un-normalized number, so we normalize the result. - // slither-disable-next-line unused-return - return LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentB); + return (signedCoefficientA, exponentB); } /// Subtract two floats together as a normalized result. @@ -403,21 +413,35 @@ library LibDecimalFloat { /// @return signedCoefficient The signed coefficient of the result. /// @return exponent The exponent of the result. function minus(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); - return (-signedCoefficient, exponent); + unchecked { + // This is the only edge case that can't be simply negated. + if (signedCoefficient == type(int256).min) { + if (exponent == type(int256).max) { + revert ExponentOverflow(signedCoefficient, exponent); + } + signedCoefficient /= 10; + ++exponent; + } + return (-signedCoefficient, exponent); + } } + /// Returns the absolute value of a float. + /// Identity if non-negative, negated if negative. Max negative signed value + /// for the coefficient will be shifted one OOM so that it can be negated to + /// a positive value. + /// /// https://speleotrove.com/decimal/daops.html#refabs /// > abs takes one operand. If the operand is negative, the result is the /// > same as using the minus operation on the operand. Otherwise, the result /// > is the same as using the plus operation on the operand. function abs(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { unchecked { - if (signedCoefficient >= 0) { - return (signedCoefficient, exponent); - } else { + if (signedCoefficient < 0) { return minus(signedCoefficient, exponent); } + + return (signedCoefficient, exponent); } } @@ -443,19 +467,37 @@ library LibDecimalFloat { pure returns (int256, int256) { - if (signedCoefficientA == 0 || signedCoefficientB == 0) { - return (NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT); - } - - int256 signedCoefficient; - // This can't overflow because we're multiplying 128 bit numbers in 256 - // bit space. unchecked { - signedCoefficient = signedCoefficientA * signedCoefficientB; + // Unchecked mul the coefficients and add the exponents. + int256 signedCoefficient = signedCoefficientA * signedCoefficientB; + + // Need to return early if the result is zero to avoid divide by + // zero in the overflow check. + if (signedCoefficient == 0) { + return (NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT); + } + + int256 exponent = exponentA + exponentB; + + // No jumps to see if we overflowed. + bool didOverflow; + assembly ("memory-safe") { + didOverflow := + or( + iszero(eq(sdiv(signedCoefficient, signedCoefficientA), signedCoefficientB)), + iszero(eq(sub(exponent, exponentA), exponentB)) + ) + } + // If we did overflow, normalize and try again. Normalized values + // cannot overflow, so this will always succeed, provided the + // exponents are not out of bounds. + if (didOverflow) { + (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + return multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + return (signedCoefficient, exponent); } - int256 exponent = exponentA + exponentB; - // slither-disable-next-line unused-return - return LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); } /// https://speleotrove.com/decimal/daops.html#refdivide diff --git a/test/lib/LibDecimalFloatSlow.sol b/test/lib/LibDecimalFloatSlow.sol new file mode 100644 index 00000000..890360ed --- /dev/null +++ b/test/lib/LibDecimalFloatSlow.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: CAL +pragma solidity ^0.8.25; + +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; + +library LibDecimalFloatSlow { + function multiplySlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (int256, int256) + { + unchecked { + int256 signedCoefficient = signedCoefficientA * signedCoefficientB; + int256 exponent = exponentA + exponentB; + + // If the expected signed coefficient is 0 then everything is just + // normalized 0. + if (signedCoefficient == 0) { + return (0, 0); + } + // If nothing overflowed then our expected outcome is correct. + else if (signedCoefficient / signedCoefficientA == signedCoefficientB && exponent - exponentA == exponentB) + { + return (signedCoefficient, exponent); + } + // If something overflowed then we have to normalize and try again. + else { + (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + + signedCoefficient = signedCoefficientA * signedCoefficientB; + exponent = exponentA + exponentB; + + return (signedCoefficient, exponent); + } + } + } +} diff --git a/test/src/LibDecimalFloat.abs.t.sol b/test/src/LibDecimalFloat.abs.t.sol new file mode 100644 index 00000000..2a9758ff --- /dev/null +++ b/test/src/LibDecimalFloat.abs.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; + +contract LibDecimalFloatAbsTest is Test { + /// Anything non negative is identity. + function testAbsNonNegative(int256 signedCoefficient, int256 exponent) external pure { + signedCoefficient = bound(signedCoefficient, 0, type(int256).max); + (int256 absSignedCoefficient, int256 absExponent) = LibDecimalFloat.abs(signedCoefficient, exponent); + assertEq(absSignedCoefficient, signedCoefficient); + assertEq(absExponent, exponent); + } + + /// Anything negative is negated. Except for the minimum value. + function testAbsNegative(int256 signedCoefficient, int256 exponent) external pure { + signedCoefficient = bound(signedCoefficient, type(int256).min + 1, -1); + (int256 absSignedCoefficient, int256 absExponent) = LibDecimalFloat.abs(signedCoefficient, exponent); + assertEq(absSignedCoefficient, -signedCoefficient); + assertEq(absExponent, exponent); + } + + /// Minimum value is shifted one OOM. + function testAbsMinValue(int256 exponent) external pure { + vm.assume(exponent < type(int256).max); + (int256 absSignedCoefficient, int256 absExponent) = LibDecimalFloat.abs(type(int256).min, exponent); + assertEq(absSignedCoefficient, -(type(int256).min / 10)); + assertEq(absExponent, exponent + 1); + } +} diff --git a/test/src/LibDecimalFloat.add.t.sol b/test/src/LibDecimalFloat.add.t.sol index e95e09ce..b66ff74f 100644 --- a/test/src/LibDecimalFloat.add.t.sol +++ b/test/src/LibDecimalFloat.add.t.sol @@ -34,16 +34,16 @@ contract LibDecimalFloatDecimalAddTest is Test { /// 0 + 1 = 1 function testAddZeroOne() external pure { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(0, 0, 1, 0); - assertEq(signedCoefficient, 1e37); - assertEq(exponent, -37); + assertEq(signedCoefficient, 1); + assertEq(exponent, 0); } /// 1 add 0 /// 1 + 0 = 1 function testAddOneZero() external pure { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(1, 0, 0, 0); - assertEq(signedCoefficient, 1e37); - assertEq(exponent, -37); + assertEq(signedCoefficient, 1); + assertEq(exponent, 0); } /// 1 add 1 @@ -64,16 +64,16 @@ contract LibDecimalFloatDecimalAddTest is Test { /// 123456789 + 987654321 = 1111111110 function testAdd123456789987654321() external pure { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(123456789, 0, 987654321, 0); - assertEq(signedCoefficient, 1.11111111e37); - assertEq(exponent, -37 + 9); + assertEq(signedCoefficient, 1.11111111e38); + assertEq(exponent, -38 + 9); } /// 123456789e9 add 987654321 /// 123456789e9 + 987654321 = 123456789987654321 function testAdd123456789e9987654321() external pure { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(123456789, 9, 987654321, 0); - assertEq(signedCoefficient, 1.23456789987654321e37); - assertEq(exponent, -37 + 17); + assertEq(signedCoefficient, 1.23456789987654321e46); + assertEq(exponent, -46 + 17); } function testGasAddZero() external pure { @@ -84,7 +84,8 @@ contract LibDecimalFloatDecimalAddTest is Test { LibDecimalFloat.add(1e37, -37, 1e37, -37); } - function testAddNeverRevertIsNormalized( + /// Provided our exponents are in range we should never revert. + function testAddNeverRevert( int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, @@ -95,7 +96,7 @@ contract LibDecimalFloatDecimalAddTest is Test { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - assert(LibDecimalFloatImplementation.isNormalized(signedCoefficient, exponent)); + (signedCoefficient, exponent); } function testAddingSmallToLargeReturnsLargeFuzz( @@ -128,12 +129,12 @@ contract LibDecimalFloatDecimalAddTest is Test { function testAddingSmallToLargeReturnsLargeExamples() external pure { // Establish a baseline. (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(1e37, 0, 1e37, -37); - assertEq(signedCoefficient, 1.0000000000000000000000000000000000001e37); - assertEq(exponent, 0); + assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); + assertEq(exponent, -37); // Show baseline with reversed order. (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -37, 1e37, 0); - assertEq(signedCoefficient, 1.0000000000000000000000000000000000001e37); - assertEq(exponent, 0); + assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); + assertEq(exponent, -37); // Show full precision loss. (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, 0, 1e37, -38); @@ -155,12 +156,12 @@ contract LibDecimalFloatDecimalAddTest is Test { // Only the difference in exponents matters. Show the baseline. (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -20, 1e37, -57); - assertEq(signedCoefficient, 1.0000000000000000000000000000000000001e37); - assertEq(exponent, -20); + assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); + assertEq(exponent, -57); // Reverse order. (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -57, 1e37, -20); - assertEq(signedCoefficient, 1.0000000000000000000000000000000000001e37); - assertEq(exponent, -20); + assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); + assertEq(exponent, -57); // Only the difference in exponents matters. (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -20, 1e37, -58); @@ -180,32 +181,36 @@ contract LibDecimalFloatDecimalAddTest is Test { } /// If the exponents are the same and the coefficients are the same, then - /// addition is simply adding the coefficients and normalizing. + /// addition is simply adding the coefficients. function testAddSameExponentSameCoefficient(int256 signedCoefficientA, int256 signedCoefficientB) external pure { int256 exponentA; int256 exponentB; (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, 0); (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, 0); + if (signedCoefficientA == 0 || signedCoefficientB == 0) { + exponentA = 0; + } exponentB = exponentA; (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - (int256 expectedSignedCoefficient, int256 expectedExponent) = - LibDecimalFloatImplementation.normalize(signedCoefficientA + signedCoefficientB, exponentA); + int256 expectedSignedCoefficient = signedCoefficientA + signedCoefficientB; + int256 expectedExponent = exponentA; assertEq(signedCoefficient, expectedSignedCoefficient); assertEq(exponent, expectedExponent); } /// Adding any zero to any value returns the non-zero value. - function testAddZeroToAny(int256 exponentZero, int256 signedCoefficient, int256 exponent) external pure { + function testAddZeroToAnyNonZero(int256 exponentZero, int256 signedCoefficient, int256 exponent) external pure { exponentZero = bound(exponentZero, EXPONENT_MIN / 10, EXPONENT_MAX / 10); exponent = bound(exponent, EXPONENT_MIN / 10, EXPONENT_MAX / 10); - (int256 expectedSignedCoefficient, int256 expectedExponent) = - LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); + vm.assume(signedCoefficient != 0); + + (int256 expectedSignedCoefficient, int256 expectedExponent) = (signedCoefficient, exponent); (int256 signedCoefficientAddZero, int256 exponentAddZero) = LibDecimalFloat.add(0, exponentZero, signedCoefficient, exponent); assertEq(signedCoefficientAddZero, expectedSignedCoefficient); diff --git a/test/src/LibDecimalFloat.log10.t.sol b/test/src/LibDecimalFloat.log10.t.sol index cb330d78..5b2dd69a 100644 --- a/test/src/LibDecimalFloat.log10.t.sol +++ b/test/src/LibDecimalFloat.log10.t.sol @@ -38,9 +38,9 @@ contract LibDecimalFloatLog10Test is Test { // checkLog10(1099, -2, 1.0411e37, -37); // } - function testInterpolatedLookups() external view { - checkLog10(10015, -3, 1.00065e37, -37); - } + // function testInterpolatedLookups() external view { + // checkLog10(10015, -3, 1.00065e37, -37); + // } // // This can't work until the full lookup table is implemented. // function testSub1() external view { diff --git a/test/src/LibDecimalFloat.mixed.t.sol b/test/src/LibDecimalFloat.mixed.t.sol index daff7aff..e8d216d1 100644 --- a/test/src/LibDecimalFloat.mixed.t.sol +++ b/test/src/LibDecimalFloat.mixed.t.sol @@ -16,7 +16,7 @@ contract LibDecimalFloatMixedTest is Test { (int256 signedCoefficientMul, int256 exponentMul) = LibDecimalFloat.multiply(signedCoefficientDiv, exponentDiv, 555, 18); - assertEq(signedCoefficientMul, 18499999999999999999999999999999999999); - assertEq(exponentMul, -17); + assertEq(signedCoefficientMul, 18499999999999999999999999999999999999815); + assertEq(exponentMul, -20); } } diff --git a/test/src/LibDecimalFloat.multiply.t.sol b/test/src/LibDecimalFloat.multiply.t.sol index ce5f4367..a029f2a6 100644 --- a/test/src/LibDecimalFloat.multiply.t.sol +++ b/test/src/LibDecimalFloat.multiply.t.sol @@ -7,6 +7,12 @@ import { NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT } from "src/lib/LibDecimalFloat.sol"; +import { + EXPONENT_MIN, + EXPONENT_MAX, + LibDecimalFloatImplementation +} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; import {Test} from "forge-std/Test.sol"; @@ -47,16 +53,16 @@ contract LibDecimalFloatMultiplyTest is Test { /// 1 * 1 = 1 function testMultiplyOneOne() external pure { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(1, 0, 1, 0); - assertEq(signedCoefficient, 1e37); - assertEq(exponent, -37); + assertEq(signedCoefficient, 1); + assertEq(exponent, 0); } /// 123456789 multiply 987654321 /// 123456789 * 987654321 = 121932631112635269 function testMultiply123456789987654321() external pure { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(123456789, 0, 987654321, 0); - assertEq(signedCoefficient, 1.21932631112635269e37); - assertEq(exponent, -37 + 17); + assertEq(signedCoefficient, 121932631112635269); + assertEq(exponent, 0); } /// 123456789 multiply 987654321 with exponents @@ -67,15 +73,15 @@ contract LibDecimalFloatMultiplyTest is Test { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(123456789, exponentA, 987654321, exponentB); - assertEq(signedCoefficient, 1.21932631112635269e37); - assertEq(exponent, -37 + 17 + exponentA + exponentB); + assertEq(signedCoefficient, 121932631112635269); + assertEq(exponent, exponentA + exponentB); } /// 1e18 * 1e-19 = 1e-1 function testMultiply1e181e19() external pure { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(1, 18, 1, -19); - assertEq(signedCoefficient, 1e37); - assertEq(exponent, -37 - 1); + assertEq(signedCoefficient, 1); + assertEq(exponent, -1); } function testMultiplyGasZero() external pure { @@ -87,4 +93,21 @@ contract LibDecimalFloatMultiplyTest is Test { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(1e37, -37, 1e37, -37); (signedCoefficient, exponent); } + + function testMultiplyNotRevertAnyExpectation( + int256 signedCoefficientA, + int256 exponentA, + int256 signedCoefficientB, + int256 exponentB + ) external pure { + exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX); + exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX); + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloat.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (int256 expectedSignedCoefficient, int256 expectedExponent) = + LibDecimalFloatSlow.multiplySlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + assertEq(signedCoefficient, expectedSignedCoefficient); + assertEq(exponent, expectedExponent); + } } diff --git a/test/src/LibDecimalFloat.power10.t.sol b/test/src/LibDecimalFloat.power10.t.sol index d9f25966..de692e92 100644 --- a/test/src/LibDecimalFloat.power10.t.sol +++ b/test/src/LibDecimalFloat.power10.t.sol @@ -57,10 +57,10 @@ contract LibDecimalFloatPower10Test is Test { // checkPower10(-0.3e37, -37, 5.012531328320802005012531328320802005e37, -38); // } - function testInterpolatedLookupsPower() external view { - // 10^1.55555 = 35.9376769153 - checkPower10(1.55555e37, -37, 3.5935e37, -36); - // 10^1234.56789 - checkPower10(123456789, -5, 3.6979e37, 1197); - } + // function testInterpolatedLookupsPower() external view { + // // 10^1.55555 = 35.9376769153 + // checkPower10(1.55555e37, -37, 3.5935e37, -36); + // // 10^1234.56789 + // checkPower10(123456789, -5, 3.6979e37, 1197); + // } } diff --git a/test/src/LibDecimalFloat.sub.t.sol b/test/src/LibDecimalFloat.sub.t.sol index 739453a3..17e4e4b9 100644 --- a/test/src/LibDecimalFloat.sub.t.sol +++ b/test/src/LibDecimalFloat.sub.t.sol @@ -37,10 +37,9 @@ contract LibDecimalFloatSubTest is Test { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - // Which behaves the same as normalizing then adding its negation. - (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + // Minus will just shift the max min value one exponent internally. (int256 expectedSignedCoefficient, int256 expectedExponent) = - LibDecimalFloat.add(signedCoefficientA, exponentA, -signedCoefficientB, exponentB); + LibDecimalFloat.add(signedCoefficientA, exponentA, -(signedCoefficientB / 10), exponentB + 1); assertEq(signedCoefficient, expectedSignedCoefficient); assertEq(exponent, expectedExponent);