From 06d260e804eeef7d00e92517f6b91c434de4ee9c Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 22 Jul 2024 18:27:25 +0400 Subject: [PATCH 01/10] wip on compare --- src/lib/LibDecimalFloat.sol | 6 +-- test/src/LibDecimalFloat.compare.t.sol | 72 +++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 26abf53d..b26536c2 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -601,11 +601,7 @@ library LibDecimalFloat { pure returns (int256) { - // We don't support negative zero. - (signedCoefficientB, exponentB) = minus(signedCoefficientB, exponentB); - // We want the un-normalized result so that rounding doesn't affect the - // comparison. - (int256 signedCoefficient,) = add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (int256 signedCoefficient,) = sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); if (signedCoefficient == 0) { return COMPARE_EQUAL; diff --git a/test/src/LibDecimalFloat.compare.t.sol b/test/src/LibDecimalFloat.compare.t.sol index 2e770963..fc494f7a 100644 --- a/test/src/LibDecimalFloat.compare.t.sol +++ b/test/src/LibDecimalFloat.compare.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, COMPARE_EQUAL, COMPARE_GREATER_THAN} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, COMPARE_EQUAL, COMPARE_GREATER_THAN, COMPARE_LESS_THAN, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; import {Test} from "forge-std/Test.sol"; @@ -23,4 +23,74 @@ contract LibDecimalFloatCompareTest is Test { int256 compare = LibDecimalFloat.compare(1.6e35, -35, 1.25e37, -37); assertEq(compare, COMPARE_GREATER_THAN); } + + /// Anything negative is less than anything positive or 0. + function testCompareNegative(int256 signedCoefficientNeg, int256 exponentNeg, int256 signedCoefficientPos, int256 exponentPos) external pure { + signedCoefficientNeg = bound(signedCoefficientNeg, type(int256).min, -1); + signedCoefficientPos = bound(signedCoefficientPos, 0, type(int256).max); + exponentNeg = bound(exponentNeg, EXPONENT_MIN, EXPONENT_MAX); + exponentPos = bound(exponentPos, EXPONENT_MIN, EXPONENT_MAX); + + int256 compare = LibDecimalFloat.compare(signedCoefficientNeg, exponentNeg, signedCoefficientPos, exponentPos); + assertEq(compare, COMPARE_LESS_THAN); + } + + /// Reversing the order reverses the outcome. + function testCompareReversed(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 compare0 = LibDecimalFloat.compare(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + int256 compare1 = LibDecimalFloat.compare(signedCoefficientB, exponentB, signedCoefficientA, exponentA); + + if (compare0 == COMPARE_EQUAL) { + assertEq(compare1, COMPARE_EQUAL); + } else if (compare0 == COMPARE_GREATER_THAN) { + assertEq(compare1, COMPARE_LESS_THAN); + } else { + assertEq(compare1, COMPARE_GREATER_THAN); + } + } + + /// The only possible return values are -1, 0, and 1. + function testCompareBounds(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 compare = LibDecimalFloat.compare(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + assert(compare == COMPARE_LESS_THAN || compare == COMPARE_EQUAL || compare == COMPARE_GREATER_THAN); + } + + /// Comparing something to itself is always equal. + function testCompareSelf(int256 signedCoefficient, int256 exponent) external pure { + exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); + + int256 compare = LibDecimalFloat.compare(signedCoefficient, exponent, signedCoefficient, exponent); + assertEq(compare, COMPARE_EQUAL); + } + + /// Two values with the same exponent are compared by their coefficients. + function testCompareSameExponent(int256 signedCoefficientA, int256 signedCoefficientB, int256 exponent) external pure { + exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); + vm.assume(signedCoefficientA != signedCoefficientB); + + int256 compare = LibDecimalFloat.compare(signedCoefficientA, exponent, signedCoefficientB, exponent); + if (signedCoefficientA < signedCoefficientB) { + assertEq(compare, COMPARE_LESS_THAN); + } else if (signedCoefficientA > signedCoefficientB) { + assertEq(compare, COMPARE_GREATER_THAN); + } else { + assertEq(compare, COMPARE_EQUAL); + } + } + + /// Anything 0 is always less than anything positive. + function testCompareZero(int256 signedCoefficient, int256 exponent, int256 exponentZero) external pure { + signedCoefficient = bound(signedCoefficient, 1, type(int256).max); + exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); + exponentZero = bound(exponentZero, EXPONENT_MIN, EXPONENT_MAX); + + int256 compare = LibDecimalFloat.compare(0, exponentZero, signedCoefficient, exponent); + assertEq(compare, COMPARE_LESS_THAN); + } } From a09a05b406a32eeca23cc2f442cbe1d78ca01f7f Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 22 Jul 2024 20:54:12 +0400 Subject: [PATCH 02/10] wip on compare --- .gas-snapshot | 83 ++++++++++++++------------ src/lib/LibDecimalFloat.sol | 82 ++++++++++++++++++++++--- test/src/LibDecimalFloat.compare.t.sol | 33 ++++++++-- 3 files changed, 148 insertions(+), 50 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index e38edae6..d764744f 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,72 +1,79 @@ 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) +LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 2054, μ: 9919, ~: 10164) +LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 2054, μ: 9034, ~: 8691) +LibDecimalFloatCompareTest:testCompareBounds(int256,int256,int256,int256) (runs: 2054, μ: 11930, ~: 11644) +LibDecimalFloatCompareTest:testCompareNegative(int256,int256,int256,int256) (runs: 2054, μ: 18472, ~: 18183) +LibDecimalFloatCompareTest:testCompareOne() (gas: 3518) +LibDecimalFloatCompareTest:testCompareOnePointSix() (gas: 3585) +LibDecimalFloatCompareTest:testCompareReversed(int256,int256,int256,int256) (runs: 2054, μ: 12557, ~: 12312) +LibDecimalFloatCompareTest:testCompareSameExponent(int256,int256,int256) (runs: 2054, μ: 9580, ~: 9381) +LibDecimalFloatCompareTest:testCompareSelf(int256,int256) (runs: 2054, μ: 9213, ~: 8908) +LibDecimalFloatCompareTest:testCompareTen() (gas: 3567) +LibDecimalFloatCompareTest:testCompareZero(int256,int256,int256) (runs: 2054, μ: 15082, ~: 15087) LibDecimalFloatDecimalAddTest:testAdd123456789987654321() (gas: 6203) LibDecimalFloatDecimalAddTest:testAdd123456789e9987654321() (gas: 6255) -LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 2055, μ: 14171, ~: 14075) +LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 2054, μ: 14170, ~: 14075) LibDecimalFloatDecimalAddTest:testAddOneOneNotNormalized() (gas: 5818) LibDecimalFloatDecimalAddTest:testAddOneOnePreNormalized() (gas: 4250) LibDecimalFloatDecimalAddTest:testAddOneZero() (gas: 3688) -LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2055, μ: 6413, ~: 6424) +LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2054, μ: 6414, ~: 6424) LibDecimalFloatDecimalAddTest:testAddZero() (gas: 3664) -LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2055, μ: 9311, ~: 9245) +LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2054, μ: 9312, ~: 9245) LibDecimalFloatDecimalAddTest:testAddZeroOne() (gas: 3686) -LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 2054, μ: 14297, ~: 14176) +LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 2053, μ: 14298, ~: 14243) 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:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 2054, μ: 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: 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:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 2054, μ: 9561, ~: 7195) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 2054, μ: 8548, ~: 7207) +LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 2049, μ: 16001, ~: 16228) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 2054, μ: 14938, ~: 14718) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 2054, μ: 10110, ~: 9778) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 2054, μ: 9916, ~: 10161) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 2053, μ: 13914, ~: 14172) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 2054, μ: 14457, ~: 14188) LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 14500) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2055, μ: 13700, ~: 13580) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2055, μ: 4575, ~: 4575) -LibDecimalFloatDivideTest:testDivide1Over3() (gas: 6240) -LibDecimalFloatDivideTest:testDivide1Over3Gas0() (gas: 679) -LibDecimalFloatDivideTest:testDivide1Over3Gas10() (gas: 6248) -LibDecimalFloatDivideTest:testDivide1Over9Over1Over3() (gas: 9610) -LibDecimalFloatDivideTest:testDivide1e18Over3() (gas: 5249) -LibDecimalFloatDivideTest:testDivideNegative1Over3() (gas: 6324) -LibDecimalFloatDivideTest:testDivideOOMs5and2() (gas: 3928) -LibDecimalFloatDivideTest:testDivideOOMsOverTen() (gas: 4952) -LibDecimalFloatDivideTest:testDivideTenOverOOMs() (gas: 4862) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2054, μ: 13699, ~: 13580) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2054, μ: 4575, ~: 4575) +LibDecimalFloatDivideTest:testDivide1Over3() (gas: 6420) +LibDecimalFloatDivideTest:testDivide1Over3Gas0() (gas: 761) +LibDecimalFloatDivideTest:testDivide1Over3Gas10() (gas: 7068) +LibDecimalFloatDivideTest:testDivide1Over9Over1Over3() (gas: 9856) +LibDecimalFloatDivideTest:testDivide1e18Over3() (gas: 6148) +LibDecimalFloatDivideTest:testDivideNegative1Over3() (gas: 6490) +LibDecimalFloatDivideTest:testDivideOOMs5and2() (gas: 4827) +LibDecimalFloatDivideTest:testDivideOOMsOverTen() (gas: 5873) +LibDecimalFloatDivideTest:testDivideTenOverOOMs() (gas: 5738) +LibDecimalFloatDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 106, μ: 23628139, ~: 23628366) LibDecimalFloatInvTest:testInvGas0() (gas: 734) -LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 2054, μ: 13005, ~: 13131) +LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 2054, μ: 13003, ~: 13131) LibDecimalFloatInvTest:testInvSlowGas0() (gas: 755) LibDecimalFloatLog10Test:testExactLogs() (gas: 19667) LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7213) LibDecimalFloatMultiplyTest:testMultiply123456789987654321() (gas: 3720) -LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2055, μ: 13188, ~: 13269) +LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2054, μ: 13187, ~: 13269) LibDecimalFloatMultiplyTest:testMultiply1e181e19() (gas: 3721) LibDecimalFloatMultiplyTest:testMultiplyGasOne() (gas: 382) LibDecimalFloatMultiplyTest:testMultiplyGasZero() (gas: 348) -LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 2055, μ: 13892, ~: 13096) +LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 2054, μ: 13897, ~: 13096) LibDecimalFloatMultiplyTest:testMultiplyOneOne() (gas: 3697) LibDecimalFloatMultiplyTest:testMultiplyOneZero() (gas: 3663) LibDecimalFloatMultiplyTest:testMultiplyZero0Exponent() (gas: 3619) -LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2055, μ: 3907, ~: 3907) +LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2054, μ: 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) +LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 2054, μ: 11156, ~: 11057) +LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 2054, μ: 4718, ~: 4718) +LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 2054, μ: 13219, ~: 13282) +LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 2053, μ: 17624, ~: 17848) +LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 2054, μ: 17359, ~: 17266) 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 b26536c2..afc024f6 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -601,14 +601,82 @@ library LibDecimalFloat { pure returns (int256) { - (int256 signedCoefficient,) = sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + unchecked { + // Firstly, if either is negative while the other is not, the latter is greater. + bool diffSign; + assembly ("memory-safe") { + diffSign := + or( + and(slt(signedCoefficientA, 0), sgt(signedCoefficientB, 0)), + and(sgt(signedCoefficientA, 0), slt(signedCoefficientB, 0)) + ) + } + if (diffSign) { + return signedCoefficientA < signedCoefficientB ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; + } - if (signedCoefficient == 0) { - return COMPARE_EQUAL; - } else if (signedCoefficient < 0) { - return COMPARE_LESS_THAN; - } else { - return COMPARE_GREATER_THAN; + // Zero needs special handling because it defies rescaling logic. + bool eitherZero; + assembly ("memory-safe") { + eitherZero := or(iszero(signedCoefficientA), iszero(signedCoefficientB)) + } + if (eitherZero) { + if (signedCoefficientA > signedCoefficientB) { + return COMPARE_GREATER_THAN; + } else if (signedCoefficientA < signedCoefficientB) { + return COMPARE_LESS_THAN; + } else { + return COMPARE_EQUAL; + } + } + + // Make sure that signedCoefficientA has the larger exponent. + bool didSwap = false; + if (exponentB > exponentA) { + int256 tmp = signedCoefficientA; + signedCoefficientA = signedCoefficientB; + signedCoefficientB = tmp; + + tmp = exponentA; + exponentA = exponentB; + exponentB = tmp; + + didSwap = true; + } + + // Almost always we won't overflow by normalizing the exponents in one step. + int256 exponentDiff = exponentA - exponentB; + if (exponentDiff + exponentB != exponentA) { + // We overflowed the diff of the exponents in signed 256 bit space, so we're gazillions of OOMs apart. + // The coefficient with the larger exponent is the larger number. + // I.e. the current A is larger than B. + return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; + } + + if (exponentDiff > 76) { + // We didn't overflow but we're still too far apart to compare. + // The coefficient with the larger exponent is the larger number. + // I.e. the current A is larger than B. + return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; + } + + int256 scale = int256(10 ** uint256(exponentDiff)); + int256 rescaled = signedCoefficientA * scale; + + if (rescaled / scale != signedCoefficientA) { + // We overflowed the rescale, so we're too far apart to compare. + // The coefficient with the larger exponent is the larger number. + // I.e. the current A is larger than B. + return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; + } else { + if (rescaled < signedCoefficientB) { + return didSwap ? COMPARE_GREATER_THAN : COMPARE_LESS_THAN; + } else if (rescaled > signedCoefficientB) { + return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; + } else { + return COMPARE_EQUAL; + } + } } } diff --git a/test/src/LibDecimalFloat.compare.t.sol b/test/src/LibDecimalFloat.compare.t.sol index fc494f7a..b92871e8 100644 --- a/test/src/LibDecimalFloat.compare.t.sol +++ b/test/src/LibDecimalFloat.compare.t.sol @@ -1,7 +1,14 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, COMPARE_EQUAL, COMPARE_GREATER_THAN, COMPARE_LESS_THAN, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; +import { + LibDecimalFloat, + COMPARE_EQUAL, + COMPARE_GREATER_THAN, + COMPARE_LESS_THAN, + EXPONENT_MIN, + EXPONENT_MAX +} from "src/lib/LibDecimalFloat.sol"; import {Test} from "forge-std/Test.sol"; @@ -25,7 +32,12 @@ contract LibDecimalFloatCompareTest is Test { } /// Anything negative is less than anything positive or 0. - function testCompareNegative(int256 signedCoefficientNeg, int256 exponentNeg, int256 signedCoefficientPos, int256 exponentPos) external pure { + function testCompareNegative( + int256 signedCoefficientNeg, + int256 exponentNeg, + int256 signedCoefficientPos, + int256 exponentPos + ) external pure { signedCoefficientNeg = bound(signedCoefficientNeg, type(int256).min, -1); signedCoefficientPos = bound(signedCoefficientPos, 0, type(int256).max); exponentNeg = bound(exponentNeg, EXPONENT_MIN, EXPONENT_MAX); @@ -36,7 +48,12 @@ contract LibDecimalFloatCompareTest is Test { } /// Reversing the order reverses the outcome. - function testCompareReversed(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure { + function testCompareReversed( + int256 signedCoefficientA, + int256 exponentA, + int256 signedCoefficientB, + int256 exponentB + ) external pure { exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX); exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX); @@ -53,7 +70,10 @@ contract LibDecimalFloatCompareTest is Test { } /// The only possible return values are -1, 0, and 1. - function testCompareBounds(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure { + function testCompareBounds(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + { exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX); exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX); @@ -70,7 +90,10 @@ contract LibDecimalFloatCompareTest is Test { } /// Two values with the same exponent are compared by their coefficients. - function testCompareSameExponent(int256 signedCoefficientA, int256 signedCoefficientB, int256 exponent) external pure { + function testCompareSameExponent(int256 signedCoefficientA, int256 signedCoefficientB, int256 exponent) + external + pure + { exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); vm.assume(signedCoefficientA != signedCoefficientB); From cb141f01a405cb9c507ebe9af259656c8072773e Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 22 Jul 2024 21:06:48 +0400 Subject: [PATCH 03/10] wip on compare --- test/src/LibDecimalFloat.compare.t.sol | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/src/LibDecimalFloat.compare.t.sol b/test/src/LibDecimalFloat.compare.t.sol index b92871e8..706ea31b 100644 --- a/test/src/LibDecimalFloat.compare.t.sol +++ b/test/src/LibDecimalFloat.compare.t.sol @@ -116,4 +116,24 @@ contract LibDecimalFloatCompareTest is Test { int256 compare = LibDecimalFloat.compare(0, exponentZero, signedCoefficient, exponent); assertEq(compare, COMPARE_LESS_THAN); } + + function testCompareGasDifferentSigns() external pure { + LibDecimalFloat.compare(1, 0, -1, 0); + } + + function testCompareGasAZero() external pure { + LibDecimalFloat.compare(0, 0, 1, 0); + } + + function testCompareGasBZero() external pure { + LibDecimalFloat.compare(1, 0, 0, 0); + } + + function testCompareBothZero() external pure { + LibDecimalFloat.compare(0, 0, 0, 0); + } + + function testCompareGasExponentDiffOverflow() external pure { + LibDecimalFloat.compare(1, type(int256).max, 1, type(int256).min); + } } From 902a15b8bb53ff3f249e2e01f116de2bdea910e8 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 23 Jul 2024 14:41:19 +0400 Subject: [PATCH 04/10] wip on lt --- src/lib/LibDecimalFloat.sol | 110 +++++++++++++++++++++++-- test/src/LibDecimalFloat.compare.t.sol | 31 ++++--- 2 files changed, 119 insertions(+), 22 deletions(-) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index afc024f6..b5aa71b7 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -575,6 +575,105 @@ library LibDecimalFloat { return (signedCoefficient, exponent); } + function compareRescale(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure returns (bool, bool, int256, int256, int256, int256) { + unchecked { + bool didSwap = false; + if (exponentB > exponentA) { + int256 tmp = signedCoefficientA; + signedCoefficientA = signedCoefficientB; + signedCoefficientB = tmp; + + tmp = exponentA; + exponentA = exponentB; + exponentB = tmp; + + didSwap = true; + } + + int256 exponentDiff = exponentA - exponentB; + bool didOverflow; + assembly ("memory-safe") { + didOverflow := or(slt(exponentDiff, 0), sgt(exponentDiff, 76)) + } + if (didOverflow) { + return (didOverflow, didSwap, 0, 0, 0, 0); + } + int256 scale = int256(10 ** uint256(exponentDiff)); + int256 rescaled = signedCoefficientA * scale; + + if (rescaled / scale != signedCoefficientA) { + return (true, didSwap, 0, 0, 0, 0); + } + else { + return (false, didSwap, rescaled, exponentA - exponentDiff, signedCoefficientB, exponentB); + } + } + } + + function equal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure returns (bool) { + // Trivially true if both coefficient and exponent are the same. + bool trivialTrue; + assembly ("memory-safe") { + trivialTrue := and(eq(signedCoefficientA, signedCoefficientB), eq(exponentA, exponentB)) + } + if (trivialTrue) { + return true; + } + + // Handle zeros that don't rescale well. + if (signedCoefficientA == 0 || signedCoefficientB == 0) { + return signedCoefficientA == signedCoefficientB; + } + + bool didOverflow; + bool didSwap; + (didOverflow, didSwap, signedCoefficientA, exponentA, signedCoefficientB, exponentB) = compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + if (didOverflow) { + return false; + } + + return signedCoefficientA == signedCoefficientB; + } + + function lt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure returns (bool) { + bool straightLt; + // If either coefficients are 0 then do straight lt. + assembly ("memory-safe") { + straightLt := or(iszero(signedCoefficientA), iszero(signedCoefficientB)) + } + if (straightLt) { + return signedCoefficientA < signedCoefficientB; + } + + // If the exponents are the same or the signs of the coefficients differ then lt is just a normal lt on the coefficients. + assembly ("memory-safe") { + straightLt := or( + slt(sdiv(signedCoefficientA, signedCoefficientB), 0), + slt(sdiv(signedCoefficientB, signedCoefficientA), 0) + ) + } + + if (straightLt) { + return signedCoefficientA < signedCoefficientB; + } + + bool didOverflow; + bool didSwap; + (didOverflow, didSwap, signedCoefficientA, exponentA, signedCoefficientB, exponentB) = compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + if (didOverflow) { + return !didSwap; + } + + if (didSwap) { + return signedCoefficientB < signedCoefficientA; + } + else { + return signedCoefficientA < signedCoefficientB; + } + } + /// https://speleotrove.com/decimal/daops.html#refnumco /// > compare takes two operands and compares their values numerically. If /// > either operand is a special value then the general rules apply. No @@ -646,15 +745,8 @@ library LibDecimalFloat { // Almost always we won't overflow by normalizing the exponents in one step. int256 exponentDiff = exponentA - exponentB; - if (exponentDiff + exponentB != exponentA) { - // We overflowed the diff of the exponents in signed 256 bit space, so we're gazillions of OOMs apart. - // The coefficient with the larger exponent is the larger number. - // I.e. the current A is larger than B. - return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; - } - - if (exponentDiff > 76) { - // We didn't overflow but we're still too far apart to compare. + if (exponentDiff < 0 || exponentDiff > 76) { + // Our exponentDiff is overflowing what we can handle. // The coefficient with the larger exponent is the larger number. // I.e. the current A is larger than B. return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; diff --git a/test/src/LibDecimalFloat.compare.t.sol b/test/src/LibDecimalFloat.compare.t.sol index 706ea31b..e915f6e4 100644 --- a/test/src/LibDecimalFloat.compare.t.sol +++ b/test/src/LibDecimalFloat.compare.t.sol @@ -40,8 +40,6 @@ contract LibDecimalFloatCompareTest is Test { ) external pure { signedCoefficientNeg = bound(signedCoefficientNeg, type(int256).min, -1); signedCoefficientPos = bound(signedCoefficientPos, 0, type(int256).max); - exponentNeg = bound(exponentNeg, EXPONENT_MIN, EXPONENT_MAX); - exponentPos = bound(exponentPos, EXPONENT_MIN, EXPONENT_MAX); int256 compare = LibDecimalFloat.compare(signedCoefficientNeg, exponentNeg, signedCoefficientPos, exponentPos); assertEq(compare, COMPARE_LESS_THAN); @@ -54,9 +52,6 @@ contract LibDecimalFloatCompareTest is Test { int256 signedCoefficientB, int256 exponentB ) external pure { - exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX); - exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX); - int256 compare0 = LibDecimalFloat.compare(signedCoefficientA, exponentA, signedCoefficientB, exponentB); int256 compare1 = LibDecimalFloat.compare(signedCoefficientB, exponentB, signedCoefficientA, exponentA); @@ -74,17 +69,12 @@ contract LibDecimalFloatCompareTest is Test { external pure { - exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX); - exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX); - int256 compare = LibDecimalFloat.compare(signedCoefficientA, exponentA, signedCoefficientB, exponentB); assert(compare == COMPARE_LESS_THAN || compare == COMPARE_EQUAL || compare == COMPARE_GREATER_THAN); } /// Comparing something to itself is always equal. function testCompareSelf(int256 signedCoefficient, int256 exponent) external pure { - exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - int256 compare = LibDecimalFloat.compare(signedCoefficient, exponent, signedCoefficient, exponent); assertEq(compare, COMPARE_EQUAL); } @@ -94,7 +84,6 @@ contract LibDecimalFloatCompareTest is Test { external pure { - exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); vm.assume(signedCoefficientA != signedCoefficientB); int256 compare = LibDecimalFloat.compare(signedCoefficientA, exponent, signedCoefficientB, exponent); @@ -110,8 +99,6 @@ contract LibDecimalFloatCompareTest is Test { /// Anything 0 is always less than anything positive. function testCompareZero(int256 signedCoefficient, int256 exponent, int256 exponentZero) external pure { signedCoefficient = bound(signedCoefficient, 1, type(int256).max); - exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - exponentZero = bound(exponentZero, EXPONENT_MIN, EXPONENT_MAX); int256 compare = LibDecimalFloat.compare(0, exponentZero, signedCoefficient, exponent); assertEq(compare, COMPARE_LESS_THAN); @@ -136,4 +123,22 @@ contract LibDecimalFloatCompareTest is Test { function testCompareGasExponentDiffOverflow() external pure { LibDecimalFloat.compare(1, type(int256).max, 1, type(int256).min); } + + function testCompareExponentDiffOverflowMax(int256 signedCoefficientA, int256 signedCoefficientB) external pure { + if (signedCoefficientA < 0) { + vm.assume(signedCoefficientB < 0); + } + if (signedCoefficientA > 0) { + vm.assume(signedCoefficientB > 0); + } + vm.assume(signedCoefficientA != 0); + vm.assume(signedCoefficientB != 0); + + int256 compare = LibDecimalFloat.compare(signedCoefficientA, type(int256).max, signedCoefficientB, type(int256).min); + assertEq(compare, COMPARE_GREATER_THAN); + } + + function testNeverRevert(int256 signedCoeffficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure { + LibDecimalFloat.compare(signedCoeffficientA, exponentA, signedCoefficientB, exponentB); + } } From 465cf4c1c94a2a227513ffdbb5d4988e27f84e81 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 23 Jul 2024 16:18:44 +0400 Subject: [PATCH 05/10] wip on lt --- .gas-snapshot | 88 ++++++++------ src/lib/LibDecimalFloat.sol | 114 ++++-------------- .../LibDecimalFloatImplementation.sol | 86 +++++++++++++ test/src/LibDecimalFloat.compare.t.sol | 8 +- test/src/LibDecimalFloat.lt.t.sol | 60 +++++++++ 5 files changed, 227 insertions(+), 129 deletions(-) create mode 100644 test/src/LibDecimalFloat.lt.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index d764744f..326fdb6b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,48 +1,55 @@ -LibDecimalFloatAbsTest:testAbsMinValue(int256) (runs: 2054, μ: 4419, ~: 4419) -LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 2054, μ: 9919, ~: 10164) -LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 2054, μ: 9034, ~: 8691) -LibDecimalFloatCompareTest:testCompareBounds(int256,int256,int256,int256) (runs: 2054, μ: 11930, ~: 11644) -LibDecimalFloatCompareTest:testCompareNegative(int256,int256,int256,int256) (runs: 2054, μ: 18472, ~: 18183) -LibDecimalFloatCompareTest:testCompareOne() (gas: 3518) -LibDecimalFloatCompareTest:testCompareOnePointSix() (gas: 3585) -LibDecimalFloatCompareTest:testCompareReversed(int256,int256,int256,int256) (runs: 2054, μ: 12557, ~: 12312) -LibDecimalFloatCompareTest:testCompareSameExponent(int256,int256,int256) (runs: 2054, μ: 9580, ~: 9381) -LibDecimalFloatCompareTest:testCompareSelf(int256,int256) (runs: 2054, μ: 9213, ~: 8908) -LibDecimalFloatCompareTest:testCompareTen() (gas: 3567) -LibDecimalFloatCompareTest:testCompareZero(int256,int256,int256) (runs: 2054, μ: 15082, ~: 15087) +LibDecimalFloatAbsTest:testAbsMinValue(int256) (runs: 2055, μ: 4419, ~: 4419) +LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 2056, μ: 9919, ~: 10164) +LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 2056, μ: 9034, ~: 8691) +LibDecimalFloatCompareTest:testCompareBothZero() (gas: 443) +LibDecimalFloatCompareTest:testCompareBounds(int256,int256,int256,int256) (runs: 2056, μ: 720, ~: 758) +LibDecimalFloatCompareTest:testCompareExponentDiffOverflowMax(int256,int256) (runs: 2052, μ: 4607, ~: 4607) +LibDecimalFloatCompareTest:testCompareGasAZero() (gas: 440) +LibDecimalFloatCompareTest:testCompareGasBZero() (gas: 436) +LibDecimalFloatCompareTest:testCompareGasDifferentSigns() (gas: 386) +LibDecimalFloatCompareTest:testCompareGasExponentDiffOverflow() (gas: 481) +LibDecimalFloatCompareTest:testCompareNegative(int256,int256,int256,int256) (runs: 2056, μ: 12219, ~: 12117) +LibDecimalFloatCompareTest:testCompareOne() (gas: 3495) +LibDecimalFloatCompareTest:testCompareOnePointSix() (gas: 3584) +LibDecimalFloatCompareTest:testCompareReversed(int256,int256,int256,int256) (runs: 2056, μ: 3875, ~: 3952) +LibDecimalFloatCompareTest:testCompareSameExponent(int256,int256,int256) (runs: 2056, μ: 3958, ~: 4017) +LibDecimalFloatCompareTest:testCompareSelf(int256,int256) (runs: 2056, μ: 3619, ~: 3621) +LibDecimalFloatCompareTest:testCompareTen() (gas: 3566) +LibDecimalFloatCompareTest:testCompareZero(int256,int256,int256) (runs: 2056, μ: 8826, ~: 8611) +LibDecimalFloatCompareTest:testNeverRevert(int256,int256,int256,int256) (runs: 2056, μ: 633, ~: 668) LibDecimalFloatDecimalAddTest:testAdd123456789987654321() (gas: 6203) LibDecimalFloatDecimalAddTest:testAdd123456789e9987654321() (gas: 6255) -LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 2054, μ: 14170, ~: 14075) +LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 2056, μ: 14172, ~: 14075) LibDecimalFloatDecimalAddTest:testAddOneOneNotNormalized() (gas: 5818) LibDecimalFloatDecimalAddTest:testAddOneOnePreNormalized() (gas: 4250) LibDecimalFloatDecimalAddTest:testAddOneZero() (gas: 3688) -LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2054, μ: 6414, ~: 6424) +LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2056, μ: 6413, ~: 6424) LibDecimalFloatDecimalAddTest:testAddZero() (gas: 3664) -LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2054, μ: 9312, ~: 9245) +LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2056, μ: 9311, ~: 9245) LibDecimalFloatDecimalAddTest:testAddZeroOne() (gas: 3686) -LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 2053, μ: 14298, ~: 14243) +LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 2055, μ: 14297, ~: 14109) 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: 2054, μ: 10145, ~: 10196) +LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 2056, μ: 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: 2054, μ: 9561, ~: 7195) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 2054, μ: 8548, ~: 7207) -LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 2049, μ: 16001, ~: 16228) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 2054, μ: 14938, ~: 14718) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 2054, μ: 10110, ~: 9778) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 2054, μ: 9916, ~: 10161) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 2053, μ: 13914, ~: 14172) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 2054, μ: 14457, ~: 14188) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 2056, μ: 9550, ~: 7195) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 2056, μ: 8542, ~: 7207) +LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 2052, μ: 16001, ~: 16228) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 2056, μ: 14939, ~: 14718) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 2056, μ: 10109, ~: 9778) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 2056, μ: 9916, ~: 10161) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 2052, μ: 13915, ~: 14172) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 2056, μ: 14458, ~: 14188) LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 14500) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2054, μ: 13699, ~: 13580) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2054, μ: 4575, ~: 4575) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2056, μ: 13700, ~: 13580) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2056, μ: 4575, ~: 4575) LibDecimalFloatDivideTest:testDivide1Over3() (gas: 6420) LibDecimalFloatDivideTest:testDivide1Over3Gas0() (gas: 761) LibDecimalFloatDivideTest:testDivide1Over3Gas10() (gas: 7068) @@ -52,28 +59,35 @@ LibDecimalFloatDivideTest:testDivideNegative1Over3() (gas: 6490) LibDecimalFloatDivideTest:testDivideOOMs5and2() (gas: 4827) LibDecimalFloatDivideTest:testDivideOOMsOverTen() (gas: 5873) LibDecimalFloatDivideTest:testDivideTenOverOOMs() (gas: 5738) -LibDecimalFloatDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 106, μ: 23628139, ~: 23628366) +LibDecimalFloatDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 108, μ: 23628118, ~: 23628366) LibDecimalFloatInvTest:testInvGas0() (gas: 734) -LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 2054, μ: 13003, ~: 13131) +LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 2055, μ: 13005, ~: 13131) LibDecimalFloatInvTest:testInvSlowGas0() (gas: 755) LibDecimalFloatLog10Test:testExactLogs() (gas: 19667) +LibDecimalFloatLtTest:testLtGasAZero() (gas: 462) +LibDecimalFloatLtTest:testLtGasBZero() (gas: 370) +LibDecimalFloatLtTest:testLtGasBothZero() (gas: 464) +LibDecimalFloatLtTest:testLtGasDifferentSigns() (gas: 430) +LibDecimalFloatLtTest:testLtGasExponentDiffOverflow() (gas: 459) +LibDecimalFloatLtTest:testLtVsCompare(int256,int256,int256,int256) (runs: 2056, μ: 3845, ~: 3900) +LibDecimalFloatLtTest:testLtVsEqualVsGt(int256,int256,int256,int256) (runs: 2056, μ: 4359, ~: 4298) LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7213) LibDecimalFloatMultiplyTest:testMultiply123456789987654321() (gas: 3720) -LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2054, μ: 13187, ~: 13269) +LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2056, μ: 13188, ~: 13269) LibDecimalFloatMultiplyTest:testMultiply1e181e19() (gas: 3721) LibDecimalFloatMultiplyTest:testMultiplyGasOne() (gas: 382) LibDecimalFloatMultiplyTest:testMultiplyGasZero() (gas: 348) -LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 2054, μ: 13897, ~: 13096) +LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 2056, μ: 13892, ~: 13096) LibDecimalFloatMultiplyTest:testMultiplyOneOne() (gas: 3697) LibDecimalFloatMultiplyTest:testMultiplyOneZero() (gas: 3663) LibDecimalFloatMultiplyTest:testMultiplyZero0Exponent() (gas: 3619) -LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2054, μ: 3907, ~: 3907) +LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2056, μ: 3907, ~: 3907) LibDecimalFloatMultiplyTest:testMultiplyZeroOne() (gas: 3620) -LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 2054, μ: 11156, ~: 11057) -LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 2054, μ: 4718, ~: 4718) -LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 2054, μ: 13219, ~: 13282) -LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 2053, μ: 17624, ~: 17848) -LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 2054, μ: 17359, ~: 17266) +LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 2056, μ: 11156, ~: 11057) +LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 2056, μ: 4718, ~: 4718) +LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 2056, μ: 13218, ~: 13282) +LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 2056, μ: 17626, ~: 17848) +LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 2056, μ: 17359, ~: 17266) 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 b5aa71b7..9e39cbd1 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -575,103 +575,37 @@ library LibDecimalFloat { return (signedCoefficient, exponent); } - function compareRescale(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure returns (bool, bool, int256, int256, int256, int256) { - unchecked { - bool didSwap = false; - if (exponentB > exponentA) { - int256 tmp = signedCoefficientA; - signedCoefficientA = signedCoefficientB; - signedCoefficientB = tmp; - - tmp = exponentA; - exponentA = exponentB; - exponentB = tmp; - - didSwap = true; - } - - int256 exponentDiff = exponentA - exponentB; - bool didOverflow; - assembly ("memory-safe") { - didOverflow := or(slt(exponentDiff, 0), sgt(exponentDiff, 76)) - } - if (didOverflow) { - return (didOverflow, didSwap, 0, 0, 0, 0); - } - int256 scale = int256(10 ** uint256(exponentDiff)); - int256 rescaled = signedCoefficientA * scale; - - if (rescaled / scale != signedCoefficientA) { - return (true, didSwap, 0, 0, 0, 0); - } - else { - return (false, didSwap, rescaled, exponentA - exponentDiff, signedCoefficientB, exponentB); - } - } - } - - function equal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure returns (bool) { - // Trivially true if both coefficient and exponent are the same. - bool trivialTrue; - assembly ("memory-safe") { - trivialTrue := and(eq(signedCoefficientA, signedCoefficientB), eq(exponentA, exponentB)) - } - if (trivialTrue) { - return true; - } - - // Handle zeros that don't rescale well. - if (signedCoefficientA == 0 || signedCoefficientB == 0) { - return signedCoefficientA == signedCoefficientB; - } - - bool didOverflow; - bool didSwap; - (didOverflow, didSwap, signedCoefficientA, exponentA, signedCoefficientB, exponentB) = compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - - if (didOverflow) { - return false; - } + function equal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (bool) + { + (signedCoefficientA, signedCoefficientB) = + LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); return signedCoefficientA == signedCoefficientB; } - function lt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure returns (bool) { - bool straightLt; - // If either coefficients are 0 then do straight lt. - assembly ("memory-safe") { - straightLt := or(iszero(signedCoefficientA), iszero(signedCoefficientB)) - } - if (straightLt) { - return signedCoefficientA < signedCoefficientB; - } - - // If the exponents are the same or the signs of the coefficients differ then lt is just a normal lt on the coefficients. - assembly ("memory-safe") { - straightLt := or( - slt(sdiv(signedCoefficientA, signedCoefficientB), 0), - slt(sdiv(signedCoefficientB, signedCoefficientA), 0) - ) - } - - if (straightLt) { - return signedCoefficientA < signedCoefficientB; - } + function lt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (bool) + { + (signedCoefficientA, signedCoefficientB) = + LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - bool didOverflow; - bool didSwap; - (didOverflow, didSwap, signedCoefficientA, exponentA, signedCoefficientB, exponentB) = compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return signedCoefficientA < signedCoefficientB; + } - if (didOverflow) { - return !didSwap; - } + function gt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (bool) + { + (signedCoefficientA, signedCoefficientB) = + LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - if (didSwap) { - return signedCoefficientB < signedCoefficientA; - } - else { - return signedCoefficientA < signedCoefficientB; - } + return signedCoefficientA > signedCoefficientB; } /// https://speleotrove.com/decimal/daops.html#refnumco diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 2e91ab25..22db63c5 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -100,4 +100,90 @@ library LibDecimalFloatImplementation { return (signedCoefficient, exponent); } } + + /// Rescale two floats so that they are possible to directly compare using + /// standard operators on the signed coefficient. + /// + /// This works by taking the number with the larger exponent and raising it + /// to the power of 10^(largerExponent - smallerExponent), then reducing its + /// float exponent by the diff. This gives both floats the same exponent, + /// which makes their signed coefficients directly comparable. + /// + /// In the case that rescaling causes an overflow, this means that the + /// rescaled number is larger than the unscaled number. We cannot directly + /// return the rescaled number, so instead we return 1, 0 for the larger and + /// 0, 0 for the smaller. This way, comparisons can still be done at all + /// scales. + function compareRescale(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (int256, int256) + { + unchecked { + // There are special cases where the signed coefficients can be + // compared directly, ignoring their exponents, without rescaling: + // - Either is zero + // - They have different signs + // - Their exponents are equal + { + bool noopRescale; + assembly ("memory-safe") { + noopRescale := + or( + or( + // Either is zero + or(iszero(signedCoefficientA), iszero(signedCoefficientB)), + // They have different signs + xor(slt(signedCoefficientA, 0), slt(signedCoefficientB, 0)) + ), + // Their exponents are equal + eq(exponentA, exponentB) + ) + } + if (noopRescale) { + return (signedCoefficientA, signedCoefficientB); + } + } + + bool didSwap = false; + if (exponentB > exponentA) { + int256 tmp = signedCoefficientA; + signedCoefficientA = signedCoefficientB; + signedCoefficientB = tmp; + + tmp = exponentA; + exponentA = exponentB; + exponentB = tmp; + + didSwap = true; + } + + int256 exponentDiff = exponentA - exponentB; + bool didOverflow; + assembly ("memory-safe") { + didOverflow := or(slt(exponentDiff, 0), sgt(exponentDiff, 76)) + } + if (didOverflow) { + if (didSwap) { + return (0, 1); + } else { + return (1, 0); + } + } + int256 scale = int256(10 ** uint256(exponentDiff)); + int256 rescaled = signedCoefficientA * scale; + + if (rescaled / scale != signedCoefficientA) { + if (didSwap) { + return (0, 1); + } else { + return (1, 0); + } + } else if (didSwap) { + return (signedCoefficientB, rescaled); + } else { + return (rescaled, signedCoefficientB); + } + } + } } diff --git a/test/src/LibDecimalFloat.compare.t.sol b/test/src/LibDecimalFloat.compare.t.sol index e915f6e4..7f27f44a 100644 --- a/test/src/LibDecimalFloat.compare.t.sol +++ b/test/src/LibDecimalFloat.compare.t.sol @@ -134,11 +134,15 @@ contract LibDecimalFloatCompareTest is Test { vm.assume(signedCoefficientA != 0); vm.assume(signedCoefficientB != 0); - int256 compare = LibDecimalFloat.compare(signedCoefficientA, type(int256).max, signedCoefficientB, type(int256).min); + int256 compare = + LibDecimalFloat.compare(signedCoefficientA, type(int256).max, signedCoefficientB, type(int256).min); assertEq(compare, COMPARE_GREATER_THAN); } - function testNeverRevert(int256 signedCoeffficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure { + function testNeverRevert(int256 signedCoeffficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + { LibDecimalFloat.compare(signedCoeffficientA, exponentA, signedCoefficientB, exponentB); } } diff --git a/test/src/LibDecimalFloat.lt.t.sol b/test/src/LibDecimalFloat.lt.t.sol new file mode 100644 index 00000000..5d7e887e --- /dev/null +++ b/test/src/LibDecimalFloat.lt.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {LibDecimalFloat, COMPARE_LESS_THAN} from "src/lib/LibDecimalFloat.sol"; + +import {Test} from "forge-std/Test.sol"; + +contract LibDecimalFloatLtTest is Test { + /// Lt and compare need to agree. + function testLtVsCompare(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + { + bool lt = LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + int256 compare = LibDecimalFloat.compare(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + if (compare == COMPARE_LESS_THAN) { + assertTrue(lt); + } else { + assertTrue(!lt); + } + } + + // If lt then not equal nor gt. + function testLtVsEqualVsGt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + { + bool lt = LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + bool equal = LibDecimalFloat.equal(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + bool gt = LibDecimalFloat.gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + if (lt) { + assertTrue(!equal); + assertTrue(!gt); + } else { + assertTrue(equal || gt); + } + } + + function testLtGasDifferentSigns() external pure { + LibDecimalFloat.lt(1, 0, -1, 0); + } + + function testLtGasAZero() external pure { + LibDecimalFloat.compare(0, 0, 1, 0); + } + + function testLtGasBZero() external pure { + LibDecimalFloat.compare(1, 0, 0, 0); + } + + function testLtGasBothZero() external pure { + LibDecimalFloat.compare(0, 0, 0, 0); + } + + function testLtGasExponentDiffOverflow() external pure { + LibDecimalFloat.compare(1, type(int256).max, 1, type(int256).min); + } +} From 8a5d55b15dd9d80b184f3a4849dff9f086e43642 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 23 Jul 2024 18:15:50 +0400 Subject: [PATCH 06/10] test comp --- .gas-snapshot | 117 ++++++++------ foundry.toml | 2 +- src/lib/LibDecimalFloat.sol | 107 +------------ .../LibDecimalFloatImplementation.sol | 41 +++-- test/src/LibDecimalFloat.add.t.sol | 8 +- test/src/LibDecimalFloat.compare.t.sol | 148 ------------------ test/src/LibDecimalFloat.eq.t.sol | 107 +++++++++++++ test/src/LibDecimalFloat.gt.t.sol | 79 ++++++++++ test/src/LibDecimalFloat.log10.t.sol | 2 +- test/src/LibDecimalFloat.lt.t.sol | 102 +++++++++--- test/src/LibDecimalFloat.multiply.t.sol | 5 +- test/src/LibDecimalFloat.power.t.sol | 2 +- test/src/LibDecimalFloat.power10.t.sol | 2 +- 13 files changed, 363 insertions(+), 359 deletions(-) delete mode 100644 test/src/LibDecimalFloat.compare.t.sol create mode 100644 test/src/LibDecimalFloat.eq.t.sol create mode 100644 test/src/LibDecimalFloat.gt.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index 326fdb6b..7b76174a 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,55 +1,39 @@ -LibDecimalFloatAbsTest:testAbsMinValue(int256) (runs: 2055, μ: 4419, ~: 4419) -LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 2056, μ: 9919, ~: 10164) -LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 2056, μ: 9034, ~: 8691) -LibDecimalFloatCompareTest:testCompareBothZero() (gas: 443) -LibDecimalFloatCompareTest:testCompareBounds(int256,int256,int256,int256) (runs: 2056, μ: 720, ~: 758) -LibDecimalFloatCompareTest:testCompareExponentDiffOverflowMax(int256,int256) (runs: 2052, μ: 4607, ~: 4607) -LibDecimalFloatCompareTest:testCompareGasAZero() (gas: 440) -LibDecimalFloatCompareTest:testCompareGasBZero() (gas: 436) -LibDecimalFloatCompareTest:testCompareGasDifferentSigns() (gas: 386) -LibDecimalFloatCompareTest:testCompareGasExponentDiffOverflow() (gas: 481) -LibDecimalFloatCompareTest:testCompareNegative(int256,int256,int256,int256) (runs: 2056, μ: 12219, ~: 12117) -LibDecimalFloatCompareTest:testCompareOne() (gas: 3495) -LibDecimalFloatCompareTest:testCompareOnePointSix() (gas: 3584) -LibDecimalFloatCompareTest:testCompareReversed(int256,int256,int256,int256) (runs: 2056, μ: 3875, ~: 3952) -LibDecimalFloatCompareTest:testCompareSameExponent(int256,int256,int256) (runs: 2056, μ: 3958, ~: 4017) -LibDecimalFloatCompareTest:testCompareSelf(int256,int256) (runs: 2056, μ: 3619, ~: 3621) -LibDecimalFloatCompareTest:testCompareTen() (gas: 3566) -LibDecimalFloatCompareTest:testCompareZero(int256,int256,int256) (runs: 2056, μ: 8826, ~: 8611) -LibDecimalFloatCompareTest:testNeverRevert(int256,int256,int256,int256) (runs: 2056, μ: 633, ~: 668) +LibDecimalFloatAbsTest:testAbsMinValue(int256) (runs: 5106, μ: 4419, ~: 4419) +LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 5107, μ: 9914, ~: 10164) +LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 5107, μ: 9040, ~: 8691) LibDecimalFloatDecimalAddTest:testAdd123456789987654321() (gas: 6203) LibDecimalFloatDecimalAddTest:testAdd123456789e9987654321() (gas: 6255) -LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 2056, μ: 14172, ~: 14075) +LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 5107, μ: 14149, ~: 14075) LibDecimalFloatDecimalAddTest:testAddOneOneNotNormalized() (gas: 5818) LibDecimalFloatDecimalAddTest:testAddOneOnePreNormalized() (gas: 4250) LibDecimalFloatDecimalAddTest:testAddOneZero() (gas: 3688) -LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2056, μ: 6413, ~: 6424) +LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 5107, μ: 6416, ~: 6427) LibDecimalFloatDecimalAddTest:testAddZero() (gas: 3664) -LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2056, μ: 9311, ~: 9245) +LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 5107, μ: 9310, ~: 9245) LibDecimalFloatDecimalAddTest:testAddZeroOne() (gas: 3686) -LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 2055, μ: 14297, ~: 14109) +LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 5106, μ: 14299, ~: 14243) LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 19289) -LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 2050, μ: 18699, ~: 18758) +LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 5099, μ: 18700, ~: 18773) LibDecimalFloatDecimalAddTest:testGasAddOne() (gas: 944) LibDecimalFloatDecimalAddTest:testGasAddZero() (gas: 382) -LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 2056, μ: 10145, ~: 10196) +LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 5107, μ: 10148, ~: 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: 2056, μ: 9550, ~: 7195) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 2056, μ: 8542, ~: 7207) -LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 2052, μ: 16001, ~: 16228) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 2056, μ: 14939, ~: 14718) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 2056, μ: 10109, ~: 9778) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 2056, μ: 9916, ~: 10161) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 2052, μ: 13915, ~: 14172) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 2056, μ: 14458, ~: 14188) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 5107, μ: 9542, ~: 7195) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 5107, μ: 8537, ~: 7207) +LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 5101, μ: 15985, ~: 15974) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 5107, μ: 14953, ~: 14718) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 5107, μ: 10117, ~: 9778) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 5107, μ: 9905, ~: 10161) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 5102, μ: 13918, ~: 14172) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 5107, μ: 14472, ~: 14190) LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 14500) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2056, μ: 13700, ~: 13580) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2056, μ: 4575, ~: 4575) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 5107, μ: 13713, ~: 13580) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 5107, μ: 4575, ~: 4575) LibDecimalFloatDivideTest:testDivide1Over3() (gas: 6420) LibDecimalFloatDivideTest:testDivide1Over3Gas0() (gas: 761) LibDecimalFloatDivideTest:testDivide1Over3Gas10() (gas: 7068) @@ -59,35 +43,64 @@ LibDecimalFloatDivideTest:testDivideNegative1Over3() (gas: 6490) LibDecimalFloatDivideTest:testDivideOOMs5and2() (gas: 4827) LibDecimalFloatDivideTest:testDivideOOMsOverTen() (gas: 5873) LibDecimalFloatDivideTest:testDivideTenOverOOMs() (gas: 5738) -LibDecimalFloatDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 108, μ: 23628118, ~: 23628366) +LibDecimalFloatDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 111, μ: 23628110, ~: 23628366) +LibDecimalFloatEqTest:testEqGasAZero() (gas: 410) +LibDecimalFloatEqTest:testEqGasBZero() (gas: 409) +LibDecimalFloatEqTest:testEqGasBothZero() (gas: 430) +LibDecimalFloatEqTest:testEqGasDifferentSigns() (gas: 440) +LibDecimalFloatEqTest:testEqGasExponentDiffOverflow() (gas: 558) +LibDecimalFloatEqTest:testEqOneEAny(int256,int256) (runs: 5107, μ: 3396, ~: 3396) +LibDecimalFloatEqTest:testEqX(int256) (runs: 5107, μ: 3372, ~: 3372) +LibDecimalFloatEqTest:testEqXEAnyVsXEAny(int256,int256,int256) (runs: 5105, μ: 4698, ~: 4696) +LibDecimalFloatEqTest:testEqXEqY(int256,int256,int256,int256) (runs: 5107, μ: 714, ~: 733) +LibDecimalFloatEqTest:testEqXNotY(int256,int256,int256,int256) (runs: 5106, μ: 3863, ~: 3888) +LibDecimalFloatEqTest:testEqXNotYExponents(int256,int256,int256,int256) (runs: 5107, μ: 4222, ~: 4301) +LibDecimalFloatEqTest:testEqZero(int256,int256) (runs: 5107, μ: 3442, ~: 3442) +LibDecimalFloatGtTest:testGtGasAZero() (gas: 431) +LibDecimalFloatGtTest:testGtGasBZero() (gas: 430) +LibDecimalFloatGtTest:testGtGasBothZero() (gas: 432) +LibDecimalFloatGtTest:testGtGasDifferentSigns() (gas: 410) +LibDecimalFloatGtTest:testGtGasExponentDiffOverflow() (gas: 513) +LibDecimalFloatGtTest:testGtOneEAny(int256,int256) (runs: 5107, μ: 3445, ~: 3445) +LibDecimalFloatGtTest:testGtX(int256) (runs: 5107, μ: 3419, ~: 3419) +LibDecimalFloatGtTest:testGtXEAnyVsXEAny(int256,int256,int256) (runs: 5107, μ: 9681, ~: 9468) +LibDecimalFloatGtTest:testGtXEAnyVsXEAnyNegative(int256,int256,int256) (runs: 5107, μ: 10305, ~: 10552) +LibDecimalFloatGtTest:testGtXNotY(int256,int256,int256,int256) (runs: 5107, μ: 4209, ~: 4288) +LibDecimalFloatGtTest:testGtZero(int256,int256) (runs: 5107, μ: 3422, ~: 3422) LibDecimalFloatInvTest:testInvGas0() (gas: 734) -LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 2055, μ: 13005, ~: 13131) +LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 5105, μ: 13017, ~: 13131) LibDecimalFloatInvTest:testInvSlowGas0() (gas: 755) LibDecimalFloatLog10Test:testExactLogs() (gas: 19667) -LibDecimalFloatLtTest:testLtGasAZero() (gas: 462) -LibDecimalFloatLtTest:testLtGasBZero() (gas: 370) -LibDecimalFloatLtTest:testLtGasBothZero() (gas: 464) -LibDecimalFloatLtTest:testLtGasDifferentSigns() (gas: 430) -LibDecimalFloatLtTest:testLtGasExponentDiffOverflow() (gas: 459) -LibDecimalFloatLtTest:testLtVsCompare(int256,int256,int256,int256) (runs: 2056, μ: 3845, ~: 3900) -LibDecimalFloatLtTest:testLtVsEqualVsGt(int256,int256,int256,int256) (runs: 2056, μ: 4359, ~: 4298) +LibDecimalFloatLtTest:testLtGasAZero() (gas: 430) +LibDecimalFloatLtTest:testLtGasBZero() (gas: 452) +LibDecimalFloatLtTest:testLtGasBothZero() (gas: 431) +LibDecimalFloatLtTest:testLtGasDifferentSigns() (gas: 453) +LibDecimalFloatLtTest:testLtGasExponentDiffOverflow() (gas: 515) +LibDecimalFloatLtTest:testLtNegativeVsPositive(int256,int256,int256,int256) (runs: 5107, μ: 12842, ~: 12740) +LibDecimalFloatLtTest:testLtNegativeVsZero(int256,int256,int256) (runs: 5107, μ: 9987, ~: 10236) +LibDecimalFloatLtTest:testLtOneEAny(int256,int256) (runs: 5107, μ: 3445, ~: 3445) +LibDecimalFloatLtTest:testLtVsEqualVsGt(int256,int256,int256,int256) (runs: 5107, μ: 4238, ~: 4312) +LibDecimalFloatLtTest:testLtX(int256) (runs: 5107, μ: 3396, ~: 3396) +LibDecimalFloatLtTest:testLtXEAnyVsXEAny(int256,int256,int256) (runs: 5107, μ: 9725, ~: 9512) +LibDecimalFloatLtTest:testLtXEAnyVsXEAnyNegative(int256,int256,int256) (runs: 5107, μ: 10261, ~: 10508) +LibDecimalFloatLtTest:testLtZero(int256,int256) (runs: 5107, μ: 3422, ~: 3422) LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7213) LibDecimalFloatMultiplyTest:testMultiply123456789987654321() (gas: 3720) -LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2056, μ: 13188, ~: 13269) +LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 5107, μ: 13185, ~: 13269) LibDecimalFloatMultiplyTest:testMultiply1e181e19() (gas: 3721) LibDecimalFloatMultiplyTest:testMultiplyGasOne() (gas: 382) LibDecimalFloatMultiplyTest:testMultiplyGasZero() (gas: 348) -LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 2056, μ: 13892, ~: 13096) +LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 5107, μ: 13911, ~: 13097) LibDecimalFloatMultiplyTest:testMultiplyOneOne() (gas: 3697) LibDecimalFloatMultiplyTest:testMultiplyOneZero() (gas: 3663) LibDecimalFloatMultiplyTest:testMultiplyZero0Exponent() (gas: 3619) -LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2056, μ: 3907, ~: 3907) +LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 5107, μ: 3907, ~: 3907) LibDecimalFloatMultiplyTest:testMultiplyZeroOne() (gas: 3620) -LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 2056, μ: 11156, ~: 11057) -LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 2056, μ: 4718, ~: 4718) -LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 2056, μ: 13218, ~: 13282) -LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 2056, μ: 17626, ~: 17848) -LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 2056, μ: 17359, ~: 17266) +LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 5107, μ: 11154, ~: 11057) +LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 5107, μ: 4718, ~: 4718) +LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 5107, μ: 13217, ~: 13268) +LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 5107, μ: 17589, ~: 17843) +LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 5107, μ: 17350, ~: 17236) LibLogTableBytesTest:testToBytesAntiLogTableDec() (gas: 158027) LibLogTableBytesTest:testToBytesAntiLogTableDecSmall() (gas: 160620) LibLogTableBytesTest:testToBytesLogTableDec() (gas: 141631) diff --git a/foundry.toml b/foundry.toml index b560a7a9..7edddf34 100644 --- a/foundry.toml +++ b/foundry.toml @@ -29,4 +29,4 @@ remappings = [ ] [fuzz] -runs = 2048 \ No newline at end of file +runs = 5096 \ No newline at end of file diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 9e39cbd1..60bab12b 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -22,13 +22,6 @@ import { EXPONENT_STEP_SIZE } from "./implementation/LibDecimalFloatImplementation.sol"; -/// @dev Returned by `compare` when the first operand is less than the second. -int256 constant COMPARE_LESS_THAN = -1; -/// @dev Returned by `compare` when the operands are equal. -int256 constant COMPARE_EQUAL = 0; -/// @dev Returned by `compare` when the first operand is greater than the second. -int256 constant COMPARE_GREATER_THAN = 1; - uint256 constant ADD_MAX_EXPONENT_DIFF = 37; /// @dev When normalizing a number, how far we "leap" when very far from @@ -575,7 +568,7 @@ library LibDecimalFloat { return (signedCoefficient, exponent); } - function equal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + function eq(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure returns (bool) @@ -608,104 +601,6 @@ library LibDecimalFloat { return signedCoefficientA > signedCoefficientB; } - /// https://speleotrove.com/decimal/daops.html#refnumco - /// > compare takes two operands and compares their values numerically. If - /// > either operand is a special value then the general rules apply. No - /// > flags are set unless an operand is a signaling NaN. - /// > - /// > Otherwise, the operands are compared as follows. - /// > - /// > If the signs of the operands differ, a value representing each operand - /// > (’-1’ if the operand is less than zero, ’0’ if the operand is zero or - /// > negative zero, or ’1’ if the operand is greater than zero) is used in - /// > place of that operand for the comparison instead of the actual operand. - /// > - /// > The comparison is then effected by subtracting the second operand from - /// > the first and then returning a value according to the result of the - /// > subtraction: ’-1’ if the result is less than zero, ’0’ if the result is - /// > zero or negative zero, or ’1’ if the result is greater than zero. - /// > - /// > An implementation may use this operation ‘under the covers’ to - /// > implement a closed set of comparison operations - /// > (greater than, equal,etc.) if desired. It need not, in this case, - /// > expose the compare operation itself. - function compare(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (int256) - { - unchecked { - // Firstly, if either is negative while the other is not, the latter is greater. - bool diffSign; - assembly ("memory-safe") { - diffSign := - or( - and(slt(signedCoefficientA, 0), sgt(signedCoefficientB, 0)), - and(sgt(signedCoefficientA, 0), slt(signedCoefficientB, 0)) - ) - } - if (diffSign) { - return signedCoefficientA < signedCoefficientB ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; - } - - // Zero needs special handling because it defies rescaling logic. - bool eitherZero; - assembly ("memory-safe") { - eitherZero := or(iszero(signedCoefficientA), iszero(signedCoefficientB)) - } - if (eitherZero) { - if (signedCoefficientA > signedCoefficientB) { - return COMPARE_GREATER_THAN; - } else if (signedCoefficientA < signedCoefficientB) { - return COMPARE_LESS_THAN; - } else { - return COMPARE_EQUAL; - } - } - - // Make sure that signedCoefficientA has the larger exponent. - bool didSwap = false; - if (exponentB > exponentA) { - int256 tmp = signedCoefficientA; - signedCoefficientA = signedCoefficientB; - signedCoefficientB = tmp; - - tmp = exponentA; - exponentA = exponentB; - exponentB = tmp; - - didSwap = true; - } - - // Almost always we won't overflow by normalizing the exponents in one step. - int256 exponentDiff = exponentA - exponentB; - if (exponentDiff < 0 || exponentDiff > 76) { - // Our exponentDiff is overflowing what we can handle. - // The coefficient with the larger exponent is the larger number. - // I.e. the current A is larger than B. - return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; - } - - int256 scale = int256(10 ** uint256(exponentDiff)); - int256 rescaled = signedCoefficientA * scale; - - if (rescaled / scale != signedCoefficientA) { - // We overflowed the rescale, so we're too far apart to compare. - // The coefficient with the larger exponent is the larger number. - // I.e. the current A is larger than B. - return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; - } else { - if (rescaled < signedCoefficientB) { - return didSwap ? COMPARE_GREATER_THAN : COMPARE_LESS_THAN; - } else if (rescaled > signedCoefficientB) { - return didSwap ? COMPARE_LESS_THAN : COMPARE_GREATER_THAN; - } else { - return COMPARE_EQUAL; - } - } - } - } - /// a^b = 10^(b * log10(a)) function power(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 22db63c5..dd41443e 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -104,16 +104,31 @@ library LibDecimalFloatImplementation { /// Rescale two floats so that they are possible to directly compare using /// standard operators on the signed coefficient. /// - /// This works by taking the number with the larger exponent and raising it - /// to the power of 10^(largerExponent - smallerExponent), then reducing its - /// float exponent by the diff. This gives both floats the same exponent, - /// which makes their signed coefficients directly comparable. + /// There is no guarantee that the returned values somehow represent the + /// input values. The only guarantee is that comparing them directly will + /// give the same result as comparing the inputs as floats. /// - /// In the case that rescaling causes an overflow, this means that the - /// rescaled number is larger than the unscaled number. We cannot directly - /// return the rescaled number, so instead we return 1, 0 for the larger and - /// 0, 0 for the smaller. This way, comparisons can still be done at all - /// scales. + /// https://speleotrove.com/decimal/daops.html#refnumco + /// > compare takes two operands and compares their values numerically. If + /// > either operand is a special value then the general rules apply. No + /// > flags are set unless an operand is a signaling NaN. + /// > + /// > Otherwise, the operands are compared as follows. + /// > + /// > If the signs of the operands differ, a value representing each operand + /// > (’-1’ if the operand is less than zero, ’0’ if the operand is zero or + /// > negative zero, or ’1’ if the operand is greater than zero) is used in + /// > place of that operand for the comparison instead of the actual operand. + /// > + /// > The comparison is then effected by subtracting the second operand from + /// > the first and then returning a value according to the result of the + /// > subtraction: ’-1’ if the result is less than zero, ’0’ if the result is + /// > zero or negative zero, or ’1’ if the result is greater than zero. + /// > + /// > An implementation may use this operation ‘under the covers’ to + /// > implement a closed set of comparison operations + /// > (greater than, equal,etc.) if desired. It need not, in this case, + /// > expose the compare operation itself. function compareRescale(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure @@ -165,9 +180,9 @@ library LibDecimalFloatImplementation { } if (didOverflow) { if (didSwap) { - return (0, 1); + return (0, signedCoefficientA); } else { - return (1, 0); + return (signedCoefficientA, 0); } } int256 scale = int256(10 ** uint256(exponentDiff)); @@ -175,9 +190,9 @@ library LibDecimalFloatImplementation { if (rescaled / scale != signedCoefficientA) { if (didSwap) { - return (0, 1); + return (0, signedCoefficientA); } else { - return (1, 0); + return (signedCoefficientA, 0); } } else if (didSwap) { return (signedCoefficientB, rescaled); diff --git a/test/src/LibDecimalFloat.add.t.sol b/test/src/LibDecimalFloat.add.t.sol index b66ff74f..c1f694ef 100644 --- a/test/src/LibDecimalFloat.add.t.sol +++ b/test/src/LibDecimalFloat.add.t.sol @@ -1,13 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import { - LibDecimalFloat, - COMPARE_EQUAL, - EXPONENT_MIN, - EXPONENT_MAX, - ADD_MAX_EXPONENT_DIFF -} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, ADD_MAX_EXPONENT_DIFF} from "src/lib/LibDecimalFloat.sol"; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {Test} from "forge-std/Test.sol"; diff --git a/test/src/LibDecimalFloat.compare.t.sol b/test/src/LibDecimalFloat.compare.t.sol deleted file mode 100644 index 7f27f44a..00000000 --- a/test/src/LibDecimalFloat.compare.t.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: CAL -pragma solidity =0.8.25; - -import { - LibDecimalFloat, - COMPARE_EQUAL, - COMPARE_GREATER_THAN, - COMPARE_LESS_THAN, - EXPONENT_MIN, - EXPONENT_MAX -} from "src/lib/LibDecimalFloat.sol"; - -import {Test} from "forge-std/Test.sol"; - -contract LibDecimalFloatCompareTest is Test { - /// 1 == 1 - function testCompareOne() external pure { - int256 compare = LibDecimalFloat.compare(1, 0, 1, 0); - assertEq(compare, COMPARE_EQUAL); - } - - /// 10,0 == 1e38,-37 - function testCompareTen() external pure { - int256 compare = LibDecimalFloat.compare(10, 0, 1e38, -37); - assertEq(compare, COMPARE_EQUAL); - } - - /// 1.6e35,-35 > 1.25e37,-37 - function testCompareOnePointSix() external pure { - int256 compare = LibDecimalFloat.compare(1.6e35, -35, 1.25e37, -37); - assertEq(compare, COMPARE_GREATER_THAN); - } - - /// Anything negative is less than anything positive or 0. - function testCompareNegative( - int256 signedCoefficientNeg, - int256 exponentNeg, - int256 signedCoefficientPos, - int256 exponentPos - ) external pure { - signedCoefficientNeg = bound(signedCoefficientNeg, type(int256).min, -1); - signedCoefficientPos = bound(signedCoefficientPos, 0, type(int256).max); - - int256 compare = LibDecimalFloat.compare(signedCoefficientNeg, exponentNeg, signedCoefficientPos, exponentPos); - assertEq(compare, COMPARE_LESS_THAN); - } - - /// Reversing the order reverses the outcome. - function testCompareReversed( - int256 signedCoefficientA, - int256 exponentA, - int256 signedCoefficientB, - int256 exponentB - ) external pure { - int256 compare0 = LibDecimalFloat.compare(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - int256 compare1 = LibDecimalFloat.compare(signedCoefficientB, exponentB, signedCoefficientA, exponentA); - - if (compare0 == COMPARE_EQUAL) { - assertEq(compare1, COMPARE_EQUAL); - } else if (compare0 == COMPARE_GREATER_THAN) { - assertEq(compare1, COMPARE_LESS_THAN); - } else { - assertEq(compare1, COMPARE_GREATER_THAN); - } - } - - /// The only possible return values are -1, 0, and 1. - function testCompareBounds(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - { - int256 compare = LibDecimalFloat.compare(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - assert(compare == COMPARE_LESS_THAN || compare == COMPARE_EQUAL || compare == COMPARE_GREATER_THAN); - } - - /// Comparing something to itself is always equal. - function testCompareSelf(int256 signedCoefficient, int256 exponent) external pure { - int256 compare = LibDecimalFloat.compare(signedCoefficient, exponent, signedCoefficient, exponent); - assertEq(compare, COMPARE_EQUAL); - } - - /// Two values with the same exponent are compared by their coefficients. - function testCompareSameExponent(int256 signedCoefficientA, int256 signedCoefficientB, int256 exponent) - external - pure - { - vm.assume(signedCoefficientA != signedCoefficientB); - - int256 compare = LibDecimalFloat.compare(signedCoefficientA, exponent, signedCoefficientB, exponent); - if (signedCoefficientA < signedCoefficientB) { - assertEq(compare, COMPARE_LESS_THAN); - } else if (signedCoefficientA > signedCoefficientB) { - assertEq(compare, COMPARE_GREATER_THAN); - } else { - assertEq(compare, COMPARE_EQUAL); - } - } - - /// Anything 0 is always less than anything positive. - function testCompareZero(int256 signedCoefficient, int256 exponent, int256 exponentZero) external pure { - signedCoefficient = bound(signedCoefficient, 1, type(int256).max); - - int256 compare = LibDecimalFloat.compare(0, exponentZero, signedCoefficient, exponent); - assertEq(compare, COMPARE_LESS_THAN); - } - - function testCompareGasDifferentSigns() external pure { - LibDecimalFloat.compare(1, 0, -1, 0); - } - - function testCompareGasAZero() external pure { - LibDecimalFloat.compare(0, 0, 1, 0); - } - - function testCompareGasBZero() external pure { - LibDecimalFloat.compare(1, 0, 0, 0); - } - - function testCompareBothZero() external pure { - LibDecimalFloat.compare(0, 0, 0, 0); - } - - function testCompareGasExponentDiffOverflow() external pure { - LibDecimalFloat.compare(1, type(int256).max, 1, type(int256).min); - } - - function testCompareExponentDiffOverflowMax(int256 signedCoefficientA, int256 signedCoefficientB) external pure { - if (signedCoefficientA < 0) { - vm.assume(signedCoefficientB < 0); - } - if (signedCoefficientA > 0) { - vm.assume(signedCoefficientB > 0); - } - vm.assume(signedCoefficientA != 0); - vm.assume(signedCoefficientB != 0); - - int256 compare = - LibDecimalFloat.compare(signedCoefficientA, type(int256).max, signedCoefficientB, type(int256).min); - assertEq(compare, COMPARE_GREATER_THAN); - } - - function testNeverRevert(int256 signedCoeffficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - { - LibDecimalFloat.compare(signedCoeffficientA, exponentA, signedCoefficientB, exponentB); - } -} diff --git a/test/src/LibDecimalFloat.eq.t.sol b/test/src/LibDecimalFloat.eq.t.sol new file mode 100644 index 00000000..379da8c4 --- /dev/null +++ b/test/src/LibDecimalFloat.eq.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; + +import {Test} from "forge-std/Test.sol"; + +contract LibDecimalFloatEqTest is Test { + /// x == x + function testEqX(int256 x) external pure { + bool eq = LibDecimalFloat.eq(x, 0, x, 0); + assertTrue(eq); + } + + /// xeX == xeX + function testEqOneEAny(int256 x, int256 exponent) external pure { + bool eq = LibDecimalFloat.eq(x, exponent, x, exponent); + assertTrue(eq); + } + + /// xeX != xeY if X != Y && x != 0 + function testEqXEAnyVsXEAny(int256 x, int256 exponentX, int256 exponentY) external pure { + vm.assume(x != 0); + bool eq = LibDecimalFloat.eq(x, exponentX, x, exponentY); + + assertEq(eq, exponentX == exponentY); + + // Reverse the order. + eq = LibDecimalFloat.eq(x, exponentY, x, exponentX); + assertEq(eq, exponentX == exponentY); + } + + /// xeX == xeY if x == 0 + function testEqZero(int256 exponentX, int256 exponentY) external pure { + bool eq = LibDecimalFloat.eq(0, exponentX, 0, exponentY); + assertTrue(eq); + } + + /// xeX != yeY if x != y + function testEqXNotY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + vm.assume(x != y); + bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); + assertTrue(!eq); + } + + /// xeX != yeY if xeX < xeY || xeX > xeY + function testEqXNotYExponents(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); + bool lt = LibDecimalFloat.lt(x, exponentX, y, exponentY); + bool gt = LibDecimalFloat.gt(x, exponentX, y, exponentY); + + assertEq(eq, !lt && !gt); + } + + /// if xeX == yeY, then x / y == 10^(X - Y) || y / x == 10^(Y - X) + function testEqXEqY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); + + if (eq) { + if (y > x) { + assertTrue(exponentY < exponentX, "y > x but exponentY >= exponentX"); + assertTrue(exponentX - exponentY < 77, "y > x but exponentX - exponentY >= 77"); + assertEq(x / y, int256(10 ** uint256(exponentX - exponentY)), "y > x but x / y != 10^(X - Y)"); + assertEq(x % y, 0, "y > x but x % y != 0"); + } else if (x < y) { + assertTrue(exponentX < exponentY, "x < y but exponentX >= exponentY"); + assertTrue(exponentY - exponentX < 77, "x < y but exponentY - exponentX >= 77"); + assertEq(y / x, int256(10 ** uint256(exponentY - exponentX)), "x < y but y / x != 10^(Y - X)"); + assertEq(y % x, 0, "x < y but y % x != 0"); + } else { + assertEq(x, y); + assertTrue(exponentX == exponentY || x == 0); + } + } else { + if (x == y) { + assertTrue(exponentX != exponentY); + if (exponentX < exponentY && exponentY - exponentX < 77) { + assertTrue(x / y != int256(10 ** uint256(exponentX - exponentY))); + assertTrue(x % y != 0); + } else if (exponentY < exponentX && exponentX - exponentY < 77) { + assertTrue(y / x != int256(10 ** uint256(exponentY - exponentX))); + assertTrue(y % x != 0); + } + } + } + } + + function testEqGasDifferentSigns() external pure { + LibDecimalFloat.eq(1, 0, -1, 0); + } + + function testEqGasAZero() external pure { + LibDecimalFloat.eq(0, 0, 1, 0); + } + + function testEqGasBZero() external pure { + LibDecimalFloat.eq(1, 0, 0, 0); + } + + function testEqGasBothZero() external pure { + LibDecimalFloat.eq(0, 0, 0, 0); + } + + function testEqGasExponentDiffOverflow() external pure { + LibDecimalFloat.eq(1, type(int256).max, 1, type(int256).min); + } +} diff --git a/test/src/LibDecimalFloat.gt.t.sol b/test/src/LibDecimalFloat.gt.t.sol new file mode 100644 index 00000000..bd048291 --- /dev/null +++ b/test/src/LibDecimalFloat.gt.t.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; + +import {Test} from "forge-std/Test.sol"; + +contract LibDecimalFloatGtTest is Test { + /// x !> x + function testGtX(int256 x) external pure { + bool gt = LibDecimalFloat.gt(x, 0, x, 0); + assertTrue(!gt); + } + + /// xeX !> xeX + function testGtOneEAny(int256 x, int256 exponent) external pure { + bool gt = LibDecimalFloat.gt(x, exponent, x, exponent); + assertTrue(!gt); + } + + /// xeX > xeY if X > Y && x > 0 + function testGtXEAnyVsXEAny(int256 x, int256 exponentA, int256 exponentB) external pure { + x = bound(x, 1, type(int256).max); + bool gt = LibDecimalFloat.gt(x, exponentA, x, exponentB); + + assertEq(gt, exponentA > exponentB); + + // Reverse the order. + gt = LibDecimalFloat.gt(x, exponentB, x, exponentA); + assertEq(gt, exponentB > exponentA); + } + + /// xeX > xeY if X < Y && x < 0 + function testGtXEAnyVsXEAnyNegative(int256 x, int256 exponentA, int256 exponentB) external pure { + x = bound(x, type(int256).min, -1); + bool gt = LibDecimalFloat.gt(x, exponentA, x, exponentB); + + assertEq(gt, exponentA < exponentB); + + // Reverse the order. + gt = LibDecimalFloat.gt(x, exponentB, x, exponentA); + assertEq(gt, exponentB < exponentA); + } + + /// xeX !> xeY if x == 0 + function testGtZero(int256 exponentA, int256 exponentB) external pure { + bool gt = LibDecimalFloat.gt(0, exponentA, 0, exponentB); + assertTrue(!gt); + } + + /// xeX > yeY if xeX != yeY && xeX !< yeY + function testGtXNotY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + bool gt = LibDecimalFloat.gt(x, exponentX, y, exponentY); + bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); + bool lt = LibDecimalFloat.lt(x, exponentX, y, exponentY); + + assertEq(gt, !lt && !eq); + } + + function testGtGasDifferentSigns() external pure { + LibDecimalFloat.gt(1, 0, -1, 0); + } + + function testGtGasAZero() external pure { + LibDecimalFloat.gt(0, 0, 1, 0); + } + + function testGtGasBZero() external pure { + LibDecimalFloat.gt(1, 0, 0, 0); + } + + function testGtGasBothZero() external pure { + LibDecimalFloat.gt(0, 0, 0, 0); + } + + function testGtGasExponentDiffOverflow() external pure { + LibDecimalFloat.gt(1, type(int256).max, 1, type(int256).min); + } +} diff --git a/test/src/LibDecimalFloat.log10.t.sol b/test/src/LibDecimalFloat.log10.t.sol index 5b2dd69a..3601da79 100644 --- a/test/src/LibDecimalFloat.log10.t.sol +++ b/test/src/LibDecimalFloat.log10.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, COMPARE_EQUAL} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; import {Test, console} from "forge-std/Test.sol"; diff --git a/test/src/LibDecimalFloat.lt.t.sol b/test/src/LibDecimalFloat.lt.t.sol index 5d7e887e..55db3c25 100644 --- a/test/src/LibDecimalFloat.lt.t.sol +++ b/test/src/LibDecimalFloat.lt.t.sol @@ -1,41 +1,93 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, COMPARE_LESS_THAN} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatLtTest is Test { - /// Lt and compare need to agree. - function testLtVsCompare(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - { - bool lt = LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - int256 compare = LibDecimalFloat.compare(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + /// x !< x + function testLtX(int256 x) external pure { + bool lt = LibDecimalFloat.lt(x, 0, x, 0); + assertTrue(!lt); + } + + /// xeX !< xeX + function testLtOneEAny(int256 x, int256 exponent) external pure { + bool lt = LibDecimalFloat.lt(x, exponent, x, exponent); + assertTrue(!lt); + } + + /// xeX < xeY if X < Y && x > 0 + function testLtXEAnyVsXEAny(int256 x, int256 exponentA, int256 exponentB) external pure { + x = bound(x, 1, type(int256).max); + bool lt = LibDecimalFloat.lt(x, exponentA, x, exponentB); + + assertEq(lt, exponentA < exponentB); - if (compare == COMPARE_LESS_THAN) { - assertTrue(lt); - } else { - assertTrue(!lt); - } + // Reverse the order. + lt = LibDecimalFloat.lt(x, exponentB, x, exponentA); + assertEq(lt, exponentB < exponentA); } - // If lt then not equal nor gt. + /// xeX < xeY if X > Y && x < 0 + function testLtXEAnyVsXEAnyNegative(int256 x, int256 exponentA, int256 exponentB) external pure { + x = bound(x, type(int256).min, -1); + bool lt = LibDecimalFloat.lt(x, exponentA, x, exponentB); + + assertEq(lt, exponentA > exponentB); + + // Reverse the order. + lt = LibDecimalFloat.lt(x, exponentB, x, exponentA); + assertEq(lt, exponentB > exponentA); + } + + /// xeX !< xeY if x == 0 + function testLtZero(int256 exponentA, int256 exponentB) external pure { + bool lt = LibDecimalFloat.lt(0, exponentA, 0, exponentB); + assertTrue(!lt); + } + + /// xeX < yeY if x < 0 && y >= 0 + function testLtNegativeVsPositive( + int256 signedCoefficientNeg, + int256 exponentNeg, + int256 signedCoefficientPos, + int256 exponentPos + ) external pure { + signedCoefficientNeg = bound(signedCoefficientNeg, type(int256).min, -1); + signedCoefficientPos = bound(signedCoefficientPos, 0, type(int256).max); + + bool lt = LibDecimalFloat.lt(signedCoefficientNeg, exponentNeg, signedCoefficientPos, exponentPos); + assertTrue(lt); + + // Reverse the order. + lt = LibDecimalFloat.lt(signedCoefficientPos, exponentPos, signedCoefficientNeg, exponentNeg); + assertTrue(!lt); + } + + /// X < Y if Y !< X && X != Y function testLtVsEqualVsGt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure { bool lt = LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - bool equal = LibDecimalFloat.equal(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + bool equal = LibDecimalFloat.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); bool gt = LibDecimalFloat.gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - if (lt) { - assertTrue(!equal); - assertTrue(!gt); - } else { - assertTrue(equal || gt); - } + assertEq(lt, !equal && !gt); + } + + /// X < Y if X < 0 && Y == 0 + function testLtNegativeVsZero(int256 signedCoefficientNeg, int256 exponentNeg, int256 exponentZero) external pure { + signedCoefficientNeg = bound(signedCoefficientNeg, type(int256).min, -1); + + bool lt = LibDecimalFloat.lt(signedCoefficientNeg, exponentNeg, 0, exponentZero); + assertTrue(lt); + + // Reverse the order. + lt = LibDecimalFloat.lt(0, exponentZero, signedCoefficientNeg, exponentNeg); + assertTrue(!lt); } function testLtGasDifferentSigns() external pure { @@ -43,18 +95,18 @@ contract LibDecimalFloatLtTest is Test { } function testLtGasAZero() external pure { - LibDecimalFloat.compare(0, 0, 1, 0); + LibDecimalFloat.lt(0, 0, 1, 0); } function testLtGasBZero() external pure { - LibDecimalFloat.compare(1, 0, 0, 0); + LibDecimalFloat.lt(1, 0, 0, 0); } function testLtGasBothZero() external pure { - LibDecimalFloat.compare(0, 0, 0, 0); + LibDecimalFloat.lt(0, 0, 0, 0); } function testLtGasExponentDiffOverflow() external pure { - LibDecimalFloat.compare(1, type(int256).max, 1, type(int256).min); + LibDecimalFloat.lt(1, type(int256).max, 1, type(int256).min); } } diff --git a/test/src/LibDecimalFloat.multiply.t.sol b/test/src/LibDecimalFloat.multiply.t.sol index a029f2a6..be4a0c8c 100644 --- a/test/src/LibDecimalFloat.multiply.t.sol +++ b/test/src/LibDecimalFloat.multiply.t.sol @@ -2,10 +2,7 @@ pragma solidity =0.8.25; import { - LibDecimalFloat, - COMPARE_EQUAL, - NORMALIZED_ZERO_SIGNED_COEFFICIENT, - NORMALIZED_ZERO_EXPONENT + LibDecimalFloat, NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT } from "src/lib/LibDecimalFloat.sol"; import { EXPONENT_MIN, diff --git a/test/src/LibDecimalFloat.power.t.sol b/test/src/LibDecimalFloat.power.t.sol index b6054b8c..e1454277 100644 --- a/test/src/LibDecimalFloat.power.t.sol +++ b/test/src/LibDecimalFloat.power.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, COMPARE_EQUAL} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; import {Test, console} from "forge-std/Test.sol"; diff --git a/test/src/LibDecimalFloat.power10.t.sol b/test/src/LibDecimalFloat.power10.t.sol index de692e92..10e1f2e0 100644 --- a/test/src/LibDecimalFloat.power10.t.sol +++ b/test/src/LibDecimalFloat.power10.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, COMPARE_EQUAL} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; import {Test, console} from "forge-std/Test.sol"; From 998b2f75f48d0824335bfc21ea1a29df36918e77 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 23 Jul 2024 21:23:05 +0400 Subject: [PATCH 07/10] tests for compare --- .gas-snapshot | 134 ++++++++++++++++-------------- test/lib/LibDecimalFloatSlow.sol | 127 ++++++++++++++++++++++++++++ test/src/LibDecimalFloat.eq.t.sol | 32 ++++--- test/src/LibDecimalFloat.gt.t.sol | 35 ++++++++ test/src/LibDecimalFloat.lt.t.sol | 12 +++ 5 files changed, 264 insertions(+), 76 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 7b76174a..e17fc991 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,39 +1,39 @@ -LibDecimalFloatAbsTest:testAbsMinValue(int256) (runs: 5106, μ: 4419, ~: 4419) -LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 5107, μ: 9914, ~: 10164) -LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 5107, μ: 9040, ~: 8691) +LibDecimalFloatAbsTest:testAbsMinValue(int256) (runs: 5108, μ: 4419, ~: 4419) +LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 5109, μ: 9914, ~: 10164) +LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 5109, μ: 9040, ~: 8691) LibDecimalFloatDecimalAddTest:testAdd123456789987654321() (gas: 6203) LibDecimalFloatDecimalAddTest:testAdd123456789e9987654321() (gas: 6255) -LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 5107, μ: 14149, ~: 14075) +LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 5109, μ: 14149, ~: 14075) LibDecimalFloatDecimalAddTest:testAddOneOneNotNormalized() (gas: 5818) LibDecimalFloatDecimalAddTest:testAddOneOnePreNormalized() (gas: 4250) LibDecimalFloatDecimalAddTest:testAddOneZero() (gas: 3688) -LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 5107, μ: 6416, ~: 6427) +LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 5109, μ: 6415, ~: 6427) LibDecimalFloatDecimalAddTest:testAddZero() (gas: 3664) -LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 5107, μ: 9310, ~: 9245) +LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 5109, μ: 9310, ~: 9245) LibDecimalFloatDecimalAddTest:testAddZeroOne() (gas: 3686) -LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 5106, μ: 14299, ~: 14243) +LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 5108, μ: 14299, ~: 14243) LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 19289) -LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 5099, μ: 18700, ~: 18773) +LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 5100, μ: 18700, ~: 18773) LibDecimalFloatDecimalAddTest:testGasAddOne() (gas: 944) LibDecimalFloatDecimalAddTest:testGasAddZero() (gas: 382) -LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 5107, μ: 10148, ~: 10196) +LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 5109, μ: 10148, ~: 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: 5107, μ: 9542, ~: 7195) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 5107, μ: 8537, ~: 7207) -LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 5101, μ: 15985, ~: 15974) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 5107, μ: 14953, ~: 14718) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 5107, μ: 10117, ~: 9778) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 5107, μ: 9905, ~: 10161) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 5109, μ: 9541, ~: 7195) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 5109, μ: 8537, ~: 7207) +LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 5103, μ: 15985, ~: 15974) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 5109, μ: 14953, ~: 14718) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 5109, μ: 10117, ~: 9778) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 5109, μ: 9905, ~: 10161) LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 5102, μ: 13918, ~: 14172) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 5107, μ: 14472, ~: 14190) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 5109, μ: 14472, ~: 14190) LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 14500) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 5107, μ: 13713, ~: 13580) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 5107, μ: 4575, ~: 4575) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 5109, μ: 13713, ~: 13580) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 5109, μ: 4575, ~: 4575) LibDecimalFloatDivideTest:testDivide1Over3() (gas: 6420) LibDecimalFloatDivideTest:testDivide1Over3Gas0() (gas: 761) LibDecimalFloatDivideTest:testDivide1Over3Gas10() (gas: 7068) @@ -43,64 +43,70 @@ LibDecimalFloatDivideTest:testDivideNegative1Over3() (gas: 6490) LibDecimalFloatDivideTest:testDivideOOMs5and2() (gas: 4827) LibDecimalFloatDivideTest:testDivideOOMsOverTen() (gas: 5873) LibDecimalFloatDivideTest:testDivideTenOverOOMs() (gas: 5738) -LibDecimalFloatDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 111, μ: 23628110, ~: 23628366) -LibDecimalFloatEqTest:testEqGasAZero() (gas: 410) -LibDecimalFloatEqTest:testEqGasBZero() (gas: 409) -LibDecimalFloatEqTest:testEqGasBothZero() (gas: 430) -LibDecimalFloatEqTest:testEqGasDifferentSigns() (gas: 440) -LibDecimalFloatEqTest:testEqGasExponentDiffOverflow() (gas: 558) -LibDecimalFloatEqTest:testEqOneEAny(int256,int256) (runs: 5107, μ: 3396, ~: 3396) -LibDecimalFloatEqTest:testEqX(int256) (runs: 5107, μ: 3372, ~: 3372) -LibDecimalFloatEqTest:testEqXEAnyVsXEAny(int256,int256,int256) (runs: 5105, μ: 4698, ~: 4696) -LibDecimalFloatEqTest:testEqXEqY(int256,int256,int256,int256) (runs: 5107, μ: 714, ~: 733) -LibDecimalFloatEqTest:testEqXNotY(int256,int256,int256,int256) (runs: 5106, μ: 3863, ~: 3888) -LibDecimalFloatEqTest:testEqXNotYExponents(int256,int256,int256,int256) (runs: 5107, μ: 4222, ~: 4301) -LibDecimalFloatEqTest:testEqZero(int256,int256) (runs: 5107, μ: 3442, ~: 3442) -LibDecimalFloatGtTest:testGtGasAZero() (gas: 431) -LibDecimalFloatGtTest:testGtGasBZero() (gas: 430) -LibDecimalFloatGtTest:testGtGasBothZero() (gas: 432) -LibDecimalFloatGtTest:testGtGasDifferentSigns() (gas: 410) -LibDecimalFloatGtTest:testGtGasExponentDiffOverflow() (gas: 513) -LibDecimalFloatGtTest:testGtOneEAny(int256,int256) (runs: 5107, μ: 3445, ~: 3445) -LibDecimalFloatGtTest:testGtX(int256) (runs: 5107, μ: 3419, ~: 3419) -LibDecimalFloatGtTest:testGtXEAnyVsXEAny(int256,int256,int256) (runs: 5107, μ: 9681, ~: 9468) -LibDecimalFloatGtTest:testGtXEAnyVsXEAnyNegative(int256,int256,int256) (runs: 5107, μ: 10305, ~: 10552) -LibDecimalFloatGtTest:testGtXNotY(int256,int256,int256,int256) (runs: 5107, μ: 4209, ~: 4288) -LibDecimalFloatGtTest:testGtZero(int256,int256) (runs: 5107, μ: 3422, ~: 3422) +LibDecimalFloatDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 113, μ: 23628114, ~: 23628366) +LibDecimalFloatEqTest:testEqGasAZero() (gas: 430) +LibDecimalFloatEqTest:testEqGasBZero() (gas: 473) +LibDecimalFloatEqTest:testEqGasBothZero() (gas: 450) +LibDecimalFloatEqTest:testEqGasDifferentSigns() (gas: 482) +LibDecimalFloatEqTest:testEqGasExponentDiffOverflow() (gas: 578) +LibDecimalFloatEqTest:testEqNotReverts(int256,int256,int256,int256) (runs: 5109, μ: 653, ~: 679) +LibDecimalFloatEqTest:testEqOneEAny(int256,int256) (runs: 5109, μ: 3416, ~: 3416) +LibDecimalFloatEqTest:testEqReference(int256,int256,int256,int256) (runs: 5109, μ: 9883, ~: 11437) +LibDecimalFloatEqTest:testEqX(int256) (runs: 5109, μ: 3392, ~: 3392) +LibDecimalFloatEqTest:testEqXEAnyVsXEAny(int256,int256,int256) (runs: 5107, μ: 4716, ~: 4714) +LibDecimalFloatEqTest:testEqXEqY(int256,int256,int256,int256) (runs: 5109, μ: 732, ~: 753) +LibDecimalFloatEqTest:testEqXNotY(int256,int256,int256,int256) (runs: 5107, μ: 3927, ~: 3953) +LibDecimalFloatEqTest:testEqXNotYExponents(int256,int256,int256,int256) (runs: 5109, μ: 4241, ~: 4321) +LibDecimalFloatEqTest:testEqZero(int256,int256) (runs: 5109, μ: 3440, ~: 3440) +LibDecimalFloatGtTest:testGtGasAZero() (gas: 429) +LibDecimalFloatGtTest:testGtGasBZero() (gas: 428) +LibDecimalFloatGtTest:testGtGasBothZero() (gas: 452) +LibDecimalFloatGtTest:testGtGasDifferentSigns() (gas: 430) +LibDecimalFloatGtTest:testGtGasExponentDiffOverflow() (gas: 578) +LibDecimalFloatGtTest:testGtOneEAny(int256,int256) (runs: 5109, μ: 3443, ~: 3443) +LibDecimalFloatGtTest:testGtReference(int256,int256,int256,int256) (runs: 5109, μ: 9876, ~: 11407) +LibDecimalFloatGtTest:testGtX(int256) (runs: 5109, μ: 3417, ~: 3417) +LibDecimalFloatGtTest:testGtXEAnyVsXEAny(int256,int256,int256) (runs: 5109, μ: 9742, ~: 9530) +LibDecimalFloatGtTest:testGtXEAnyVsXEAnyNegative(int256,int256,int256) (runs: 5109, μ: 10324, ~: 10570) +LibDecimalFloatGtTest:testGtXNotY(int256,int256,int256,int256) (runs: 5109, μ: 4207, ~: 4286) +LibDecimalFloatGtTest:testGtXPositiveYNegative(int256,int256,int256,int256) (runs: 5109, μ: 12894, ~: 12801) +LibDecimalFloatGtTest:testGtXPositiveYZero(int256,int256,int256) (runs: 5109, μ: 9465, ~: 9255) +LibDecimalFloatGtTest:testGtZero(int256,int256) (runs: 5109, μ: 3465, ~: 3465) LibDecimalFloatInvTest:testInvGas0() (gas: 734) -LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 5105, μ: 13017, ~: 13131) +LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 5107, μ: 13016, ~: 13131) LibDecimalFloatInvTest:testInvSlowGas0() (gas: 755) LibDecimalFloatLog10Test:testExactLogs() (gas: 19667) -LibDecimalFloatLtTest:testLtGasAZero() (gas: 430) -LibDecimalFloatLtTest:testLtGasBZero() (gas: 452) -LibDecimalFloatLtTest:testLtGasBothZero() (gas: 431) -LibDecimalFloatLtTest:testLtGasDifferentSigns() (gas: 453) -LibDecimalFloatLtTest:testLtGasExponentDiffOverflow() (gas: 515) -LibDecimalFloatLtTest:testLtNegativeVsPositive(int256,int256,int256,int256) (runs: 5107, μ: 12842, ~: 12740) -LibDecimalFloatLtTest:testLtNegativeVsZero(int256,int256,int256) (runs: 5107, μ: 9987, ~: 10236) -LibDecimalFloatLtTest:testLtOneEAny(int256,int256) (runs: 5107, μ: 3445, ~: 3445) -LibDecimalFloatLtTest:testLtVsEqualVsGt(int256,int256,int256,int256) (runs: 5107, μ: 4238, ~: 4312) -LibDecimalFloatLtTest:testLtX(int256) (runs: 5107, μ: 3396, ~: 3396) -LibDecimalFloatLtTest:testLtXEAnyVsXEAny(int256,int256,int256) (runs: 5107, μ: 9725, ~: 9512) -LibDecimalFloatLtTest:testLtXEAnyVsXEAnyNegative(int256,int256,int256) (runs: 5107, μ: 10261, ~: 10508) -LibDecimalFloatLtTest:testLtZero(int256,int256) (runs: 5107, μ: 3422, ~: 3422) +LibDecimalFloatLtTest:testLtGasAZero() (gas: 450) +LibDecimalFloatLtTest:testLtGasBZero() (gas: 472) +LibDecimalFloatLtTest:testLtGasBothZero() (gas: 451) +LibDecimalFloatLtTest:testLtGasDifferentSigns() (gas: 473) +LibDecimalFloatLtTest:testLtGasExponentDiffOverflow() (gas: 535) +LibDecimalFloatLtTest:testLtNegativeVsPositive(int256,int256,int256,int256) (runs: 5109, μ: 12879, ~: 12780) +LibDecimalFloatLtTest:testLtNegativeVsZero(int256,int256,int256) (runs: 5109, μ: 10028, ~: 10276) +LibDecimalFloatLtTest:testLtOneEAny(int256,int256) (runs: 5109, μ: 3465, ~: 3465) +LibDecimalFloatLtTest:testLtReference(int256,int256,int256,int256) (runs: 5109, μ: 9901, ~: 11454) +LibDecimalFloatLtTest:testLtVsEqualVsGt(int256,int256,int256,int256) (runs: 5109, μ: 4258, ~: 4332) +LibDecimalFloatLtTest:testLtX(int256) (runs: 5109, μ: 3438, ~: 3438) +LibDecimalFloatLtTest:testLtXEAnyVsXEAny(int256,int256,int256) (runs: 5109, μ: 9764, ~: 9552) +LibDecimalFloatLtTest:testLtXEAnyVsXEAnyNegative(int256,int256,int256) (runs: 5109, μ: 10303, ~: 10548) +LibDecimalFloatLtTest:testLtZero(int256,int256) (runs: 5109, μ: 3442, ~: 3442) LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7213) LibDecimalFloatMultiplyTest:testMultiply123456789987654321() (gas: 3720) -LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 5107, μ: 13185, ~: 13269) +LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 5109, μ: 13184, ~: 13269) LibDecimalFloatMultiplyTest:testMultiply1e181e19() (gas: 3721) LibDecimalFloatMultiplyTest:testMultiplyGasOne() (gas: 382) LibDecimalFloatMultiplyTest:testMultiplyGasZero() (gas: 348) -LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 5107, μ: 13911, ~: 13097) +LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 5109, μ: 13910, ~: 13097) LibDecimalFloatMultiplyTest:testMultiplyOneOne() (gas: 3697) LibDecimalFloatMultiplyTest:testMultiplyOneZero() (gas: 3663) LibDecimalFloatMultiplyTest:testMultiplyZero0Exponent() (gas: 3619) -LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 5107, μ: 3907, ~: 3907) +LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 5109, μ: 3907, ~: 3907) LibDecimalFloatMultiplyTest:testMultiplyZeroOne() (gas: 3620) -LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 5107, μ: 11154, ~: 11057) -LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 5107, μ: 4718, ~: 4718) -LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 5107, μ: 13217, ~: 13268) -LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 5107, μ: 17589, ~: 17843) -LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 5107, μ: 17350, ~: 17236) +LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 5109, μ: 11154, ~: 11057) +LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 5109, μ: 4718, ~: 4718) +LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 5109, μ: 13217, ~: 13268) +LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 5108, μ: 17589, ~: 17843) +LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 5109, μ: 17350, ~: 17236) LibLogTableBytesTest:testToBytesAntiLogTableDec() (gas: 158027) LibLogTableBytesTest:testToBytesAntiLogTableDecSmall() (gas: 160620) LibLogTableBytesTest:testToBytesLogTableDec() (gas: 141631) diff --git a/test/lib/LibDecimalFloatSlow.sol b/test/lib/LibDecimalFloatSlow.sol index 72bec800..7bd98484 100644 --- a/test/lib/LibDecimalFloatSlow.sol +++ b/test/lib/LibDecimalFloatSlow.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.25; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; +import {console2} from "forge-std/Test.sol"; + library LibDecimalFloatSlow { function multiplySlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal @@ -40,4 +42,129 @@ library LibDecimalFloatSlow { function invSlow(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { return LibDecimalFloat.divide(1e37, -37, signedCoefficient, exponent); } + + function eqSlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (bool) + { + // If the exponents are the same we compare the coefficients. + if (exponentA == exponentB) { + return signedCoefficientA == signedCoefficientB; + } + + // Comparisons with zero ignore exponents. + if (signedCoefficientA == 0 || signedCoefficientB == 0) { + return signedCoefficientA == signedCoefficientB; + } + + unchecked { + int256 tmp = 0; + while (exponentA > exponentB) { + tmp = signedCoefficientA * 10; + if (tmp / 10 != signedCoefficientA) { + return false; + } + signedCoefficientA = tmp; + exponentA--; + } + + while (exponentB > exponentA) { + tmp = signedCoefficientB * 10; + if (tmp / 10 != signedCoefficientB) { + return false; + } + signedCoefficientB = tmp; + exponentB--; + } + } + + return eqSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + + function gtSlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (bool) + { + // If the exponents are the same we compare the coefficients. + if (exponentA == exponentB) { + return signedCoefficientA > signedCoefficientB; + } + + // Comparisons with zero ignore exponents. + if (signedCoefficientA == 0 || signedCoefficientB == 0) { + return signedCoefficientA > signedCoefficientB; + } + + unchecked { + int256 tmp = 0; + while (exponentA > exponentB) { + tmp = signedCoefficientA * 10; + if (tmp / 10 != signedCoefficientA) { + // A overflowed so is huge in magnitude, it's gt everything + // if positive and lt everything if it's negative. + return signedCoefficientA > 0; + } + signedCoefficientA = tmp; + exponentA--; + } + + while (exponentB > exponentA) { + tmp = signedCoefficientB * 10; + if (tmp / 10 != signedCoefficientB) { + // B overflowed so is huge in magnitude, it's gt everything + // if positive and lt everything if it's negative. + return signedCoefficientB < 0; + } + signedCoefficientB = tmp; + exponentB--; + } + } + + return gtSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + + function ltSlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (bool) + { + // If the exponents are the same we compare the coefficients. + if (exponentA == exponentB) { + return signedCoefficientA < signedCoefficientB; + } + + // Comparisons with zero ignore exponents. + if (signedCoefficientA == 0 || signedCoefficientB == 0) { + return signedCoefficientA < signedCoefficientB; + } + + unchecked { + int256 tmp = 0; + while (exponentA > exponentB) { + tmp = signedCoefficientA * 10; + if (tmp / 10 != signedCoefficientA) { + // A overflowed so is huge in magnitude, it's gt everything + // if positive and lt everything if it's negative. + return signedCoefficientA < 0; + } + signedCoefficientA = tmp; + exponentA--; + } + + while (exponentB > exponentA) { + tmp = signedCoefficientB * 10; + if (tmp / 10 != signedCoefficientB) { + // B overflowed so is huge in magnitude, it's gt everything + // if positive and lt everything if it's negative. + return signedCoefficientB > 0; + } + signedCoefficientB = tmp; + exponentB--; + } + } + + return ltSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } } diff --git a/test/src/LibDecimalFloat.eq.t.sol b/test/src/LibDecimalFloat.eq.t.sol index 379da8c4..bbaa64d0 100644 --- a/test/src/LibDecimalFloat.eq.t.sol +++ b/test/src/LibDecimalFloat.eq.t.sol @@ -3,9 +3,25 @@ pragma solidity =0.8.25; import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; + import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatEqTest is Test { + function testEqReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + { + bool actual = LibDecimalFloat.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + bool expected = LibDecimalFloatSlow.eqSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + assertEq(actual, expected); + } + + function testEqNotReverts(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + LibDecimalFloat.eq(x, exponentX, y, exponentY); + } + /// x == x function testEqX(int256 x) external pure { bool eq = LibDecimalFloat.eq(x, 0, x, 0); @@ -57,30 +73,22 @@ contract LibDecimalFloatEqTest is Test { bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); if (eq) { - if (y > x) { + if (x == y) { + assertTrue(exponentX == exponentY || x == 0); + } else if (y > x) { assertTrue(exponentY < exponentX, "y > x but exponentY >= exponentX"); assertTrue(exponentX - exponentY < 77, "y > x but exponentX - exponentY >= 77"); assertEq(x / y, int256(10 ** uint256(exponentX - exponentY)), "y > x but x / y != 10^(X - Y)"); assertEq(x % y, 0, "y > x but x % y != 0"); - } else if (x < y) { + } else { assertTrue(exponentX < exponentY, "x < y but exponentX >= exponentY"); assertTrue(exponentY - exponentX < 77, "x < y but exponentY - exponentX >= 77"); assertEq(y / x, int256(10 ** uint256(exponentY - exponentX)), "x < y but y / x != 10^(Y - X)"); assertEq(y % x, 0, "x < y but y % x != 0"); - } else { - assertEq(x, y); - assertTrue(exponentX == exponentY || x == 0); } } else { if (x == y) { assertTrue(exponentX != exponentY); - if (exponentX < exponentY && exponentY - exponentX < 77) { - assertTrue(x / y != int256(10 ** uint256(exponentX - exponentY))); - assertTrue(x % y != 0); - } else if (exponentY < exponentX && exponentX - exponentY < 77) { - assertTrue(y / x != int256(10 ** uint256(exponentY - exponentX))); - assertTrue(y % x != 0); - } } } } diff --git a/test/src/LibDecimalFloat.gt.t.sol b/test/src/LibDecimalFloat.gt.t.sol index bd048291..23b1f49f 100644 --- a/test/src/LibDecimalFloat.gt.t.sol +++ b/test/src/LibDecimalFloat.gt.t.sol @@ -3,9 +3,21 @@ pragma solidity =0.8.25; import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; + import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatGtTest is Test { + function testGtReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + { + bool actual = LibDecimalFloat.gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + bool expected = LibDecimalFloatSlow.gtSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + assertEq(actual, expected); + } + /// x !> x function testGtX(int256 x) external pure { bool gt = LibDecimalFloat.gt(x, 0, x, 0); @@ -48,6 +60,18 @@ contract LibDecimalFloatGtTest is Test { assertTrue(!gt); } + /// xeX > yeY if x >= 0 && y < 0 + function testGtXPositiveYNegative(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + x = bound(x, 0, type(int256).max); + y = bound(y, type(int256).min, -1); + bool gt = LibDecimalFloat.gt(x, exponentX, y, exponentY); + assertTrue(gt); + + // Reverse the order. + gt = LibDecimalFloat.gt(y, exponentY, x, exponentX); + assertTrue(!gt); + } + /// xeX > yeY if xeX != yeY && xeX !< yeY function testGtXNotY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { bool gt = LibDecimalFloat.gt(x, exponentX, y, exponentY); @@ -57,6 +81,17 @@ contract LibDecimalFloatGtTest is Test { assertEq(gt, !lt && !eq); } + /// xeX > yeY if xeX > 0 && yeY == 0 + function testGtXPositiveYZero(int256 x, int256 exponentX, int256 exponentZero) external pure { + x = bound(x, 1, type(int256).max); + bool gt = LibDecimalFloat.gt(x, exponentX, 0, exponentZero); + assertTrue(gt); + + // Reverse the order. + gt = LibDecimalFloat.gt(0, exponentZero, x, exponentX); + assertTrue(!gt); + } + function testGtGasDifferentSigns() external pure { LibDecimalFloat.gt(1, 0, -1, 0); } diff --git a/test/src/LibDecimalFloat.lt.t.sol b/test/src/LibDecimalFloat.lt.t.sol index 55db3c25..a26e461f 100644 --- a/test/src/LibDecimalFloat.lt.t.sol +++ b/test/src/LibDecimalFloat.lt.t.sol @@ -3,9 +3,21 @@ pragma solidity =0.8.25; import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; + import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatLtTest is Test { + function testLtReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + { + bool actual = LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + bool expected = LibDecimalFloatSlow.ltSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + assertEq(actual, expected); + } + /// x !< x function testLtX(int256 x) external pure { bool lt = LibDecimalFloat.lt(x, 0, x, 0); From 48476f943c492b7936370b69f797350a92b27a42 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 23 Jul 2024 21:35:16 +0400 Subject: [PATCH 08/10] docs --- src/lib/LibDecimalFloat.sol | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 60bab12b..4abf03aa 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -568,6 +568,19 @@ library LibDecimalFloat { return (signedCoefficient, exponent); } + /// Numeric equality for floats. + /// Two floats are equal if their numeric value is equal. + /// For example, 1e2, 10e1, and 100e0 are all equal. Also implies that 0eX + /// and 0eY are equal for all X and Y. + /// Any representable value can be equality checked without precision loss, + /// e.g. no normalization is done internally. + /// @param signedCoefficientA The signed coefficient of the first floating + /// point number. + /// @param exponentA The exponent of the first floating point number. + /// @param signedCoefficientB The signed coefficient of the second floating + /// point number. + /// @param exponentB The exponent of the second floating point number. + /// @return `true` if the two floats are equal, `false` otherwise. function eq(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure @@ -579,6 +592,19 @@ library LibDecimalFloat { return signedCoefficientA == signedCoefficientB; } + /// Numeric less than for floats. + /// A float is less than another if its numeric value is less than the other. + /// For example, 1e2 is less than 1e3, and 1e2 is less than 2e2. + /// Any representable value can be compared without precision loss, e.g. no + /// normalization is done internally. + /// @param signedCoefficientA The signed coefficient of the first floating + /// point number. + /// @param exponentA The exponent of the first floating point number. + /// @param signedCoefficientB The signed coefficient of the second floating + /// point number. + /// @param exponentB The exponent of the second floating point number. + /// @return `true` if the first float is less than the second, `false` + /// otherwise. function lt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure @@ -590,6 +616,19 @@ library LibDecimalFloat { return signedCoefficientA < signedCoefficientB; } + /// Numeric greater than for floats. + /// A float is greater than another if its numeric value is greater than the + /// other. For example, 1e3 is greater than 1e2, and 2e2 is greater than 1e2. + /// Any representable value can be compared without precision loss, e.g. no + /// normalization is done internally. + /// @param signedCoefficientA The signed coefficient of the first floating + /// point number. + /// @param exponentA The exponent of the first floating point number. + /// @param signedCoefficientB The signed coefficient of the second floating + /// point number. + /// @param exponentB The exponent of the second floating point number. + /// @return `true` if the first float is greater than the second, `false` + /// otherwise. function gt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure From 0928ca5903d92831840a3a28fe4e3b5f1226d2f5 Mon Sep 17 00:00:00 2001 From: David Meister Date: Wed, 24 Jul 2024 22:14:55 +0400 Subject: [PATCH 09/10] Update test/lib/LibDecimalFloatSlow.sol --- test/lib/LibDecimalFloatSlow.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/lib/LibDecimalFloatSlow.sol b/test/lib/LibDecimalFloatSlow.sol index 7bd98484..c84dc60b 100644 --- a/test/lib/LibDecimalFloatSlow.sol +++ b/test/lib/LibDecimalFloatSlow.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.25; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; -import {console2} from "forge-std/Test.sol"; library LibDecimalFloatSlow { function multiplySlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) From cdda9c6f9ab6fa6a91918b184225f794c2e18504 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Wed, 24 Jul 2024 23:40:06 +0400 Subject: [PATCH 10/10] fmt --- test/lib/LibDecimalFloatSlow.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/lib/LibDecimalFloatSlow.sol b/test/lib/LibDecimalFloatSlow.sol index c84dc60b..e2b1f975 100644 --- a/test/lib/LibDecimalFloatSlow.sol +++ b/test/lib/LibDecimalFloatSlow.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.25; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; - library LibDecimalFloatSlow { function multiplySlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal