diff --git a/.gas-snapshot b/.gas-snapshot index ac80e98e..8621da76 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,42 +1,42 @@ -DecimalFloatAbsTest:testAbsDeployed(bytes32) (runs: 5098, μ: 2661669, ~: 2661611) -DecimalFloatAddTest:testAddDeployed(bytes32,bytes32) (runs: 5098, μ: 2665752, ~: 2665829) -DecimalFloatCeilTest:testCeilDeployed(bytes32) (runs: 5098, μ: 2661695, ~: 2661324) -DecimalFloatConstantsTest:testEDeployed() (gas: 2660876) -DecimalFloatConstantsTest:testMaxNegativeValueDeployed() (gas: 2660843) -DecimalFloatConstantsTest:testMaxPositiveValueDeployed() (gas: 2660866) -DecimalFloatConstantsTest:testMinNegativeValueDeployed() (gas: 2660841) -DecimalFloatConstantsTest:testMinPositiveValueDeployed() (gas: 2660842) -DecimalFloatConstantsTest:testZeroDeployed() (gas: 2660909) -DecimalFloatDivTest:testDivDeployed(bytes32,bytes32) (runs: 5098, μ: 2667703, ~: 2667779) -DecimalFloatEqTest:testEqDeployed(bytes32,bytes32) (runs: 5098, μ: 2662076, ~: 2662001) -DecimalFloatFloorTest:testFloorDeployed(bytes32) (runs: 5098, μ: 2661551, ~: 2661367) -DecimalFloatFormatTest:testFormatDeployed(bytes32) (runs: 5098, μ: 2665643, ~: 2665501) -DecimalFloatFracTest:testFracDeployed(bytes32) (runs: 5098, μ: 2661920, ~: 2661904) -DecimalFloatFromFixedDecimalLosslessTest:testFromFixedDecimalLosslessDeployed(uint256,uint8) (runs: 5098, μ: 2662474, ~: 2662408) -DecimalFloatFromFixedDecimalLossyTest:testFromFixedDecimalLossyDeployed(uint256,uint8) (runs: 5098, μ: 2662958, ~: 2662878) -DecimalFloatGtTest:testGtDeployed(bytes32,bytes32) (runs: 5098, μ: 2661995, ~: 2661921) -DecimalFloatGteTest:testGteDeployed(bytes32,bytes32) (runs: 5098, μ: 2662068, ~: 2661994) -DecimalFloatInvTest:testInvDeployed(bytes32) (runs: 5098, μ: 2666551, ~: 2666588) -DecimalFloatIsZeroTest:testIsZeroDeployed(bytes32) (runs: 5098, μ: 2661186, ~: 2661186) -DecimalFloatLtTest:testLtDeployed(bytes32,bytes32) (runs: 5098, μ: 2661994, ~: 2661920) -DecimalFloatLteTest:testLteDeployed(bytes32,bytes32) (runs: 5098, μ: 2662026, ~: 2661951) -DecimalFloatMaxTest:testMaxDeployed(bytes32,bytes32) (runs: 5098, μ: 2662057, ~: 2661995) -DecimalFloatMinTest:testMinDeployed(bytes32,bytes32) (runs: 5098, μ: 2662055, ~: 2661993) -DecimalFloatMinusTest:testMinusDeployed(bytes32) (runs: 5098, μ: 2661791, ~: 2661791) -DecimalFloatMulTest:testMulDeployed(bytes32,bytes32) (runs: 5098, μ: 2665714, ~: 2666527) +DecimalFloatAbsTest:testAbsDeployed(bytes32) (runs: 5098, μ: 3058233, ~: 3058176) +DecimalFloatAddTest:testAddDeployed(bytes32,bytes32) (runs: 5098, μ: 3062294, ~: 3062372) +DecimalFloatCeilTest:testCeilDeployed(bytes32) (runs: 5098, μ: 3058259, ~: 3057889) +DecimalFloatConstantsTest:testEDeployed() (gas: 3057419) +DecimalFloatConstantsTest:testMaxNegativeValueDeployed() (gas: 3057408) +DecimalFloatConstantsTest:testMaxPositiveValueDeployed() (gas: 3057409) +DecimalFloatConstantsTest:testMinNegativeValueDeployed() (gas: 3057384) +DecimalFloatConstantsTest:testMinPositiveValueDeployed() (gas: 3057385) +DecimalFloatConstantsTest:testZeroDeployed() (gas: 3057452) +DecimalFloatDivTest:testDivDeployed(bytes32,bytes32) (runs: 5098, μ: 3064244, ~: 3064316) +DecimalFloatEqTest:testEqDeployed(bytes32,bytes32) (runs: 5098, μ: 3058618, ~: 3058544) +DecimalFloatFloorTest:testFloorDeployed(bytes32) (runs: 5098, μ: 3058072, ~: 3057887) +DecimalFloatFormatTest:testFormatDeployed(bytes32,uint256) (runs: 5098, μ: 3083592, ~: 3090149) +DecimalFloatFracTest:testFracDeployed(bytes32) (runs: 5098, μ: 3058464, ~: 3058447) +DecimalFloatFromFixedDecimalLosslessTest:testFromFixedDecimalLosslessDeployed(uint256,uint8) (runs: 5098, μ: 3059017, ~: 3058951) +DecimalFloatFromFixedDecimalLossyTest:testFromFixedDecimalLossyDeployed(uint256,uint8) (runs: 5098, μ: 3059504, ~: 3059421) +DecimalFloatGtTest:testGtDeployed(bytes32,bytes32) (runs: 5098, μ: 3058560, ~: 3058486) +DecimalFloatGteTest:testGteDeployed(bytes32,bytes32) (runs: 5098, μ: 3058546, ~: 3058472) +DecimalFloatInvTest:testInvDeployed(bytes32) (runs: 5098, μ: 3063094, ~: 3063131) +DecimalFloatIsZeroTest:testIsZeroDeployed(bytes32) (runs: 5098, μ: 3057729, ~: 3057729) +DecimalFloatLtTest:testLtDeployed(bytes32,bytes32) (runs: 5098, μ: 3058537, ~: 3058463) +DecimalFloatLteTest:testLteDeployed(bytes32,bytes32) (runs: 5098, μ: 3058590, ~: 3058516) +DecimalFloatMaxTest:testMaxDeployed(bytes32,bytes32) (runs: 5098, μ: 3058599, ~: 3058538) +DecimalFloatMinTest:testMinDeployed(bytes32,bytes32) (runs: 5098, μ: 3058596, ~: 3058536) +DecimalFloatMinusTest:testMinusDeployed(bytes32) (runs: 5098, μ: 3058334, ~: 3058334) +DecimalFloatMulTest:testMulDeployed(bytes32,bytes32) (runs: 5098, μ: 3062191, ~: 3063003) DecimalFloatPackLosslessTest:testPackDeployed(int224,int32) (runs: 5098, μ: 158769, ~: 158769) -DecimalFloatParseTest:testParseDeployed(string) (runs: 5098, μ: 2664423, ~: 2664292) -DecimalFloatPowTest:testPowDeployed(bytes32,bytes32) (runs: 5098, μ: 2673719, ~: 2672635) -DecimalFloatSqrtTest:testSqrtDeployed(bytes32) (runs: 5098, μ: 2673426, ~: 2673337) -DecimalFloatSubTest:testSubDeployed(bytes32,bytes32) (runs: 5098, μ: 2666110, ~: 2666129) -DecimalFloatToFixedDecimalLosslessTest:testToFixedDecimalLosslessDeployed(bytes32,uint8) (runs: 5098, μ: 2663137, ~: 2663014) -DecimalFloatToFixedDecimalLossyTest:testToFixedDecimalLossyDeployed(bytes32,uint8) (runs: 5098, μ: 2663251, ~: 2663520) +DecimalFloatParseTest:testParseDeployed(string) (runs: 5098, μ: 3060967, ~: 3060836) +DecimalFloatPowTest:testPowDeployed(bytes32,bytes32) (runs: 5098, μ: 3070355, ~: 3069178) +DecimalFloatSqrtTest:testSqrtDeployed(bytes32) (runs: 5098, μ: 3070062, ~: 3069880) +DecimalFloatSubTest:testSubDeployed(bytes32,bytes32) (runs: 5098, μ: 3062590, ~: 3062628) +DecimalFloatToFixedDecimalLosslessTest:testToFixedDecimalLosslessDeployed(bytes32,uint8) (runs: 5098, μ: 3059704, ~: 3059579) +DecimalFloatToFixedDecimalLossyTest:testToFixedDecimalLossyDeployed(bytes32,uint8) (runs: 5098, μ: 3059795, ~: 3060063) LibDecimalFloatAbsTest:testAbsMinValue(int32) (runs: 5098, μ: 5121, ~: 5121) LibDecimalFloatAbsTest:testAbsNegative(int256,int32) (runs: 5098, μ: 10475, ~: 10702) LibDecimalFloatAbsTest:testAbsNonNegative(int256,int32) (runs: 5098, μ: 9641, ~: 9392) LibDecimalFloatCeilTest:testCeilExamples() (gas: 30794) LibDecimalFloatCeilTest:testCeilInRange(int224,int256) (runs: 5098, μ: 11040, ~: 10713) -LibDecimalFloatCeilTest:testCeilLessThanMin(int224,int256) (runs: 5098, μ: 10060, ~: 9820) +LibDecimalFloatCeilTest:testCeilLessThanMin(int224,int256) (runs: 5098, μ: 10059, ~: 9820) LibDecimalFloatCeilTest:testCeilNonNegative(int224,int256) (runs: 5098, μ: 8961, ~: 9212) LibDecimalFloatCeilTest:testCeilNotReverts(bytes32) (runs: 5098, μ: 598, ~: 411) LibDecimalFloatCeilTest:testCeilZero(int32) (runs: 5098, μ: 5438, ~: 5438) @@ -68,19 +68,19 @@ LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOneMillion() (gas: 685937) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOverflow() (gas: 715261) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyPacked(uint256,uint8) (runs: 5098, μ: 9481, ~: 9396) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 5098, μ: 5978, ~: 5937) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 5098, μ: 6062, ~: 5860) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 5098, μ: 6061, ~: 5860) LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 5097, μ: 15987, ~: 15862) LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 5098, μ: 14963, ~: 14729) LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 5098, μ: 10153, ~: 9811) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 5098, μ: 10826, ~: 11076) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 5098, μ: 10825, ~: 11076) LibDecimalFloatDecimalTest:testToFixedDecimalLossyPacked(bytes32,uint8) (runs: 5098, μ: 6803, ~: 6905) LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 5097, μ: 15348, ~: 15612) LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 5098, μ: 14492, ~: 14212) LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 14500) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 5098, μ: 13737, ~: 13602) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 5098, μ: 13738, ~: 13602) LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 5098, μ: 4575, ~: 4575) -LibDecimalFloatDivTest:testDivByNegativeOneFloat(int224,int32) (runs: 5098, μ: 327534, ~: 326998) -LibDecimalFloatDivTest:testDivByOneFloat(int224,int32) (runs: 5098, μ: 303116, ~: 302160) +LibDecimalFloatDivTest:testDivByNegativeOneFloat(int224,int32) (runs: 5098, μ: 327533, ~: 326998) +LibDecimalFloatDivTest:testDivByOneFloat(int224,int32) (runs: 5098, μ: 303113, ~: 302160) LibDecimalFloatDivTest:testDivPacked(bytes32,bytes32) (runs: 5098, μ: 11577, ~: 11667) LibDecimalFloatEqTest:testEqPacked(bytes32,bytes32) (runs: 5098, μ: 5524, ~: 5450) LibDecimalFloatEqTest:testEqXNotYExponents(bytes32,bytes32) (runs: 5098, μ: 4341, ~: 4234) @@ -107,13 +107,13 @@ LibDecimalFloatGtTest:testGtGasBothZero() (gas: 751) LibDecimalFloatGtTest:testGtGasDifferentSigns() (gas: 974) LibDecimalFloatGtTest:testGtGasExponentDiffOverflow() (gas: 1143) LibDecimalFloatGtTest:testGtOneEAny(bytes32) (runs: 5098, μ: 3494, ~: 3494) -LibDecimalFloatGtTest:testGtReference(int224,int32,int224,int32) (runs: 5098, μ: 8068, ~: 6285) +LibDecimalFloatGtTest:testGtReference(int224,int32,int224,int32) (runs: 5098, μ: 8067, ~: 6285) LibDecimalFloatGtTest:testGtX(int224,int32) (runs: 5098, μ: 3882, ~: 3882) -LibDecimalFloatGtTest:testGtXEAnyVsXEAny(int256,int32,int32) (runs: 5098, μ: 10592, ~: 10332) +LibDecimalFloatGtTest:testGtXEAnyVsXEAny(int256,int32,int32) (runs: 5098, μ: 10593, ~: 10332) LibDecimalFloatGtTest:testGtXEAnyVsXEAnyNegative(int256,int32,int32) (runs: 5098, μ: 11144, ~: 11349) LibDecimalFloatGtTest:testGtXNotY(bytes32,bytes32) (runs: 5098, μ: 4341, ~: 4232) LibDecimalFloatGtTest:testGtXPositiveYNegative(int256,int32,int256,int32) (runs: 5098, μ: 13764, ~: 13595) -LibDecimalFloatGtTest:testGtXPositiveYZero(int256,int32,int32) (runs: 5098, μ: 10273, ~: 10026) +LibDecimalFloatGtTest:testGtXPositiveYZero(int256,int32,int32) (runs: 5098, μ: 10274, ~: 10026) LibDecimalFloatGtTest:testGtZero(int32,int32) (runs: 5098, μ: 4793, ~: 4793) LibDecimalFloatGteTest:testGteGasAZero() (gas: 976) LibDecimalFloatGteTest:testGteGasBZero() (gas: 1020) @@ -135,7 +135,7 @@ LibDecimalFloatImplementationAddTest:testAddNeverRevert(int256,int256,int256,int LibDecimalFloatImplementationAddTest:testAddOneOneNotMaximized() (gas: 6148) LibDecimalFloatImplementationAddTest:testAddOneOnePreMaximized() (gas: 4300) LibDecimalFloatImplementationAddTest:testAddOneZero() (gas: 3666) -LibDecimalFloatImplementationAddTest:testAddSameExponent(int256,int256) (runs: 5097, μ: 6865, ~: 6935) +LibDecimalFloatImplementationAddTest:testAddSameExponent(int256,int256) (runs: 5098, μ: 6865, ~: 6935) LibDecimalFloatImplementationAddTest:testAddZero() (gas: 3665) LibDecimalFloatImplementationAddTest:testAddZeroAnyExponent(int128) (runs: 5098, μ: 9293, ~: 9271) LibDecimalFloatImplementationAddTest:testAddZeroOne() (gas: 3664) @@ -151,13 +151,13 @@ LibDecimalFloatImplementationDivTest:testDiv1Over3Gas0() (gas: 2277) LibDecimalFloatImplementationDivTest:testDiv1Over3Gas10() (gas: 19442) LibDecimalFloatImplementationDivTest:testDiv1Over9Over1Over3() (gas: 14735) LibDecimalFloatImplementationDivTest:testDiv1e18Over3() (gas: 6766) -LibDecimalFloatImplementationDivTest:testDivBy1(int256,int256) (runs: 5098, μ: 335339, ~: 339387) -LibDecimalFloatImplementationDivTest:testDivByNegativeOneFloat(int256,int256) (runs: 5098, μ: 337845, ~: 342152) +LibDecimalFloatImplementationDivTest:testDivBy1(int256,int256) (runs: 5098, μ: 335333, ~: 339387) +LibDecimalFloatImplementationDivTest:testDivByNegativeOneFloat(int256,int256) (runs: 5098, μ: 337839, ~: 342152) LibDecimalFloatImplementationDivTest:testDivNegative1Over3() (gas: 7157) LibDecimalFloatImplementationDivTest:testDivOOMs5and2() (gas: 6387) LibDecimalFloatImplementationDivTest:testDivOOMsOverTen() (gas: 7077) LibDecimalFloatImplementationDivTest:testDivTenOverOOMs() (gas: 6990) -LibDecimalFloatImplementationDivTest:testUnnormalizedThreesDiv0(int256,int256) (runs: 102, μ: 27473206, ~: 27491668) +LibDecimalFloatImplementationDivTest:testUnnormalizedThreesDiv0(int256,int256) (runs: 102, μ: 27472606, ~: 27491668) LibDecimalFloatImplementationEqTest:testEqGasAZero() (gas: 430) LibDecimalFloatImplementationEqTest:testEqGasBZero() (gas: 473) LibDecimalFloatImplementationEqTest:testEqGasBothZero() (gas: 450) @@ -165,7 +165,7 @@ LibDecimalFloatImplementationEqTest:testEqGasDifferentSigns() (gas: 482) LibDecimalFloatImplementationEqTest:testEqGasExponentDiffOverflow() (gas: 533) LibDecimalFloatImplementationEqTest:testEqNotReverts(int256,int256,int256,int256) (runs: 5098, μ: 654, ~: 679) LibDecimalFloatImplementationEqTest:testEqOneEAny(int256,int256) (runs: 5098, μ: 3416, ~: 3416) -LibDecimalFloatImplementationEqTest:testEqReference(int256,int256,int256,int256) (runs: 5098, μ: 9903, ~: 11446) +LibDecimalFloatImplementationEqTest:testEqReference(int256,int256,int256,int256) (runs: 5098, μ: 9903, ~: 11437) LibDecimalFloatImplementationEqTest:testEqX(int256) (runs: 5098, μ: 3392, ~: 3392) LibDecimalFloatImplementationEqTest:testEqXEAnyVsXEAny(int256,int256,int256) (runs: 5098, μ: 4718, ~: 4714) LibDecimalFloatImplementationEqTest:testEqXEqY(int256,int256,int256,int256) (runs: 5098, μ: 732, ~: 753) @@ -173,7 +173,7 @@ LibDecimalFloatImplementationEqTest:testEqXNotY(int256,int256,int256,int256) (ru LibDecimalFloatImplementationEqTest:testEqZero(int256,int256) (runs: 5098, μ: 3440, ~: 3440) LibDecimalFloatImplementationInvTest:testInv0() (gas: 5527) LibDecimalFloatImplementationInvTest:testInvGas0() (gas: 2081) -LibDecimalFloatImplementationInvTest:testInvReference(int256,int256) (runs: 5098, μ: 14577, ~: 14532) +LibDecimalFloatImplementationInvTest:testInvReference(int256,int256) (runs: 5098, μ: 14576, ~: 14532) LibDecimalFloatImplementationInvTest:testInvSlowGas0() (gas: 2265) LibDecimalFloatImplementationLog10Test:testExactLogs() (gas: 1265623) LibDecimalFloatImplementationLog10Test:testExactLookupsLog10() (gas: 1301555) @@ -182,7 +182,7 @@ LibDecimalFloatImplementationLog10Test:testSub1() (gas: 1262032) LibDecimalFloatImplementationMaximizeTest:testMaximizedEverything(int256,int256) (runs: 5098, μ: 9478, ~: 9455) LibDecimalFloatImplementationMaximizeTest:testMaximizedExamples() (gas: 165819) LibDecimalFloatImplementationMaximizeTest:testMaximizedIdempotent(int256,int256) (runs: 5098, μ: 9903, ~: 9868) -LibDecimalFloatImplementationMaximizeTest:testMaximizedReference(int256,int256) (runs: 5098, μ: 13377, ~: 14267) +LibDecimalFloatImplementationMaximizeTest:testMaximizedReference(int256,int256) (runs: 5098, μ: 13376, ~: 14263) LibDecimalFloatImplementationMinusTest:testMinusIsSubZero(int256,int256,int256) (runs: 5098, μ: 12949, ~: 12929) LibDecimalFloatImplementationMulTest:testMul123456789987654321() (gas: 5559) LibDecimalFloatImplementationMulTest:testMul123456789987654321WithExponents(int128,int128) (runs: 5098, μ: 15078, ~: 15160) @@ -205,37 +205,37 @@ LibDecimalFloatImplementationPow10Test:testExactPows() (gas: 1260107) LibDecimalFloatImplementationPow10Test:testInterpolatedLookupsPower() (gas: 1285510) LibDecimalFloatImplementationPow10Test:testNoRevert(int224,int32) (runs: 5098, μ: 1259368, ~: 1259107) LibDecimalFloatImplementationSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 5098, μ: 15801, ~: 15834) -LibDecimalFloatImplementationSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 5098, μ: 14991, ~: 14932) +LibDecimalFloatImplementationSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 5098, μ: 14990, ~: 14932) LibDecimalFloatImplementationSubTest:testSubOneFromMax() (gas: 6517) LibDecimalFloatImplementationSubTest:testSubSelf(int224,int32) (runs: 5098, μ: 5510, ~: 5620) LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyMixedAB(uint256,uint256,uint256,int256) (runs: 5098, μ: 9961, ~: 9888) LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyMixedABOverflow(uint256,uint256,uint256,int256) (runs: 5098, μ: 11207, ~: 11200) LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyMixedBA(uint256,uint256,uint256,int256) (runs: 5098, μ: 11205, ~: 11198) -LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyMixedBAOverflow(uint256,uint256,int256) (runs: 5098, μ: 9071, ~: 9018) +LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyMixedBAOverflow(uint256,uint256,int256) (runs: 5098, μ: 9070, ~: 9018) LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyNegative(uint256,uint256,uint256,int256) (runs: 5098, μ: 9968, ~: 9892) LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyNegativeOverflow(uint256,uint256,uint256,int256) (runs: 5098, μ: 11200, ~: 11190) LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyPositive(uint256,uint256,uint256,int256) (runs: 5098, μ: 9103, ~: 9033) LibDecimalFloatImplementationUnabsUnsignedMulOrDivLossyTest:testUnabsUnsignedMulOrDivLossyPositiveOverflow(uint256,uint256,uint256,int256) (runs: 5098, μ: 11090, ~: 11086) LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentExamples() (gas: 13429) -LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerExponentOverflowRescaleRevert(int256,int256,int256) (runs: 5097, μ: 14426, ~: 14395) +LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerExponentOverflowRescaleRevert(int256,int256,int256) (runs: 5098, μ: 14426, ~: 14395) LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerExponentVeryLargeDiffRevert(int256,int256,int256) (runs: 5098, μ: 13307, ~: 13527) LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerTargetExponentNoRevert(int256,int256,int256) (runs: 5098, μ: 11669, ~: 11718) LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentSameExponentNoop(int256,int256) (runs: 5098, μ: 3676, ~: 3676) -LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentSmallerExponentNoRevert(int256,int256,int256) (runs: 5097, μ: 13874, ~: 13665) +LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentSmallerExponentNoRevert(int256,int256,int256) (runs: 5096, μ: 13874, ~: 13665) LibDecimalFloatInvTest:testInvMem(bytes32) (runs: 5098, μ: 10606, ~: 10628) LibDecimalFloatIsZeroTest:testIsZeroDeployed(bytes32) (runs: 5098, μ: 3899, ~: 3899) LibDecimalFloatIsZeroTest:testIsZeroEqZero(bytes32) (runs: 5098, μ: 3527, ~: 3527) LibDecimalFloatIsZeroTest:testIsZeroExamples(int32) (runs: 5098, μ: 4477, ~: 4477) LibDecimalFloatIsZeroTest:testNotIsZero(int224,int32) (runs: 5098, μ: 3896, ~: 3896) -LibDecimalFloatLog10Test:testLog10Packed(bytes32) (runs: 5098, μ: 1646641, ~: 1270121) +LibDecimalFloatLog10Test:testLog10Packed(bytes32) (runs: 5098, μ: 1646402, ~: 1270121) LibDecimalFloatLtTest:testLtExamples() (gas: 3994) LibDecimalFloatLtTest:testLtGasAZero() (gas: 946) LibDecimalFloatLtTest:testLtGasBZero() (gas: 1012) LibDecimalFloatLtTest:testLtGasBothZero() (gas: 969) LibDecimalFloatLtTest:testLtGasDifferentSigns() (gas: 969) LibDecimalFloatLtTest:testLtGasExponentDiffOverflow() (gas: 1053) -LibDecimalFloatLtTest:testLtNegativeVsPositive(int256,int32,int256,int32) (runs: 5098, μ: 13750, ~: 13593) -LibDecimalFloatLtTest:testLtNegativeVsZero(int256,int32,int32) (runs: 5098, μ: 10790, ~: 11008) +LibDecimalFloatLtTest:testLtNegativeVsPositive(int256,int32,int256,int32) (runs: 5098, μ: 13749, ~: 13593) +LibDecimalFloatLtTest:testLtNegativeVsZero(int256,int32,int32) (runs: 5098, μ: 10789, ~: 11008) LibDecimalFloatLtTest:testLtOneEAny(int224,int32) (runs: 5098, μ: 3905, ~: 3905) LibDecimalFloatLtTest:testLtReference(bytes32,bytes32) (runs: 5098, μ: 4697, ~: 4998) LibDecimalFloatLtTest:testLtVsEqualVsGt(bytes32,bytes32) (runs: 5098, μ: 4323, ~: 4210) @@ -249,72 +249,77 @@ LibDecimalFloatLteTest:testLteGasBothZero() (gas: 753) LibDecimalFloatLteTest:testLteGasDifferentSigns() (gas: 976) LibDecimalFloatLteTest:testLteGasExponentDiffOverflow() (gas: 1082) LibDecimalFloatLteTest:testLteOneEAny(bytes32) (runs: 5098, μ: 3493, ~: 3493) -LibDecimalFloatLteTest:testLteReference(int224,int32,int224,int32) (runs: 5098, μ: 8113, ~: 6341) +LibDecimalFloatLteTest:testLteReference(int224,int32,int224,int32) (runs: 5098, μ: 8112, ~: 6341) LibDecimalFloatLteTest:testLteX(int224,int32) (runs: 5098, μ: 3904, ~: 3904) LibDecimalFloatLteTest:testLteXEAnyVsXEAny(int256,int32,int32) (runs: 5098, μ: 10582, ~: 10321) LibDecimalFloatLteTest:testLteXEAnyVsXEAnyNegative(int256,int32,int32) (runs: 5098, μ: 11177, ~: 11383) LibDecimalFloatLteTest:testLteXNotLtY(bytes32,bytes32) (runs: 5098, μ: 3882, ~: 3807) -LibDecimalFloatLteTest:testLteXPositiveYNegative(int256,int32,int256,int32) (runs: 5098, μ: 13102, ~: 12931) +LibDecimalFloatLteTest:testLteXPositiveYNegative(int256,int32,int256,int32) (runs: 5098, μ: 13103, ~: 12931) LibDecimalFloatLteTest:testLteXPositiveYZero(int256,int32,int32) (runs: 5098, μ: 9575, ~: 9191) LibDecimalFloatLteTest:testLteZero(int32,int32) (runs: 5098, μ: 4816, ~: 4816) LibDecimalFloatMaxTest:testMaxX(bytes32) (runs: 5098, μ: 4246, ~: 4246) LibDecimalFloatMaxTest:testMaxXY(bytes32,bytes32) (runs: 5098, μ: 4689, ~: 4613) LibDecimalFloatMaxTest:testMaxXYEqual(bytes32) (runs: 5098, μ: 5272, ~: 5272) -LibDecimalFloatMaxTest:testMaxXYGreater(bytes32,bytes32) (runs: 5097, μ: 6129, ~: 6016) -LibDecimalFloatMaxTest:testMaxXYLess(bytes32,bytes32) (runs: 5097, μ: 6141, ~: 6027) +LibDecimalFloatMaxTest:testMaxXYGreater(bytes32,bytes32) (runs: 5098, μ: 6129, ~: 6016) +LibDecimalFloatMaxTest:testMaxXYLess(bytes32,bytes32) (runs: 5096, μ: 6141, ~: 6027) LibDecimalFloatMinTest:testMinX(bytes32) (runs: 5098, μ: 4268, ~: 4268) LibDecimalFloatMinTest:testMinXY(bytes32,bytes32) (runs: 5098, μ: 4689, ~: 4613) LibDecimalFloatMinTest:testMinXYEqual(bytes32) (runs: 5098, μ: 5292, ~: 5292) -LibDecimalFloatMinTest:testMinXYGreater(bytes32,bytes32) (runs: 5097, μ: 6074, ~: 5961) -LibDecimalFloatMinTest:testMinXYLess(bytes32,bytes32) (runs: 5097, μ: 6087, ~: 5972) +LibDecimalFloatMinTest:testMinXYGreater(bytes32,bytes32) (runs: 5098, μ: 6074, ~: 5961) +LibDecimalFloatMinTest:testMinXYLess(bytes32,bytes32) (runs: 5096, μ: 6087, ~: 5972) LibDecimalFloatMinusTest:testMinusPacked(bytes32) (runs: 5098, μ: 5550, ~: 5550) LibDecimalFloatMixedTest:testDiv1Over3Mixed() (gas: 11063) LibDecimalFloatMulTest:testMulPacked(bytes32,bytes32) (runs: 5098, μ: 9630, ~: 10369) LibDecimalFloatPackTest:testPartsRoundTrip(int224,int32) (runs: 5098, μ: 5352, ~: 5352) -LibDecimalFloatPow10Test:testPow10Packed(bytes32) (runs: 5098, μ: 1646622, ~: 1256455) -LibDecimalFloatPowTest:testNegativePowError(bytes32,bytes32) (runs: 5098, μ: 1247476, ~: 1247586) -LibDecimalFloatPowTest:testPowAZero(int32,bytes32) (runs: 5096, μ: 1246471, ~: 1246471) -LibDecimalFloatPowTest:testPowAZeroNegative(bytes32) (runs: 5097, μ: 1246870, ~: 1246870) +LibDecimalFloatPow10Test:testPow10Packed(bytes32) (runs: 5098, μ: 1646864, ~: 1256455) +LibDecimalFloatPowTest:testNegativePowError(bytes32,bytes32) (runs: 5098, μ: 1247477, ~: 1247586) +LibDecimalFloatPowTest:testPowAZero(int32,bytes32) (runs: 5098, μ: 1246471, ~: 1246471) +LibDecimalFloatPowTest:testPowAZeroNegative(bytes32) (runs: 5096, μ: 1246870, ~: 1246870) LibDecimalFloatPowTest:testPowBZero(bytes32,int32) (runs: 5098, μ: 1246058, ~: 1246058) LibDecimalFloatPowTest:testPows() (gas: 1310306) -LibDecimalFloatPowTest:testRoundTripFuzzPow(bytes32,bytes32) (runs: 5098, μ: 1261918, ~: 1258607) +LibDecimalFloatPowTest:testRoundTripFuzzPow(bytes32,bytes32) (runs: 5098, μ: 1261922, ~: 1258608) LibDecimalFloatPowTest:testRoundTripSimple() (gas: 1597986) -LibDecimalFloatSqrtTest:testRoundTripFuzzSqrt(int224,int32) (runs: 5098, μ: 1295710, ~: 1299383) +LibDecimalFloatSqrtTest:testRoundTripFuzzSqrt(int224,int32) (runs: 5098, μ: 1295705, ~: 1299374) LibDecimalFloatSqrtTest:testSqrt() (gas: 1291880) LibDecimalFloatSqrtTest:testSqrtNegative(bytes32) (runs: 5098, μ: 1247095, ~: 1247204) LibDecimalFloatSqrtTest:testSqrtRoundTrip() (gas: 1415022) LibDecimalFloatSubTest:testSubPacked(bytes32,bytes32) (runs: 5098, μ: 9974, ~: 9995) -LibFormatDecimalFloatTest:testFormatDecimalExamples() (gas: 133439) -LibFormatDecimalFloatTest:testFormatDecimalRoundTrip(uint256) (runs: 5098, μ: 25221, ~: 20105) -LibFormatDecimalFloatTest:testFormatDecimalRoundTripNegative(int256) (runs: 5098, μ: 19678, ~: 21006) +LibFormatDecimalFloatCountSigFigs:testCountSigFigsExamples() (gas: 83596) +LibFormatDecimalFloatCountSigFigs:testCountSigFigsOne(int256) (runs: 5098, μ: 31883, ~: 31719) +LibFormatDecimalFloatCountSigFigs:testCountSigFigsZero(int256) (runs: 5098, μ: 3764, ~: 3764) +LibFormatDecimalFloatToDecimalStringTest:testFormatDecimalCustomSigFigs() (gas: 26601) +LibFormatDecimalFloatToDecimalStringTest:testFormatDecimalExamples() (gas: 895496) +LibFormatDecimalFloatToDecimalStringTest:testFormatDecimalRoundTripNegative(int256,uint256) (runs: 5098, μ: 71195, ~: 73798) +LibFormatDecimalFloatToDecimalStringTest:testFormatDecimalRoundTripNonNegative(uint256,uint256) (runs: 5098, μ: 54838, ~: 47581) LibLogTableBytesTest:testToBytesAntiLogTableDec() (gas: 159794) LibLogTableBytesTest:testToBytesAntiLogTableDecSmall() (gas: 162322) LibLogTableBytesTest:testToBytesLogTableDec() (gas: 143165) LibLogTableBytesTest:testToBytesLogTableDecSmall() (gas: 145706) LibLogTableBytesTest:testToBytesLogTableDecSmallAlt() (gas: 18375) -LibParseDecimalFloatTest:testParseDecimalFloatEmpty() (gas: 4130) -LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert() (gas: 4146) +LibParseDecimalFloatTest:testParseDecimalFloatEmpty() (gas: 4173) +LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert() (gas: 4169) LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert2() (gas: 5336) -LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert3() (gas: 5400) -LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert4() (gas: 4124) -LibParseDecimalFloatTest:testParseDecimalFloatNonDecimal() (gas: 4167) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDecimals() (gas: 383021) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotE() (gas: 4188) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotE0() (gas: 4144) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert() (gas: 4167) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert2() (gas: 4144) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert3() (gas: 5162) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatEDot() (gas: 4190) +LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert3() (gas: 5444) +LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert4() (gas: 4147) +LibParseDecimalFloatTest:testParseDecimalFloatNonDecimal() (gas: 4145) +LibParseDecimalFloatTest:testParseFormatterRoundTripBug0() (gas: 27142) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDecimals() (gas: 384619) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotE() (gas: 4166) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotE0() (gas: 4167) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert() (gas: 4145) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert2() (gas: 4167) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert3() (gas: 5140) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatEDot() (gas: 4168) LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponentRevert5() (gas: 4176) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponentRevert6() (gas: 4188) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponents() (gas: 402635) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatFuzz(uint256,uint8,bool) (runs: 5098, μ: 45903, ~: 37386) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatLeadingZeros() (gas: 59779) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeE() (gas: 6100) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeFrac() (gas: 5137) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert0() (gas: 27743) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert1() (gas: 24801) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponentRevert6() (gas: 4166) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponents() (gas: 402907) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatFuzz(uint256,uint8,bool) (runs: 5098, μ: 46196, ~: 37319) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatLeadingZeros() (gas: 59757) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeE() (gas: 6120) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeFrac() (gas: 5115) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert0() (gas: 27741) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert1() (gas: 24934) LibParseDecimalFloatTest:testParseLiteralDecimalFloatSpecific() (gas: 22959) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatUnrelated() (gas: 50856) -LibParseDecimalFloatTest:testParsePacked(string) (runs: 5098, μ: 9787, ~: 9668) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatUnrelated() (gas: 51254) +LibParseDecimalFloatTest:testParsePacked(string) (runs: 5098, μ: 9811, ~: 9692) TestDecimalFloatUnpackTest:testUnpackDeployed(bytes32) (runs: 5098, μ: 158422, ~: 158422) \ No newline at end of file diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index b88b69f6..e12702d1 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -357,7 +357,11 @@ impl Float { /// ``` pub fn format(self) -> Result { let Float(a) = self; - let calldata = DecimalFloat::formatCall { a }.abi_encode(); + let calldata = DecimalFloat::formatCall { + a, + sigFigsLimit: U256::from(9), + } + .abi_encode(); execute_call(Bytes::from(calldata), |output| { let decoded = DecimalFloat::formatCall::abi_decode_returns(output.as_ref())?; @@ -1016,17 +1020,6 @@ mod tests { } } - #[test] - fn test_parse_and_format() { - let float = Float::parse("1.1341234234625468391".to_string()).unwrap(); - let err = float.format().unwrap_err(); - - assert!(matches!( - err, - FloatError::DecimalFloat(DecimalFloatErrors::LossyConversionFromFloat(_)) - )); - } - #[test] fn test_parse_empty_string_error() { let err = Float::parse("".to_string()).unwrap_err(); @@ -1223,12 +1216,12 @@ mod tests { let float = Float::parse("-3613.1324123".to_string()).unwrap(); let abs = float.abs().unwrap(); let formatted = abs.format().unwrap(); - assert_eq!(formatted, "3613.1324123"); + assert_eq!(formatted, "3.6131324123e3"); let float = Float::parse("3613.1324123".to_string()).unwrap(); let abs = float.abs().unwrap(); let formatted = abs.format().unwrap(); - assert_eq!(formatted, "3613.1324123"); + assert_eq!(formatted, "3.6131324123e3"); let float = Float::parse("0".to_string()).unwrap(); let abs = float.abs().unwrap(); @@ -1248,12 +1241,12 @@ mod tests { let float = Float::parse("-123.1234234625468391".to_string()).unwrap(); let negated = float.neg().unwrap(); let formatted = negated.format().unwrap(); - assert_eq!(formatted, "123.1234234625468391"); + assert_eq!(formatted, "1.231234234625468391e2"); let float = Float::parse(formatted).unwrap(); let negated = float.neg().unwrap(); let formatted = negated.format().unwrap(); - assert_eq!(formatted, "-123.1234234625468391"); + assert_eq!(formatted, "-1.231234234625468391e2"); let float = Float::parse("0".to_string()).unwrap(); let negated = float.neg().unwrap(); diff --git a/src/concrete/DecimalFloat.sol b/src/concrete/DecimalFloat.sol index 0bb025ef..aed1d7ef 100644 --- a/src/concrete/DecimalFloat.sol +++ b/src/concrete/DecimalFloat.sol @@ -58,9 +58,10 @@ contract DecimalFloat { /// Exposes `LibFormatDecimalFloat.toDecimalString` for offchain use. /// @param a The float to format. + /// @param sigFigsLimit The significant figures limit. /// @return The string representation of the float. - function format(Float a) external pure returns (string memory) { - return LibFormatDecimalFloat.toDecimalString(a); + function format(Float a, uint256 sigFigsLimit) external pure returns (string memory) { + return LibFormatDecimalFloat.toDecimalString(a, sigFigsLimit); } /// Exposes `LibDecimalFloat.add` for offchain use. diff --git a/src/lib/format/LibFormatDecimalFloat.sol b/src/lib/format/LibFormatDecimalFloat.sol index b104d408..e817f6c7 100644 --- a/src/lib/format/LibFormatDecimalFloat.sol +++ b/src/lib/format/LibFormatDecimalFloat.sol @@ -5,23 +5,123 @@ pragma solidity ^0.8.25; import {LibDecimalFloat, Float} from "../LibDecimalFloat.sol"; import {LibFixedPointDecimalFormat} from "rain.math.fixedpoint/lib/format/LibFixedPointDecimalFormat.sol"; +import {LibDecimalFloatImplementation} from "../../lib/implementation/LibDecimalFloatImplementation.sol"; + +import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; library LibFormatDecimalFloat { + function countSigFigs(int256 signedCoefficient, int256 exponent) internal pure returns (uint256) { + if (signedCoefficient == 0) { + return 1; + } + + uint256 sigFigs = 0; + + if (exponent < 0) { + while (signedCoefficient % 10 == 0) { + signedCoefficient /= 10; + exponent++; + } + } + + while (signedCoefficient != 0) { + sigFigs++; + signedCoefficient /= 10; + } + + // Adjust for exponent + if (exponent < 0) { + exponent = -exponent; + sigFigs = sigFigs > uint256(exponent) ? sigFigs : uint256(exponent); + } else if (exponent > 0) { + sigFigs += uint256(exponent); + } + + return sigFigs; + } + /// Format a decimal float as a string. - /// Currently is a thin wrapper around converting to a fixed point decimal - /// and then formatting that as a string. - /// In the future this may be extended to support a wider range of possible - /// values. + /// Not particularly efficient as it is intended for offchain use that + /// doesn't cost gas. /// @param float The decimal float to format. /// @return The string representation of the decimal float. - function toDecimalString(Float float) internal pure returns (string memory) { + //slither-disable-next-line cyclomatic-complexity + function toDecimalString(Float float, uint256 sigFigsLimit) internal pure returns (string memory) { (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.unpack(float); - string memory prefix = ""; - if (signedCoefficient < 0) { - prefix = "-"; - signedCoefficient = -signedCoefficient; + if (signedCoefficient == 0) { + return "0"; + } + + uint256 sigFigs = countSigFigs(signedCoefficient, exponent); + bool scientific = sigFigs > sigFigsLimit; + uint256 scaleExponent; + uint256 scale = 0; + if (scientific) { + (signedCoefficient, exponent) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + + bool isAtLeastE76 = signedCoefficient / 1e76 != 0; + scaleExponent = isAtLeastE76 ? uint256(76) : uint256(75); + scale = uint256(10) ** scaleExponent; + } else { + if (exponent > 0) { + signedCoefficient *= int256(10) ** uint256(exponent); + exponent = 0; + } + if (exponent < 0) { + scale = uint256(10) ** uint256(-exponent); + scaleExponent = uint256(-exponent); + } else { + scaleExponent = uint256(exponent); + } } - uint256 decimal18Value = LibDecimalFloat.toFixedDecimalLossless(signedCoefficient, exponent, 18); - return string.concat(prefix, LibFixedPointDecimalFormat.fixedPointToDecimalString(decimal18Value)); + + int256 integral = scale != 0 ? signedCoefficient / int256(scale) : signedCoefficient; + int256 fractional = scale != 0 ? signedCoefficient % int256(scale) : int256(0); + bool isNeg = false; + if (integral < 0) { + isNeg = true; + integral = -integral; + } + if (fractional < 0) { + isNeg = true; + fractional = -fractional; + } + + string memory fractionalString = ""; + { + string memory fracLeadingZerosString = ""; + + if (fractional != 0) { + uint256 fracLeadingZeros = 0; + uint256 fracScale = scale / 10; + while (fractional / int256(fracScale) == 0) { + fracScale /= 10; + fracLeadingZeros++; + } + + for (uint256 i = 0; i < fracLeadingZeros; i++) { + fracLeadingZerosString = string.concat(fracLeadingZerosString, "0"); + } + + while (fractional % 10 == 0) { + fractional /= 10; + } + } + + fractionalString = + fractional == 0 ? "" : string.concat(".", fracLeadingZerosString, Strings.toString(fractional)); + } + + string memory integralString = Strings.toString(integral); + + int256 displayExponent = exponent + int256(scaleExponent); + string memory exponentString = + (displayExponent == 0 || !scientific) ? "" : string.concat("e", Strings.toString(displayExponent)); + + string memory prefix = isNeg ? "-" : ""; + + string memory fullString = string.concat(prefix, integralString, fractionalString, exponentString); + + return fullString; } } diff --git a/src/lib/parse/LibParseDecimalFloat.sol b/src/lib/parse/LibParseDecimalFloat.sol index 8d0e8212..350b0763 100644 --- a/src/lib/parse/LibParseDecimalFloat.sol +++ b/src/lib/parse/LibParseDecimalFloat.sol @@ -78,12 +78,15 @@ library LibParseDecimalFloat { // fractional part. exponent = int256(fracStart) - int256(nonZeroCursor); uint256 scale = uint256(-exponent); - if (scale >= 67 && signedCoefficient != 0) { + if (scale > 67 && signedCoefficient != 0) { return (ParseDecimalPrecisionLoss.selector, cursor, 0, 0); } scale = 10 ** scale; int256 rescaledIntValue = signedCoefficient * int256(scale); - if (rescaledIntValue / int256(scale) != signedCoefficient) { + if ( + rescaledIntValue / int256(scale) != signedCoefficient + || int224(rescaledIntValue) != rescaledIntValue + ) { return (ParseDecimalPrecisionLoss.selector, cursor, 0, 0); } signedCoefficient = rescaledIntValue + fracValue; diff --git a/test/src/concrete/DecimalFloat.format.t.sol b/test/src/concrete/DecimalFloat.format.t.sol index 90ef4248..5251837d 100644 --- a/test/src/concrete/DecimalFloat.format.t.sol +++ b/test/src/concrete/DecimalFloat.format.t.sol @@ -10,20 +10,20 @@ import {LibFormatDecimalFloat} from "src/lib/format/LibFormatDecimalFloat.sol"; contract DecimalFloatFormatTest is Test { using LibDecimalFloat for Float; - function formatExternal(Float a) external pure returns (string memory) { - return LibFormatDecimalFloat.toDecimalString(a); + function formatExternal(Float a, uint256 sigFigsLimit) external pure returns (string memory) { + return LibFormatDecimalFloat.toDecimalString(a, sigFigsLimit); } - function testFormatDeployed(Float a) external { + function testFormatDeployed(Float a, uint256 sigFigsLimit) external { DecimalFloat deployed = new DecimalFloat(); - try this.formatExternal(a) returns (string memory str) { - string memory deployedStr = deployed.format(a); + try this.formatExternal(a, sigFigsLimit) returns (string memory str) { + string memory deployedStr = deployed.format(a, sigFigsLimit); assertEq(str, deployedStr); } catch (bytes memory err) { vm.expectRevert(err); - deployed.format(a); + deployed.format(a, sigFigsLimit); } } } diff --git a/test/src/lib/format/LibFormatDecimalFloat.countSigFigs.t.sol b/test/src/lib/format/LibFormatDecimalFloat.countSigFigs.t.sol new file mode 100644 index 00000000..9fe785f8 --- /dev/null +++ b/test/src/lib/format/LibFormatDecimalFloat.countSigFigs.t.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 thedavidmeister +pragma solidity =0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {LibFormatDecimalFloat} from "src/lib/format/LibFormatDecimalFloat.sol"; + +/// @title LibFormatDecimalFloatCountSigFigs +contract LibFormatDecimalFloatCountSigFigs is Test { + function checkCountSigFigs(int256 signedCoefficient, int256 exponent, uint256 expected) internal pure { + uint256 actual = LibFormatDecimalFloat.countSigFigs(signedCoefficient, exponent); + assertEq(actual, expected, "Unexpected significant figures count"); + } + + function testCountSigFigsExamples() external pure { + checkCountSigFigs(0, 0, 1); + + // 1 = 1 + checkCountSigFigs(1, 0, 1); + checkCountSigFigs(10, -1, 1); + + // -1 = 1 + checkCountSigFigs(-1, 0, 1); + checkCountSigFigs(-10, -1, 1); + + // 10 = 2 + checkCountSigFigs(10, 0, 2); + checkCountSigFigs(100, -1, 2); + + // -10 = 2 + checkCountSigFigs(-10, 0, 2); + checkCountSigFigs(-100, -1, 2); + + // 0.1 = 1 + checkCountSigFigs(1, -1, 1); + checkCountSigFigs(10, -2, 1); + + // -0.1 = 1 + checkCountSigFigs(-1, -1, 1); + checkCountSigFigs(-10, -2, 1); + + // 0.01 = 2 + checkCountSigFigs(1, -2, 2); + checkCountSigFigs(10, -3, 2); + + // -0.01 = 2 + checkCountSigFigs(-1, -2, 2); + checkCountSigFigs(-10, -3, 2); + + // 0.001 = 3 + checkCountSigFigs(1, -3, 3); + checkCountSigFigs(10, -4, 3); + + // -0.001 = 3 + checkCountSigFigs(-1, -3, 3); + checkCountSigFigs(-10, -4, 3); + + // 1.1 = 2 + checkCountSigFigs(11, -1, 2); + checkCountSigFigs(110, -2, 2); + + // -1.1 = 2 + checkCountSigFigs(-11, -1, 2); + checkCountSigFigs(-110, -2, 2); + + // 1.01 = 3 + checkCountSigFigs(101, -2, 3); + checkCountSigFigs(1010, -3, 3); + + // -1.01 = 3 + checkCountSigFigs(-101, -2, 3); + checkCountSigFigs(-1010, -3, 3); + + // 10.1 = 3 + checkCountSigFigs(101, -1, 3); + checkCountSigFigs(1010, -2, 3); + + // -10.1 = 3 + checkCountSigFigs(-101, -1, 3); + checkCountSigFigs(-1010, -2, 3); + + // 10.01 = 4 + checkCountSigFigs(1001, -2, 4); + checkCountSigFigs(10010, -3, 4); + + // -10.01 = 4 + checkCountSigFigs(-1001, -2, 4); + checkCountSigFigs(-10010, -3, 4); + + // internal zeros are significant + checkCountSigFigs(100100, 0, 6); + checkCountSigFigs(-100100, 0, 6); + + // trailing zeros without decimal are significant + checkCountSigFigs(100, 0, 3); + checkCountSigFigs(1000, 0, 4); + + // trailing zeros after decimal are not significant + // 1.00 and 0.00100 + checkCountSigFigs(100, -2, 1); + checkCountSigFigs(100, -5, 3); + + // positive exponent growth + // 10 + checkCountSigFigs(1, 1, 2); + // 100 + checkCountSigFigs(1, 2, 3); + // -1000 + checkCountSigFigs(-1, 3, 4); + } + + function testCountSigFigsZero(int256 exponent) external pure { + checkCountSigFigs(0, exponent, 1); + } + + function testCountSigFigsOne(int256 exponent) external pure { + exponent = bound(exponent, -76, 0); + int256 one = int256(10 ** uint256(-exponent)); + checkCountSigFigs(one, exponent, 1); + checkCountSigFigs(-one, exponent, 1); + } +} diff --git a/test/src/lib/format/LibFormatDecimalFloat.t.sol b/test/src/lib/format/LibFormatDecimalFloat.t.sol deleted file mode 100644 index 4874591b..00000000 --- a/test/src/lib/format/LibFormatDecimalFloat.t.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-DCL-1.0 -// SPDX-FileCopyrightText: Copyright (c) 2020 thedavidmeister -pragma solidity =0.8.25; - -import {Test} from "forge-std/Test.sol"; -import {Float, LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; -import {LibFormatDecimalFloat} from "src/lib/format/LibFormatDecimalFloat.sol"; -import {LibParseDecimalFloat} from "src/lib/parse/LibParseDecimalFloat.sol"; - -/// @title LibFormatDecimalFloatTest -/// @notice Test contract for verifying the functionality of LibFormatDecimalFloat -/// @dev Tests both the stack and memory versions of formatting functions and round-trip conversions -contract LibFormatDecimalFloatTest is Test { - using LibDecimalFloat for Float; - using LibFormatDecimalFloat for Float; - - function checkFormat(int256 signedCoefficient, int256 exponent, string memory expected) internal pure { - string memory actual = - LibFormatDecimalFloat.toDecimalString(LibDecimalFloat.packLossless(signedCoefficient, exponent)); - assertEq(actual, expected, "Formatted value mismatch"); - } - - function toString(Float float) external pure returns (string memory) { - return LibFormatDecimalFloat.toDecimalString(float); - } - - /// Test round tripping a value through parse and format. - function testFormatDecimalRoundTrip(uint256 value) external pure { - value = bound(value, 0, uint256(int256(type(int224).max))); - Float float = LibDecimalFloat.fromFixedDecimalLosslessPacked(value, 18); - string memory formatted = LibFormatDecimalFloat.toDecimalString(float); - (bytes4 errorCode, Float parsed) = LibParseDecimalFloat.parseDecimalFloat(formatted); - assertEq(errorCode, 0, "Parse error"); - assertTrue(float.eq(parsed), "Round trip failed"); - } - - /// Negative matches positive. - function testFormatDecimalRoundTripNegative(int256 value) external pure { - value = bound(value, 1, int256(type(int128).max)); - Float float = LibDecimalFloat.packLossless(value, 18); - string memory formatted = float.toDecimalString(); - float = float.minus(); - string memory formattedNeg = float.toDecimalString(); - - assertEq(string.concat("-", formatted), formattedNeg, "Negative format mismatch"); - } - - /// Test some specific examples. - function testFormatDecimalExamples() external pure { - // pos decs - checkFormat(123456789012345678901234567890, 0, "123456789012345678901234567890"); - checkFormat(123456789012345678901234567890, -1, "12345678901234567890123456789"); - checkFormat(123456789012345678901234567890, -2, "1234567890123456789012345678.9"); - checkFormat(123456789012345678901234567890, -3, "123456789012345678901234567.89"); - checkFormat(123456789012345678901234567890, -4, "12345678901234567890123456.789"); - checkFormat(123456789012345678901234567890, -5, "1234567890123456789012345.6789"); - checkFormat(123456789012345678901234567890, -6, "123456789012345678901234.56789"); - - // // zeros - checkFormat(0, 0, "0"); - checkFormat(0, -1, "0"); - checkFormat(0, -2, "0"); - checkFormat(0, -3, "0"); - checkFormat(0, 1, "0"); - checkFormat(0, 2, "0"); - checkFormat(0, 3, "0"); - - // // neg decs - checkFormat(-123456789012345678901234567890, 0, "-123456789012345678901234567890"); - checkFormat(-123456789012345678901234567890, -1, "-12345678901234567890123456789"); - checkFormat(-123456789012345678901234567890, -2, "-1234567890123456789012345678.9"); - checkFormat(-123456789012345678901234567890, -3, "-123456789012345678901234567.89"); - checkFormat(-123456789012345678901234567890, -4, "-12345678901234567890123456.789"); - checkFormat(-123456789012345678901234567890, -5, "-1234567890123456789012345.6789"); - checkFormat(-123456789012345678901234567890, -6, "-123456789012345678901234.56789"); - } -} diff --git a/test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol b/test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol new file mode 100644 index 00000000..67e36783 --- /dev/null +++ b/test/src/lib/format/LibFormatDecimalFloat.toDecimalString.t.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 thedavidmeister +pragma solidity =0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {Float, LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; +import {LibFormatDecimalFloat} from "src/lib/format/LibFormatDecimalFloat.sol"; +import {LibParseDecimalFloat} from "src/lib/parse/LibParseDecimalFloat.sol"; + +/// @title LibFormatDecimalFloatToDecimalStringTest +/// @notice Test contract for verifying the functionality of LibFormatDecimalFloat +/// @dev Tests both the stack and memory versions of formatting functions and round-trip conversions +contract LibFormatDecimalFloatToDecimalStringTest is Test { + using LibDecimalFloat for Float; + using LibFormatDecimalFloat for Float; + + function checkFormat(int256 signedCoefficient, int256 exponent, uint256 sigFigsLimit, string memory expected) + internal + pure + { + string memory actual = LibFormatDecimalFloat.toDecimalString( + LibDecimalFloat.packLossless(signedCoefficient, exponent), sigFigsLimit + ); + assertEq(actual, expected, "Formatted value mismatch"); + } + + /// Test round tripping a value through parse and format. + function testFormatDecimalRoundTripNonNegative(uint256 value, uint256 sigFigsLimit) external pure { + value = bound(value, 0, uint256(int256(type(int224).max))); + Float float = LibDecimalFloat.fromFixedDecimalLosslessPacked(value, 18); + string memory formatted = LibFormatDecimalFloat.toDecimalString(float, sigFigsLimit); + (bytes4 errorCode, Float parsed) = LibParseDecimalFloat.parseDecimalFloat(formatted); + assertEq(errorCode, 0, "Parse error"); + assertTrue(float.eq(parsed), "Round trip failed"); + // Canonicalization: format(parse(format(x))) == format(x) + string memory reFormatted = LibFormatDecimalFloat.toDecimalString(parsed, sigFigsLimit); + assertEq(formatted, reFormatted, "Formatting not canonical"); + } + + /// Negative matches positive. + function testFormatDecimalRoundTripNegative(int256 value, uint256 sigFigsLimit) external pure { + value = bound(value, 1, int256(type(int128).max)); + Float float = LibDecimalFloat.fromFixedDecimalLosslessPacked(uint256(value), 18); + string memory formatted = float.toDecimalString(sigFigsLimit); + float = float.minus(); + string memory formattedNeg = float.toDecimalString(sigFigsLimit); + + assertEq(string.concat("-", formatted), formattedNeg, "Negative format mismatch"); + // Parse/eq for negative path as well + (bytes4 err, Float parsedNeg) = LibParseDecimalFloat.parseDecimalFloat(formattedNeg); + assertEq(err, 0, "Parse error (neg)"); + assertTrue(float.eq(parsedNeg), "Round trip failed (neg)"); + // Canonicalization for negative: format(parse(s)) == s + string memory reFormattedNeg = LibFormatDecimalFloat.toDecimalString(parsedNeg, sigFigsLimit); + assertEq(formattedNeg, reFormattedNeg, "Formatting not canonical (neg)"); + } + + /// Test some specific examples. + function testFormatDecimalExamples() external pure { + // pos decs + checkFormat(123456789012345678901234567890, 0, 9, "1.2345678901234567890123456789e29"); + checkFormat(123456789012345678901234567890, -1, 9, "1.2345678901234567890123456789e28"); + checkFormat(123456789012345678901234567890, -2, 9, "1.2345678901234567890123456789e27"); + checkFormat(123456789012345678901234567890, -3, 9, "1.2345678901234567890123456789e26"); + checkFormat(123456789012345678901234567890, -4, 9, "1.2345678901234567890123456789e25"); + checkFormat(123456789012345678901234567890, -5, 9, "1.2345678901234567890123456789e24"); + checkFormat(123456789012345678901234567890, -6, 9, "1.2345678901234567890123456789e23"); + + // zeros + checkFormat(0, 0, 9, "0"); + checkFormat(0, -1, 9, "0"); + checkFormat(0, -2, 9, "0"); + checkFormat(0, -3, 9, "0"); + checkFormat(0, 1, 9, "0"); + checkFormat(0, 2, 9, "0"); + checkFormat(0, 3, 9, "0"); + + // neg decs + checkFormat(-123456789012345678901234567890, 0, 9, "-1.2345678901234567890123456789e29"); + checkFormat(-123456789012345678901234567890, -1, 9, "-1.2345678901234567890123456789e28"); + checkFormat(-123456789012345678901234567890, -2, 9, "-1.2345678901234567890123456789e27"); + checkFormat(-123456789012345678901234567890, -3, 9, "-1.2345678901234567890123456789e26"); + checkFormat(-123456789012345678901234567890, -4, 9, "-1.2345678901234567890123456789e25"); + checkFormat(-123456789012345678901234567890, -5, 9, "-1.2345678901234567890123456789e24"); + checkFormat(-123456789012345678901234567890, -6, 9, "-1.2345678901234567890123456789e23"); + + // one + checkFormat(1, 0, 9, "1"); + + // 100 + checkFormat(100, 0, 9, "100"); + checkFormat(10, 1, 9, "100"); + checkFormat(1, 2, 9, "100"); + checkFormat(1000, -1, 9, "100"); + + // -100 + checkFormat(-100, 0, 9, "-100"); + checkFormat(-10, 1, 9, "-100"); + checkFormat(-1, 2, 9, "-100"); + checkFormat(-1000, -1, 9, "-100"); + + // 0.1 + checkFormat(1, -1, 9, "0.1"); + checkFormat(10, -2, 9, "0.1"); + checkFormat(100, -3, 9, "0.1"); + checkFormat(1000, -4, 9, "0.1"); + + // -0.1 + checkFormat(-1, -1, 9, "-0.1"); + checkFormat(-10, -2, 9, "-0.1"); + checkFormat(-100, -3, 9, "-0.1"); + checkFormat(-1000, -4, 9, "-0.1"); + + // 0.101 + checkFormat(101, -3, 9, "0.101"); + checkFormat(1010, -4, 9, "0.101"); + checkFormat(10100, -5, 9, "0.101"); + checkFormat(101000, -6, 9, "0.101"); + + // -0.101 + checkFormat(-101, -3, 9, "-0.101"); + checkFormat(-1010, -4, 9, "-0.101"); + checkFormat(-10100, -5, 9, "-0.101"); + checkFormat(-101000, -6, 9, "-0.101"); + + // 1.1 + checkFormat(11, -1, 9, "1.1"); + checkFormat(110, -2, 9, "1.1"); + checkFormat(1100, -3, 9, "1.1"); + checkFormat(11000, -4, 9, "1.1"); + + // -1.1 + checkFormat(-11, -1, 9, "-1.1"); + checkFormat(-110, -2, 9, "-1.1"); + checkFormat(-1100, -3, 9, "-1.1"); + checkFormat(-11000, -4, 9, "-1.1"); + + // 9 sig figs + checkFormat(123456789, 0, 9, "123456789"); + checkFormat(-123456789, 0, 9, "-123456789"); + checkFormat(123456789, -1, 9, "12345678.9"); + checkFormat(-123456789, -1, 9, "-12345678.9"); + checkFormat(12345678, 1, 9, "123456780"); + checkFormat(-12345678, 1, 9, "-123456780"); + + // 10 sig figs + checkFormat(1234567890, 0, 9, "1.23456789e9"); + checkFormat(-1234567890, 0, 9, "-1.23456789e9"); + checkFormat(123456789, 1, 9, "1.23456789e9"); + checkFormat(-123456789, 1, 9, "-1.23456789e9"); + checkFormat(1, -10, 9, "1e-10"); + + // examples from fuzz + checkFormat(1019001501928, -18, 9, "1.019001501928e-6"); + checkFormat(-1019001501928, -18, 9, "-1.019001501928e-6"); + + // pure powers of 10 at the cutoff + checkFormat(1000000000, 0, 9, "1e9"); + checkFormat(-1000000000, 0, 9, "-1e9"); + // extreme small/large magnitudes still choose scientific + checkFormat(1, -76, 9, "1e-76"); + checkFormat(-1, -76, 9, "-1e-76"); + checkFormat(1, 76, 9, "1e76"); + checkFormat(-1, 76, 9, "-1e76"); + + // impossible sig figs. + checkFormat(1, 200, 1, "1e200"); + // we can't actually fit 200 zeros into the binary representation so + // even though the threshold is 200 we still use scientific notation. + checkFormat(1, 200, 200, "1e200"); + } + + function testFormatDecimalCustomSigFigs() external pure { + // Force rounding under a tighter sig-figs limit. + Float f = LibDecimalFloat.packLossless(12345678, 0); + string memory s = LibFormatDecimalFloat.toDecimalString(f, 5); + // Verify the explicit limit path (adjust expected if rounding policy differs). + assertEq(s, "1.2345678e7", "Custom sig-figs not applied"); + } +} diff --git a/test/src/lib/parse/LibParseDecimalFloat.t.sol b/test/src/lib/parse/LibParseDecimalFloat.t.sol index 4734f4a3..612d3ff1 100644 --- a/test/src/lib/parse/LibParseDecimalFloat.t.sol +++ b/test/src/lib/parse/LibParseDecimalFloat.t.sol @@ -297,6 +297,16 @@ contract LibParseDecimalFloatTest is Test { checkParseDecimalFloat("1.2e3.4", 12, 2, 5); } + /// issue found in fuzzing round trip with formatter 1. + function testParseFormatterRoundTripBug0() external pure { + checkParseDecimalFloat( + "1.3479973333575319897333507543509815336818572211270286240551805124605e49", + 13479973333575319897333507543509815336818572211270286240551805124605, + -18, + 72 + ); + } + /// An empty string should fail. function testParseDecimalFloatEmpty() external pure { checkParseDecimalFloatFail("", ParseEmptyDecimalString.selector, 0); @@ -392,7 +402,7 @@ contract LibParseDecimalFloatTest is Test { /// impossible to fit the max decimals. function testParseLiteralDecimalFloatPrecisionRevert1() external pure { checkParseDecimalFloatFail( - "1.5789604461865809771178549250434395392663499233282028201972879200395", + "2.5789604461865809771178549250434395392663499233282028201972879200395", ParseDecimalPrecisionLoss.selector, 69 );