Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,37 @@ LibDecimalFloatCompareTest:testCompareOnePointSix() (gas: 5040)
LibDecimalFloatCompareTest:testCompareTen() (gas: 5333)
LibDecimalFloatDecimalAddTest:testAdd123456789987654321() (gas: 6549)
LibDecimalFloatDecimalAddTest:testAdd123456789e9987654321() (gas: 6830)
LibDecimalFloatDecimalAddTest:testAddNeverRevertIsNormalized(int256,int256,int256,int256) (runs: 2063, μ: 14270, ~: 14152)
LibDecimalFloatDecimalAddTest:testAddNeverRevertIsNormalized(int256,int256,int256,int256) (runs: 2050, μ: 14296, ~: 14175)
LibDecimalFloatDecimalAddTest:testAddOneOneNotNormalized() (gas: 5958)
LibDecimalFloatDecimalAddTest:testAddOneOnePreNormalized() (gas: 4390)
LibDecimalFloatDecimalAddTest:testAddOneZero() (gas: 5275)
LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2063, μ: 6848, ~: 6862)
LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 2050, μ: 6849, ~: 6874)
LibDecimalFloatDecimalAddTest:testAddZero() (gas: 4388)
LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2063, μ: 10055, ~: 9992)
LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 2050, μ: 10061, ~: 9992)
LibDecimalFloatDecimalAddTest:testAddZeroOne() (gas: 5256)
LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 22033)
LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 2054, μ: 18704, ~: 18883)
LibDecimalFloatDecimalAddTest:testAddZeroToAny(int256,int256,int256) (runs: 2050, μ: 17340, ~: 17323)
LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 22282)
LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 2049, μ: 18698, ~: 18818)
LibDecimalFloatDecimalAddTest:testGasAddOne() (gas: 1084)
LibDecimalFloatDecimalAddTest:testGasAddZero() (gas: 1106)
LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 2063, μ: 10144, ~: 10196)
LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 2050, μ: 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: 2063, μ: 9574, ~: 7195)
LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 2063, μ: 8556, ~: 7207)
LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 2056, μ: 16000, ~: 16228)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 2063, μ: 14938, ~: 14718)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 2063, μ: 10110, ~: 9778)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 2063, μ: 9916, ~: 10161)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 2055, μ: 13915, ~: 14172)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 2063, μ: 14457, ~: 14188)
LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 2050, μ: 9549, ~: 7195)
LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 2050, μ: 8541, ~: 7207)
LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 2049, μ: 16001, ~: 16228)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 2050, μ: 14938, ~: 14718)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 2050, μ: 10110, ~: 9778)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 2050, μ: 9916, ~: 10161)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 2049, μ: 13915, ~: 14172)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 2050, μ: 14458, ~: 14188)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 14500)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2063, μ: 13700, ~: 13580)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2063, μ: 4575, ~: 4575)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 2050, μ: 13699, ~: 13580)
LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 2050, μ: 4575, ~: 4575)
LibDecimalFloatDivideTest:testDivide1Over3() (gas: 6388)
LibDecimalFloatDivideTest:testDivide1Over3Gas0() (gas: 827)
LibDecimalFloatDivideTest:testDivide1Over3Gas10() (gas: 7934)
Expand All @@ -46,18 +47,21 @@ LibDecimalFloatLog10Test:testExactLogs() (gas: 19667)
LibDecimalFloatLog10Test:testInterpolatedLookups() (gas: 18818)
LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7926)
LibDecimalFloatMultiplyTest:testMultiply123456789987654321() (gas: 4551)
LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2063, μ: 14168, ~: 14254)
LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 2050, μ: 14170, ~: 14254)
LibDecimalFloatMultiplyTest:testMultiply1e181e19() (gas: 4732)
LibDecimalFloatMultiplyTest:testMultiplyGasOne() (gas: 1284)
LibDecimalFloatMultiplyTest:testMultiplyGasZero() (gas: 326)
LibDecimalFloatMultiplyTest:testMultiplyOneOne() (gas: 4753)
LibDecimalFloatMultiplyTest:testMultiplyOneZero() (gas: 3628)
LibDecimalFloatMultiplyTest:testMultiplyZero0Exponent() (gas: 3642)
LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2063, μ: 3930, ~: 3930)
LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 2050, μ: 3930, ~: 3930)
LibDecimalFloatMultiplyTest:testMultiplyZeroOne() (gas: 3621)
LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 2063, μ: 11025, ~: 10932)
LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 2063, μ: 4588, ~: 4588)
LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int256) (runs: 2050, μ: 11025, ~: 10932)
LibDecimalFloatPackTest:testPartsRoundTrip(int128,int128) (runs: 2050, μ: 4588, ~: 4588)
LibDecimalFloatPower10Test:testInterpolatedLookupsPower() (gas: 43971)
LibDecimalFloatSubTest:testMinusIsSubZero(int256,int256,int256) (runs: 2050, μ: 15675, ~: 15579)
LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 2050, μ: 17940, ~: 18083)
LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 2050, μ: 17687, ~: 17559)
LibLogTableBytesTest:testToBytesAntiLogTableDec() (gas: 158027)
LibLogTableBytesTest:testToBytesAntiLogTableDecSmall() (gas: 160620)
LibLogTableBytesTest:testToBytesLogTableDec() (gas: 141631)
Expand Down
27 changes: 26 additions & 1 deletion src/lib/LibDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,11 @@ library LibDecimalFloat {
// The early return here allows us to do unchecked pow on the
// multiplier and means we never revert due to overflow here.
if (alignmentExponentDiff > ADD_MAX_EXPONENT_DIFF) {
return (signedCoefficientA, exponentA);
if (signedCoefficientA == 0) {
return (signedCoefficientB, exponentB);
} else {
return (signedCoefficientA, exponentA);
}
}
multiplier = 10 ** alignmentExponentDiff;
}
Expand All @@ -359,6 +363,18 @@ library LibDecimalFloat {
return LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentB);
}

