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
231 changes: 116 additions & 115 deletions .gas-snapshot

Large diffs are not rendered by default.

48 changes: 34 additions & 14 deletions src/lib/LibDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import {
LibDecimalFloatImplementation,
NORMALIZED_ZERO_SIGNED_COEFFICIENT,
NORMALIZED_ZERO_EXPONENT,
EXPONENT_MIN,
EXPONENT_MAX,
NORMALIZED_MIN,
NORMALIZED_MAX,
EXPONENT_STEP_SIZE,
SIGNED_NORMALIZED_MAX
SIGNED_NORMALIZED_MAX,
EXPONENT_MAX,
EXPONENT_MIN
} from "./implementation/LibDecimalFloatImplementation.sol";

type PackedFloat is bytes32;
Expand All @@ -51,8 +51,8 @@ struct Float {

/// @title LibDecimalFloat
/// Floating point math library for Rainlang.
/// Broadly implements decimal floating point math with 128 signed bits for the
/// coefficient and 128 signed bits for the exponent. Notably the implementation
/// Broadly implements decimal floating point math with 224 signed bits for the
/// coefficient and 32 signed bits for the exponent. Notably the implementation
/// differs from standard specifications in a few key areas:
///
/// - There is no concept of NaN or Infinity.
Expand Down Expand Up @@ -283,8 +283,9 @@ library LibDecimalFloat {
return toFixedDecimalLossless(float.signedCoefficient, float.exponent, decimals);
}

/// Pack a signed coefficient and exponent into a single uint256. Clearly
/// this involves fitting 64 bytes into 32 bytes, so there will be data loss.
/// Pack a signed coefficient and exponent into a single `PackedFloat`.
/// Clearly this involves fitting 64 bytes into 32 bytes, so there will be
/// data loss.
/// Normalized numbers are guaranteed to round trip through pack/unpack in
/// a lossless manner. The normalization process will _truncate_ on precision
/// loss if required, which is significantly better than potentially
Expand All @@ -298,12 +299,31 @@ library LibDecimalFloat {
/// @return packed The packed representation of the signed coefficient and
/// exponent.
function pack(int256 signedCoefficient, int256 exponent) internal pure returns (PackedFloat packed) {
if (int128(signedCoefficient) != signedCoefficient || int128(exponent) != exponent) {
(signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent);
if (signedCoefficient == 0) {
return PackedFloat.wrap(0);
}

if (int224(signedCoefficient) != signedCoefficient) {
if (signedCoefficient / 1e72 != 0) {
signedCoefficient /= 1e5;
exponent += 5;
}

while (int224(signedCoefficient) != signedCoefficient) {
signedCoefficient /= 10;
++exponent;
}
Comment thread
thedavidmeister marked this conversation as resolved.
}
uint256 mask = type(uint128).max;

if (int32(exponent) != exponent) {
revert ExponentOverflow(signedCoefficient, exponent);
}

// Need a mask to zero out the bits that could be set to 1 if the
// coefficient is negative.
uint256 mask = type(uint224).max;
assembly ("memory-safe") {
packed := or(and(signedCoefficient, mask), shl(0x80, exponent))
packed := or(and(signedCoefficient, mask), shl(0xe0, exponent))
}
}

Expand All @@ -320,10 +340,10 @@ library LibDecimalFloat {
/// representation.
/// @return exponent The exponent of the floating point representation.
function unpack(PackedFloat packed) internal pure returns (int256 signedCoefficient, int256 exponent) {
uint256 mask = type(uint128).max;
uint256 mask = type(uint224).max;
assembly ("memory-safe") {
signedCoefficient := signextend(0x0F, and(packed, mask))
exponent := sar(0x80, packed)
signedCoefficient := signextend(27, and(packed, mask))
exponent := sar(0xe0, packed)
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/lib/implementation/LibDecimalFloatImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import {LibDecimalFloat} from "../LibDecimalFloat.sol";

error WithTargetExponentOverflow(int256 signedCoefficient, int256 exponent, int256 targetExponent);

/// @dev The minimum exponent that can be normalized.
/// This is crazy small, so should never be a problem for any real use case.
/// We need it to guard against overflow when normalizing.
int256 constant EXPONENT_MIN = type(int128).min + 79;

/// @dev The maximum exponent that can be normalized.
/// This is crazy large, so should never be a problem for any real use case.
/// We need it to guard against overflow when normalizing.
int256 constant EXPONENT_MAX = type(int128).max - 78;
int256 constant EXPONENT_MAX = type(int256).max / 2;
int256 constant EXPONENT_MAX_PLUS_ONE = EXPONENT_MAX + 1;

/// @dev The minimum exponent that can be normalized.
/// This is crazy small, so should never be a problem for any real use case.
/// We need it to guard against overflow when normalizing.
int256 constant EXPONENT_MIN = -EXPONENT_MAX;

/// @dev When normalizing a number, how far we "step" when close to normalized.
int256 constant EXPONENT_STEP_SIZE = 1;
/// @dev The multiplier for the step size, calculated at compile time.
Expand Down
6 changes: 3 additions & 3 deletions test/src/lib/LibDecimalFloat.divide.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity =0.8.25;

import {THREES, ONES} from "../../lib/LibCommonResults.sol";
import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, Float} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloat, Float, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol";

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

Expand Down Expand Up @@ -120,8 +120,8 @@ contract LibDecimalFloatDivideTest is Test {

/// forge-config: default.fuzz.runs = 100
function testUnnormalizedThreesDivision0(int256 exponentA, int256 exponentB) external pure {
exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX);
exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX);
exponentA = bound(exponentA, EXPONENT_MIN / 2, EXPONENT_MAX / 2);
exponentB = bound(exponentB, EXPONENT_MIN / 2, EXPONENT_MAX / 2);

int256 d = 3;
int256 di = 0;
Expand Down
2 changes: 1 addition & 1 deletion test/src/lib/LibDecimalFloat.inv.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity =0.8.25;

import {Test} from "forge-std/Test.sol";
import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, Float} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloat, Float, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol";

contract LibDecimalFloatInvTest is Test {
Expand Down
2 changes: 1 addition & 1 deletion test/src/lib/LibDecimalFloat.minus.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

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

import {Test} from "forge-std/Test.sol";
Expand Down
10 changes: 4 additions & 6 deletions test/src/lib/LibDecimalFloat.multiply.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import {
LibDecimalFloat,
NORMALIZED_ZERO_SIGNED_COEFFICIENT,
NORMALIZED_ZERO_EXPONENT,
Float
} from "src/lib/LibDecimalFloat.sol";
import {
Float,
EXPONENT_MIN,
EXPONENT_MAX,
LibDecimalFloatImplementation
} from "src/lib/implementation/LibDecimalFloatImplementation.sol";
EXPONENT_MAX
} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol";
import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol";

import {Test} from "forge-std/Test.sol";
Expand Down
39 changes: 21 additions & 18 deletions test/src/lib/LibDecimalFloat.pack.t.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

import {
LibDecimalFloat,
ExponentOverflow,
EXPONENT_MIN,
EXPONENT_MAX,
PackedFloat,
Float
} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloat, ExponentOverflow, PackedFloat, Float, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol";
import {Test} from "forge-std/Test.sol";

Expand All @@ -30,24 +23,34 @@ contract LibDecimalFloatPackTest is Test {
}

/// Round trip from/to parts.
function testPartsRoundTrip(int128 signedCoefficient, int128 exponent) external pure {
function testPartsRoundTrip(int224 signedCoefficient, int32 exponent) external pure {
PackedFloat packed = LibDecimalFloat.pack(signedCoefficient, exponent);
(int256 signedCoefficientOut, int256 exponentOut) = LibDecimalFloat.unpack(packed);

assertEq(signedCoefficient, signedCoefficientOut, "coefficient");
assertEq(exponent, exponentOut, "exponent");
if (signedCoefficient != 0) {
assertEq(exponent, exponentOut, "exponent");
} else {
// 0 exponent is always 0.
assertEq(exponentOut, 0, "exponent");
}
}

/// Can round trip any normalized number.
function testNormalizedRoundTrip(int256 signedCoefficient, int256 exponent) external pure {
exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX);
(signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent);
/// Can round trip any normalized number provided the exponent is in range.
function testNormalizedRoundTrip(int256 signedCoefficient, int32 exponent) external pure {
(int256 signedCoefficientNormalized, int256 exponentNormalized) =
LibDecimalFloatImplementation.normalize(signedCoefficient, exponent);
vm.assume(int32(exponentNormalized) == exponentNormalized);

PackedFloat packed = LibDecimalFloat.pack(signedCoefficient, exponent);
(int256 signedCoefficientOut, int256 exponentOut) = LibDecimalFloat.unpack(packed);
PackedFloat packed = LibDecimalFloat.pack(signedCoefficientNormalized, exponentNormalized);
(int256 signedCoefficientUnpacked, int256 exponentUnpacked) = LibDecimalFloat.unpack(packed);

assertEq(signedCoefficient, signedCoefficientOut, "coefficient");
assertEq(exponent, exponentOut, "exponent");
assertTrue(
LibDecimalFloat.eq(
signedCoefficientNormalized, exponentNormalized, signedCoefficientUnpacked, exponentUnpacked
),
"eq"
);
}

/// Mem and stack versions behave the same for pack.
Expand Down
7 changes: 1 addition & 6 deletions test/src/lib/LibDecimalFloat.power10.t.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

import {LibDecimalFloat, EXPONENT_MIN, Float} from "src/lib/LibDecimalFloat.sol";
import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol";
import {LogTest} from "../../abstract/LogTest.sol";

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

contract LibDecimalFloatPower10Test is LogTest {
using LibDecimalFloat for Float;

Expand Down Expand Up @@ -40,11 +38,8 @@ contract LibDecimalFloatPower10Test is LogTest {
int256 expectedExponent
) internal {
address tables = logTables();
uint256 a = gasleft();
(int256 actualSignedCoefficient, int256 actualExponent) =
LibDecimalFloat.power10(tables, signedCoefficient, exponent);
uint256 b = gasleft();
console2.log("%d %d Gas used: %d", uint256(signedCoefficient), uint256(exponent), a - b);
assertEq(actualSignedCoefficient, expectedSignedCoefficient, "signedCoefficient");
assertEq(actualExponent, expectedExponent, "exponent");
}
Expand Down
2 changes: 1 addition & 1 deletion test/src/lib/LibDecimalFloat.sub.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

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

import {Test} from "forge-std/Test.sol";
Expand Down
1 change: 1 addition & 0 deletions test/src/lib/parse/LibParseDecimalFloat.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ contract LibParseDecimalFloatTest is Test {
try this.parseDecimalFloatExternal(data) returns (
bytes4 errorSelector, uint256 cursorAfter, int256 signedCoefficient, int256 exponent
) {
(cursorAfter);
Comment thread
thedavidmeister marked this conversation as resolved.
(bytes4 errorSelectorMem, Float memory float) = this.parseDecimalFloatExternalMem(data);
assertEq(errorSelector, errorSelectorMem, "Error selector mismatch");
assertEq(signedCoefficient, float.signedCoefficient, "Signed coefficient mismatch");
Expand Down
Loading