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
393 changes: 191 additions & 202 deletions .gas-snapshot

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/error/ErrDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2020 thedavidmeister
pragma solidity ^0.8.25;

/// @dev Thrown when a coefficient overflows.
error CoefficientOverflow(int256 signedCoefficient, int256 exponent);

/// @dev Thrown when an exponent overflows.
error ExponentOverflow(int256 signedCoefficient, int256 exponent);

Expand Down
8 changes: 4 additions & 4 deletions src/generated/LogTables.pointers.sol

Large diffs are not rendered by default.

986 changes: 204 additions & 782 deletions src/lib/LibDecimalFloat.sol

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions src/lib/format/LibFormatDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ library LibFormatDecimalFloat {
return LibFixedPointDecimalFormat.fixedPointToDecimalString(decimal18Value);
}

function toDecimalString(Float memory float) internal pure returns (string memory) {
return toDecimalString(float.signedCoefficient, float.exponent);
function toDecimalString(Float float) internal pure returns (string memory) {
(int256 signedCoefficient, int256 exponent) = LibDecimalFloat.unpack(float);
return toDecimalString(signedCoefficient, exponent);
}
}
528 changes: 506 additions & 22 deletions src/lib/implementation/LibDecimalFloatImplementation.sol

Large diffs are not rendered by default.