/// Subtract two floats together as a normalized result.
///
/// This is effectively shorthand for adding the two floats with the second
/// float negated. Therefore, the same caveats apply as for `add`.
/// @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 signedCoefficient The signed coefficient of the result.
/// @return exponent The exponent of the result.
function sub(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB)
internal
pure
Expand All @@ -368,6 +384,9 @@ library LibDecimalFloat {
return add(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
}

/// Negates and normalizes a float.
/// Equivalent to `0 - x`.
///
/// https://speleotrove.com/decimal/daops.html#refplusmin
/// > minus and plus both take one operand, and correspond to the prefix
/// > minus and plus operators in programming languages.
Expand All @@ -377,6 +396,12 @@ library LibDecimalFloat {
/// > (where a and b refer to any numbers) are calculated as the operations
/// > add(’0’, a) and subtract(’0’, b) respectively, where the ’0’ has the
/// > same exponent as the operand.
///
/// @param signedCoefficient The signed coefficient of the floating point
/// number.
/// @param exponent The exponent of the floating point number.
/// @return signedCoefficient The signed coefficient of the result.
/// @return exponent The exponent of the result.
function minus(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) {
(signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent);
return (-signedCoefficient, exponent);
Expand Down
18 changes: 18 additions & 0 deletions test/src/LibDecimalFloat.add.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,22 @@ contract LibDecimalFloatDecimalAddTest is Test {
assertEq(signedCoefficient, expectedSignedCoefficient);
assertEq(exponent, expectedExponent);
}

/// Adding any zero to any value returns the non-zero value.
function testAddZeroToAny(int256 exponentZero, int256 signedCoefficient, int256 exponent) external pure {
exponentZero = bound(exponentZero, EXPONENT_MIN / 10, EXPONENT_MAX / 10);
exponent = bound(exponent, EXPONENT_MIN / 10, EXPONENT_MAX / 10);

(int256 expectedSignedCoefficient, int256 expectedExponent) =
LibDecimalFloatImplementation.normalize(signedCoefficient, exponent);
(int256 signedCoefficientAddZero, int256 exponentAddZero) =
LibDecimalFloat.add(0, exponentZero, signedCoefficient, exponent);
assertEq(signedCoefficientAddZero, expectedSignedCoefficient);
assertEq(exponentAddZero, expectedExponent);

// Reverse order.
(signedCoefficientAddZero, exponentAddZero) = LibDecimalFloat.add(signedCoefficient, exponent, 0, exponentZero);
assertEq(signedCoefficientAddZero, expectedSignedCoefficient);
assertEq(exponentAddZero, expectedExponent);
}
}
22 changes: 22 additions & 0 deletions test/src/LibDecimalFloat.minus.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol";

import {Test} from "forge-std/Test.sol";

contract LibDecimalFloatSubTest is Test {
/// Minus is the same as `0 - x`.
function testMinusIsSubZero(int256 exponentZero, int256 signedCoefficient, int256 exponent) external pure {
exponentZero = bound(exponentZero, EXPONENT_MIN / 10, EXPONENT_MAX / 10);
exponent = bound(exponent, EXPONENT_MIN / 10, EXPONENT_MAX / 10);

(int256 signedCoefficientMinus, int256 exponentMinus) = LibDecimalFloat.minus(signedCoefficient, exponent);
(int256 expectedSignedCoefficient, int256 expectedExponent) =
LibDecimalFloat.sub(0, exponentZero, signedCoefficient, exponent);

assertEq(signedCoefficientMinus, expectedSignedCoefficient);
assertEq(exponentMinus, expectedExponent);
}
}
48 changes: 48 additions & 0 deletions test/src/LibDecimalFloat.sub.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol";

import {Test} from "forge-std/Test.sol";

contract LibDecimalFloatSubTest is Test {
/// Sub is the same as add, but with the second coefficient negated.
function testSubIsAdd(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB)
external
pure
{
exponentA = bound(exponentA, EXPONENT_MIN / 10, EXPONENT_MAX / 10);
exponentB = bound(exponentB, EXPONENT_MIN / 10, EXPONENT_MAX / 10);

// The min signed value cannot be negated directly so we can't test it
// in this function.
vm.assume(signedCoefficientB != type(int256).min);

(int256 signedCoefficient, int256 exponent) =
LibDecimalFloat.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
(int256 expectedSignedCoefficient, int256 expectedExponent) =
LibDecimalFloat.add(signedCoefficientA, exponentA, -signedCoefficientB, exponentB);
assertEq(signedCoefficient, expectedSignedCoefficient);
assertEq(exponent, expectedExponent);
}

/// We can sub the min signed value as it will be normalized.
function testSubMinSignedValue(int256 signedCoefficientA, int256 exponentA, int256 exponentB) external pure {
exponentA = bound(exponentA, EXPONENT_MIN / 10, EXPONENT_MAX / 10);
exponentB = bound(exponentB, EXPONENT_MIN / 10, EXPONENT_MAX / 10);

// Able to sub the non-normalized min signed value.
int256 signedCoefficientB = type(int256).min;
(int256 signedCoefficient, int256 exponent) =
LibDecimalFloat.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB);

// Which behaves the same as normalizing then adding its negation.
(signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB);
(int256 expectedSignedCoefficient, int256 expectedExponent) =
LibDecimalFloat.add(signedCoefficientA, exponentA, -signedCoefficientB, exponentB);

assertEq(signedCoefficient, expectedSignedCoefficient);
assertEq(exponent, expectedExponent);
}
}