34 changes: 10 additions & 24 deletions src/lib/parse/LibParseDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,19 @@ import {
import {LibParseDecimal} from "rain.string/lib/parse/LibParseDecimal.sol";
import {MalformedExponentDigits, ParseDecimalPrecisionLoss, MalformedDecimalPoint} from "../../error/ErrParse.sol";
import {ParseDecimalOverflow, ParseEmptyDecimalString} from "rain.string/error/ErrParse.sol";
import {LibDecimalFloat, PackedFloat, Float} from "../LibDecimalFloat.sol";
import {LibDecimalFloat, Float} from "../LibDecimalFloat.sol";
import {LibDecimalFloatImplementation} from "../implementation/LibDecimalFloatImplementation.sol";
import {console2} from "forge-std/Test.sol";

library LibParseDecimalFloat {
function parseDecimalFloatPacked(uint256 start, uint256 end) internal pure returns (bytes4, uint256, PackedFloat) {
function parseDecimalFloatPacked(uint256 start, uint256 end) internal pure returns (bytes4, uint256, Float) {
(bytes4 errorSelector, uint256 cursor, int256 signedCoefficient, int256 exponent) =
parseDecimalFloat(start, end);
if (errorSelector != 0) {
return (errorSelector, cursor, PackedFloat.wrap(0));
return (errorSelector, cursor, Float.wrap(0));
}

// Prenormalize signed coefficients that are smaller than their
// normalized form at parse time, as this can save runtime gas that would
// be needed to normalize the value at runtime.
// We only do normalization that will scale up, to avoid causing
// unneccessary precision loss.
if (-1e37 < signedCoefficient && signedCoefficient < 1e37) {
(signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent);
}

PackedFloat packedFloat = LibDecimalFloat.pack(signedCoefficient, exponent);

(int256 unpackedSignedCoefficient, int256 unpackedExponent) = LibDecimalFloat.unpack(packedFloat);
if (unpackedSignedCoefficient != signedCoefficient || unpackedExponent != exponent) {
return (ParseDecimalPrecisionLoss.selector, cursor, PackedFloat.wrap(0));
}

return (0, cursor, packedFloat);
return (0, cursor, LibDecimalFloat.packLossless(signedCoefficient, exponent));
}

function parseDecimalFloat(uint256 start, uint256 end)
Expand Down Expand Up @@ -103,7 +88,7 @@ library LibParseDecimalFloat {
// fractional part.
exponent = int256(fracStart) - int256(nonZeroCursor);
uint256 scale = uint256(-exponent);
if (scale >= 77 && signedCoefficient != 0) {
if (scale >= 67 && signedCoefficient != 0) {
Comment thread
thedavidmeister marked this conversation as resolved.
return (ParseDecimalPrecisionLoss.selector, cursor, 0, 0);
}
scale = 10 ** scale;
Expand Down Expand Up @@ -141,15 +126,16 @@ library LibParseDecimalFloat {
}
}

function parseDecimalFloat(string memory str) internal pure returns (bytes4 errorSelector, Float memory float) {
function parseDecimalFloat(string memory str) internal pure returns (bytes4, Float) {
uint256 start;
uint256 end;
assembly {
start := add(str, 0x20)
end := add(start, mload(str))
}
uint256 cursor;
(errorSelector, cursor, float.signedCoefficient, float.exponent) = parseDecimalFloat(start, end);
(bytes4 errorSelector, uint256 cursor, int256 signedCoefficient, int256 exponent) =
parseDecimalFloat(start, end);
(cursor);
return (errorSelector, LibDecimalFloat.packLossless(signedCoefficient, exponent));
}
}
62 changes: 36 additions & 26 deletions src/lib/table/LibLogTable.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
// SPDX-License-Identifier: CAL
pragma solidity ^0.8.25;

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

uint16 constant ALT_TABLE_FLAG = 0x8000;

/// @dev https://icap.org.pk/files/per/students/exam/notices/log-table.pdf
library LibLogTable {
function toBytes(uint16[10][90] memory table) internal pure returns (bytes memory) {
function toBytes(uint16[10][91] memory table) internal pure returns (bytes memory) {
bytes memory encoded;
assembly ("memory-safe") {
encoded := mload(0x40)
mstore(0x40, add(encoded, add(1800, 0x20)))
mstore(0x40, add(encoded, add(1820, 0x20)))

let cursor := sub(mload(0x40), 0x20)

for {
let i := add(table, mul(0x20, 89))
let i := add(table, mul(0x20, 90))
let j := mul(0x20, 9)
} gt(cursor, encoded) {
cursor := sub(cursor, 2)
Expand All @@ -30,21 +28,21 @@ library LibLogTable {
}
}

mstore(cursor, 1800)
mstore(cursor, 1820)
}
return encoded;
}

function toBytes(uint8[10][90] memory table) internal pure returns (bytes memory) {
function toBytes(uint8[10][91] memory table) internal pure returns (bytes memory) {
bytes memory encoded;
assembly ("memory-safe") {
encoded := mload(0x40)
mstore(0x40, add(encoded, add(900, 0x20)))
mstore(0x40, add(encoded, add(910, 0x20)))

let cursor := sub(mload(0x40), 0x20)

for {
let i := add(table, mul(0x20, 89))
let i := add(table, mul(0x20, 90))
let j := mul(0x20, 9)
} gt(cursor, encoded) {
cursor := sub(cursor, 1)
Expand All @@ -58,21 +56,21 @@ library LibLogTable {
}
}

mstore(cursor, 900)
mstore(cursor, 910)
}
return encoded;
}

function toBytes(uint8[10][100] memory table) internal pure returns (bytes memory) {
function toBytes(uint8[10][101] memory table) internal pure returns (bytes memory) {
bytes memory encoded;
assembly ("memory-safe") {
encoded := mload(0x40)
mstore(0x40, add(encoded, add(1000, 0x20)))
mstore(0x40, add(encoded, add(1010, 0x20)))

let cursor := sub(mload(0x40), 0x20)

for {
let i := add(table, mul(0x20, 99))
let i := add(table, mul(0x20, 100))
let j := mul(0x20, 9)
} gt(cursor, encoded) {
cursor := sub(cursor, 1)
Expand All @@ -86,7 +84,7 @@ library LibLogTable {
}
}

mstore(cursor, 1000)
mstore(cursor, 1010)
}
return encoded;
}
Expand Down Expand Up @@ -119,16 +117,16 @@ library LibLogTable {
return encoded;
}

function toBytes(uint16[10][100] memory table) internal pure returns (bytes memory) {
function toBytes(uint16[10][101] memory table) internal pure returns (bytes memory) {
bytes memory encoded;
assembly ("memory-safe") {
encoded := mload(0x40)
mstore(0x40, add(encoded, add(2000, 0x20)))
mstore(0x40, add(encoded, add(2020, 0x20)))

let cursor := sub(mload(0x40), 0x20)

for {
let i := add(table, mul(0x20, 99))
let i := add(table, mul(0x20, 100))
let j := mul(0x20, 9)
} gt(cursor, encoded) {
cursor := sub(cursor, 2)
Expand All @@ -142,12 +140,12 @@ library LibLogTable {
}
}

mstore(cursor, 2000)
mstore(cursor, 2020)
}
return encoded;
}

function logTableDec() internal pure returns (uint16[10][90] memory) {
function logTableDec() internal pure returns (uint16[10][91] memory) {
return [
[
0,
Expand Down Expand Up @@ -348,11 +346,14 @@ library LibLogTable {
[9823, 9827, 9832, 9836, 9841, 9845, 9850, 9854, 9859, 9863],
[9868, 9872, 9877, 9881, 9886, 9890, 9894, 9899, 9903, 9908],
[9912, 9917, 9921, 9926, 9930, 9934, 9939, 9943, 9948, 9952],
[9956, 9961, 9965, 9969, 9974, 9978, 9983, 9987, 9991, 9996]
[9956, 9961, 9965, 9969, 9974, 9978, 9983, 9987, 9991, 9996],
// This row is a placeholder for when we interpolate past the last
// entry in the table. The last entry is 10000, so we just use that.
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
];
Comment thread
thedavidmeister marked this conversation as resolved.
}

function logTableDecSmall() internal pure returns (uint8[10][90] memory) {
function logTableDecSmall() internal pure returns (uint8[10][91] memory) {
return [
[0, 4, 9, 13, 17, 21, 26, 30, 34, 38],
[0, 4, 8, 12, 15, 19, 23, 27, 31, 35],
Expand Down Expand Up @@ -443,7 +444,10 @@ library LibLogTable {
[0, 0, 1, 1, 2, 2, 3, 3, 4, 4],
[0, 0, 1, 1, 2, 2, 3, 3, 4, 4],
[0, 0, 1, 1, 2, 2, 3, 3, 4, 4],
[0, 0, 1, 1, 2, 2, 3, 3, 3, 4]
[0, 0, 1, 1, 2, 2, 3, 3, 3, 4],
// This row is a placeholder for when we interpolate past the last
// entry in the table.
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
}

Expand All @@ -462,7 +466,7 @@ library LibLogTable {
];
}

function antiLogTableDec() internal pure returns (uint16[10][100] memory) {
function antiLogTableDec() internal pure returns (uint16[10][101] memory) {
return [
[1000, 1002, 1005, 1007, 1009, 1012, 1014, 1016, 1019, 1021],
[1023, 1026, 1028, 1030, 1033, 1035, 1038, 1040, 1042, 1045],
Expand Down Expand Up @@ -563,11 +567,14 @@ library LibLogTable {
[9120, 9141, 9162, 9183, 9204, 9226, 9247, 9268, 9290, 9311],
[9333, 9354, 9376, 9397, 9419, 9441, 9462, 9484, 9506, 9528],
[9550, 9572, 9594, 9616, 9638, 9661, 9683, 9705, 9727, 9750],
[9772, 9795, 9817, 9840, 9863, 9886, 9908, 9931, 9954, 9977]
[9772, 9795, 9817, 9840, 9863, 9886, 9908, 9931, 9954, 9977],
// This row is a placeholder for when we need to interpolate from
// 9999 to 9999+1.
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
Comment thread
thedavidmeister marked this conversation as resolved.
];
Comment thread
thedavidmeister marked this conversation as resolved.
}

function antiLogTableDecSmall() internal pure returns (uint8[10][100] memory) {
function antiLogTableDecSmall() internal pure returns (uint8[10][101] memory) {
return [
[0, 0, 0, 1, 1, 1, 1, 2, 2, 2],
[0, 0, 0, 1, 1, 1, 1, 2, 2, 2],
Expand Down Expand Up @@ -668,7 +675,10 @@ library LibLogTable {
[0, 2, 4, 6, 8, 11, 13, 15, 17, 19],
[0, 2, 4, 7, 9, 11, 13, 15, 17, 20],
[0, 2, 4, 7, 9, 11, 13, 16, 18, 20],
[0, 2, 5, 7, 9, 11, 14, 16, 18, 20]
[0, 2, 5, 7, 9, 11, 14, 16, 18, 20],
// This row is a placeholder for when we need to interpolate from
// 9999 to 9999+1.
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
}
}
3 changes: 2 additions & 1 deletion test/abstract/LogTest.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

import {Test} from "forge-std/Test.sol";
// Re-export console2 here for convenience.
import {Test, console2} from "forge-std/Test.sol";
import {DataContractMemoryContainer, LibDataContract} from "rain.datacontract/lib/LibDataContract.sol";
import {LibDecimalFloatDeploy} from "src/lib/LibDecimalFloatDeploy.sol";

Expand Down
6 changes: 4 additions & 2 deletions test/lib/LibDecimalFloatSlow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
pragma solidity ^0.8.25;

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

library LibDecimalFloatSlow {
using LibDecimalFloat for Float;

function multiplySlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB)
internal
pure
Expand Down Expand Up @@ -38,7 +40,7 @@ library LibDecimalFloatSlow {
}

function invSlow(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) {
return LibDecimalFloat.divide(1e37, -37, signedCoefficient, exponent);
return LibDecimalFloatImplementation.divide(1e37, -37, signedCoefficient, exponent);
}

function eqSlow(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB)
Expand Down
59 changes: 21 additions & 38 deletions test/src/lib/LibDecimalFloat.abs.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,33 @@ import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol";
contract LibDecimalFloatAbsTest is Test {
using LibDecimalFloat for Float;

function absExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) {
return LibDecimalFloat.abs(signedCoefficient, exponent);
}

function absExternal(Float memory float) external pure returns (Float memory) {
return LibDecimalFloat.abs(float);
}
/// Validate that operations using stack-based parameters (int256, int256)
/// and memory-based parameters (Float struct) yield identical results.

function testAbsMem(Float memory float) external {
try this.absExternal(float.signedCoefficient, float.exponent) returns (
int256 signedCoefficient, int256 exponent
) {
Float memory floatAbs = this.absExternal(float);
assertEq(signedCoefficient, floatAbs.signedCoefficient);
assertEq(exponent, floatAbs.exponent);
} catch (bytes memory err) {
vm.expectRevert(err);
this.absExternal(float);
}
}

/// Anything non negative is identity.
function testAbsNonNegative(int256 signedCoefficient, int256 exponent) external pure {
signedCoefficient = bound(signedCoefficient, 0, type(int256).max);
(int256 absSignedCoefficient, int256 absExponent) = LibDecimalFloat.abs(signedCoefficient, exponent);
assertEq(absSignedCoefficient, signedCoefficient);
assertEq(absExponent, exponent);
function testAbsNonNegative(int256 signedCoefficient, int32 exponent) external pure {
signedCoefficient = bound(signedCoefficient, 0, type(int224).max);
Float float = LibDecimalFloat.packLossless(signedCoefficient, exponent);
Float result = float.abs();
(int256 resultSignedCoefficient, int256 resultExponent) = LibDecimalFloat.unpack(result);
assertEq(resultSignedCoefficient, signedCoefficient);
assertEq(resultExponent, exponent);
}

/// Anything negative is negated. Except for the minimum value.
function testAbsNegative(int256 signedCoefficient, int256 exponent) external pure {
signedCoefficient = bound(signedCoefficient, type(int256).min + 1, -1);
(int256 absSignedCoefficient, int256 absExponent) = LibDecimalFloat.abs(signedCoefficient, exponent);
assertEq(absSignedCoefficient, -signedCoefficient);
assertEq(absExponent, exponent);
function testAbsNegative(int256 signedCoefficient, int32 exponent) external pure {
signedCoefficient = bound(signedCoefficient, type(int224).min + 1, -1);
Float float = LibDecimalFloat.packLossless(signedCoefficient, exponent);
Float result = float.abs();
(int256 resultSignedCoefficient, int256 resultExponent) = LibDecimalFloat.unpack(result);
assertEq(resultSignedCoefficient, -signedCoefficient);
assertEq(resultExponent, exponent);
}

/// Minimum value is shifted one OOM.
function testAbsMinValue(int256 exponent) external pure {
vm.assume(exponent < type(int256).max);
(int256 absSignedCoefficient, int256 absExponent) = LibDecimalFloat.abs(type(int256).min, exponent);
assertEq(absSignedCoefficient, -(type(int256).min / 10));
assertEq(absExponent, exponent + 1);
function testAbsMinValue(int32 exponent) external pure {
vm.assume(exponent < type(int32).max);
Float float = LibDecimalFloat.packLossless(type(int224).min, exponent);
Float result = float.abs();
(int256 resultSignedCoefficient, int256 resultExponent) = LibDecimalFloat.unpack(result);
assertEq(resultSignedCoefficient, -(type(int224).min / 10));
assertEq(resultExponent, exponent + 1);
}
}
Loading
Loading