From f37077005380c617e76afea17822b8424c3b536d Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Thu, 17 Apr 2025 19:18:00 +0400 Subject: [PATCH 01/13] wip on consolidating packed --- src/error/ErrDecimalFloat.sol | 3 + src/lib/LibDecimalFloat.sol | 219 ++++++++++-------- src/lib/format/LibFormatDecimalFloat.sol | 5 +- src/lib/parse/LibParseDecimalFloat.sol | 33 +-- test/src/lib/LibDecimalFloat.abs.t.sol | 18 +- test/src/lib/LibDecimalFloat.add.t.sol | 31 ++- test/src/lib/LibDecimalFloat.decimal.t.sol | 16 +- .../lib/LibDecimalFloat.decimalLossless.t.sol | 20 +- test/src/lib/LibDecimalFloat.divide.t.sol | 16 +- test/src/lib/LibDecimalFloat.eq.t.sol | 9 +- test/src/lib/LibDecimalFloat.floor.t.sol | 16 +- test/src/lib/LibDecimalFloat.frac.t.sol | 17 +- test/src/lib/LibDecimalFloat.gt.t.sol | 8 +- test/src/lib/LibDecimalFloat.inv.t.sol | 16 +- test/src/lib/LibDecimalFloat.log10.t.sol | 16 +- test/src/lib/LibDecimalFloat.lt.t.sol | 9 +- test/src/lib/LibDecimalFloat.max.t.sol | 59 ++--- test/src/lib/LibDecimalFloat.min.t.sol | 47 ++-- test/src/lib/LibDecimalFloat.minus.t.sol | 14 +- test/src/lib/LibDecimalFloat.multiply.t.sol | 15 +- test/src/lib/LibDecimalFloat.normalize.t.sol | 16 +- test/src/lib/LibDecimalFloat.pack.t.sol | 47 +--- test/src/lib/LibDecimalFloat.power.t.sol | 15 +- test/src/lib/LibDecimalFloat.power10.t.sol | 14 +- test/src/lib/LibDecimalFloat.sub.t.sol | 15 +- .../lib/format/LibFormatDecimalFloat.t.sol | 11 +- test/src/lib/parse/LibParseDecimalFloat.t.sol | 18 +- 27 files changed, 378 insertions(+), 345 deletions(-) diff --git a/src/error/ErrDecimalFloat.sol b/src/error/ErrDecimalFloat.sol index ce4921e9..60f814b9 100644 --- a/src/error/ErrDecimalFloat.sol +++ b/src/error/ErrDecimalFloat.sol @@ -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); diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 0143a318..cd9f11fe 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -10,6 +10,7 @@ import { } from "../generated/LogTables.pointers.sol"; import { ExponentOverflow, + CoefficientOverflow, Log10Negative, Log10Zero, NegativeFixedDecimalConversion, @@ -28,7 +29,7 @@ import { EXPONENT_MIN } from "./implementation/LibDecimalFloatImplementation.sol"; -type PackedFloat is bytes32; +type Float is bytes32; uint256 constant ADD_MAX_EXPONENT_DIFF = 37; @@ -38,16 +39,16 @@ int256 constant EXPONENT_LEAP_SIZE = 24; /// @dev The multiplier for the leap size, calculated at compile time. int256 constant EXPONENT_LEAP_MULTIPLIER = int256(uint256(10 ** uint256(EXPONENT_LEAP_SIZE))); -/// @dev An in memory representation of a float. This is more gas intensive but -/// helps mitigate stack depth issues, and is more ergonomic for the caller. -/// This is not a packed representation, so it is not normalized. -/// @param signedCoefficient The signed coefficient of the floating point -/// representation. -/// @param exponent The exponent of the floating point representation. -struct Float { - int256 signedCoefficient; - int256 exponent; -} +// /// @dev An in memory representation of a float. This is more gas intensive but +// /// helps mitigate stack depth issues, and is more ergonomic for the caller. +// /// This is not a packed representation, so it is not normalized. +// /// @param signedCoefficient The signed coefficient of the floating point +// /// representation. +// /// @param exponent The exponent of the floating point representation. +// struct Float { +// int256 signedCoefficient; +// int256 exponent; +// } /// @title LibDecimalFloat /// Floating point math library for Rainlang. @@ -98,6 +99,8 @@ struct Float { /// applied without rigourous testing/mathematical models that are beyond the /// scope of the typical user of Rainlang. library LibDecimalFloat { + using LibDecimalFloat for Float; + /// Convert a fixed point decimal value to a signed coefficient and exponent. /// The conversion can be lossy if the unsigned value is too large to fit in /// the signed coefficient. @@ -131,12 +134,9 @@ library LibDecimalFloat { /// e.g. If 1e18 represents 1 this would be 18 decimals. /// @return float The Float struct containing the signed coefficient and /// exponent. - function fromFixedDecimalLossyMem(uint256 value, uint8 decimals) - internal - pure - returns (Float memory float, bool lossless) - { - (float.signedCoefficient, float.exponent, lossless) = fromFixedDecimalLossy(value, decimals); + function fromFixedDecimalLossyPacked(uint256 value, uint8 decimals) internal pure returns (Float, bool) { + (int256 signedCoefficient, int256 exponent, bool lossless) = fromFixedDecimalLossy(value, decimals); + return (pack(signedCoefficient, exponent), lossless); } /// Lossless version of `fromFixedDecimalLossy`. This will revert if the @@ -159,8 +159,9 @@ library LibDecimalFloat { /// @param decimals As per `fromFixedDecimalLossyMem`. /// @return float The Float struct containing the signed coefficient and /// exponent. - function fromFixedDecimalLosslessMem(uint256 value, uint8 decimals) internal pure returns (Float memory float) { - (float.signedCoefficient, float.exponent) = fromFixedDecimalLossless(value, decimals); + function fromFixedDecimalLosslessPacked(uint256 value, uint8 decimals) internal pure returns (Float) { + (int256 signedCoefficient, int256 exponent) = fromFixedDecimalLossless(value, decimals); + return pack(signedCoefficient, exponent); } /// Convert a signed coefficient and exponent to a fixed point decimal value. @@ -248,8 +249,9 @@ library LibDecimalFloat { /// e.g. If 1e18 represents 1 this would be 18 decimals. /// @return value The fixed point decimal value. /// @return lossless `true` if the conversion is lossless. - function toFixedDecimalLossy(Float memory float, uint8 decimals) internal pure returns (uint256, bool) { - return toFixedDecimalLossy(float.signedCoefficient, float.exponent, decimals); + function toFixedDecimalLossy(Float float, uint8 decimals) internal pure returns (uint256, bool) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + return toFixedDecimalLossy(signedCoefficient, exponent, decimals); } /// Lossless version of `toFixedDecimalLossy`. This will revert if the @@ -279,8 +281,9 @@ library LibDecimalFloat { /// @param decimals The number of decimals in the fixed point representation. /// e.g. If 1e18 represents 1 this would be 18 decimals. /// @return value The fixed point decimal value. - function toFixedDecimalLossless(Float memory float, uint8 decimals) internal pure returns (uint256) { - return toFixedDecimalLossless(float.signedCoefficient, float.exponent, decimals); + function toFixedDecimalLossless(Float float, uint8 decimals) internal pure returns (uint256) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + return toFixedDecimalLossless(signedCoefficient, exponent, decimals); } /// Pack a signed coefficient and exponent into a single `PackedFloat`. @@ -296,23 +299,11 @@ library LibDecimalFloat { /// @param signedCoefficient The signed coefficient of the floating point /// representation. /// @param exponent The exponent of the floating point representation. - /// @return packed The packed representation of the signed coefficient and + /// @return float The packed representation of the signed coefficient and /// exponent. - function pack(int256 signedCoefficient, int256 exponent) internal pure returns (PackedFloat packed) { - if (signedCoefficient == 0) { - return PackedFloat.wrap(0); - } - + function pack(int256 signedCoefficient, int256 exponent) internal pure returns (Float float) { if (int224(signedCoefficient) != signedCoefficient) { - if (signedCoefficient / 1e72 != 0) { - signedCoefficient /= 1e5; - exponent += 5; - } - - while (int224(signedCoefficient) != signedCoefficient) { - signedCoefficient /= 10; - ++exponent; - } + revert CoefficientOverflow(signedCoefficient, exponent); } if (int32(exponent) != exponent) { @@ -323,34 +314,26 @@ library LibDecimalFloat { // coefficient is negative. uint256 mask = type(uint224).max; assembly ("memory-safe") { - packed := or(and(signedCoefficient, mask), shl(0xe0, exponent)) + float := or(and(signedCoefficient, mask), shl(0xe0, exponent)) } } - function pack(Float memory float) internal pure returns (PackedFloat) { - return pack(float.signedCoefficient, float.exponent); - } - - /// Unpack a packed uint256 into a signed coefficient and exponent. This is + /// Unpack a packed bytes32 into a signed coefficient and exponent. This is /// the inverse of `pack`. Note that the unpacked values are not necessarily /// normalized, especially if their provenance is unknown or user input. - /// @param packed The packed representation of the signed coefficient and + /// @param float The packed representation of the signed coefficient and /// exponent. /// @return signedCoefficient The signed coefficient of the floating point /// representation. /// @return exponent The exponent of the floating point representation. - function unpack(PackedFloat packed) internal pure returns (int256 signedCoefficient, int256 exponent) { + function unpack(Float float) internal pure returns (int256 signedCoefficient, int256 exponent) { uint256 mask = type(uint224).max; assembly ("memory-safe") { - signedCoefficient := signextend(27, and(packed, mask)) - exponent := sar(0xe0, packed) + signedCoefficient := signextend(27, and(float, mask)) + exponent := sar(0xe0, float) } } - function unpackMem(PackedFloat packed) internal pure returns (Float memory float) { - (float.signedCoefficient, float.exponent) = unpack(packed); - } - /// Add two floats together as a normalized result. /// /// Note that because the input values can have arbitrary exponents that may @@ -479,9 +462,11 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function add(Float memory a, Float memory b) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = - add(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function add(Float a, Float b) internal pure returns (Float) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + (int256 signedCoefficient, int256 exponent) = add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return pack(signedCoefficient, exponent); } /// Subtract two floats together as a normalized result. @@ -512,9 +497,11 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function sub(Float memory a, Float memory b) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = - sub(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function sub(Float a, Float b) internal pure returns (Float result) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + (int256 signedCoefficient, int256 exponent) = sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return pack(signedCoefficient, exponent); } /// Negates and normalizes a float. @@ -554,8 +541,10 @@ library LibDecimalFloat { /// ergonomic for the caller. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function minus(Float memory float) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = minus(float.signedCoefficient, float.exponent); + function minus(Float float) internal pure returns (Float) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + (signedCoefficient, exponent) = minus(signedCoefficient, exponent); + return pack(signedCoefficient, exponent); } /// Returns the absolute value of a float. @@ -582,8 +571,10 @@ library LibDecimalFloat { /// ergonomic for the caller. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function abs(Float memory float) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = abs(float.signedCoefficient, float.exponent); + function abs(Float float) internal pure returns (Float) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + (signedCoefficient, exponent) = abs(signedCoefficient, exponent); + return pack(signedCoefficient, exponent); } /// https://speleotrove.com/decimal/daops.html#refmult @@ -648,9 +639,12 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function multiply(Float memory a, Float memory b) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = - multiply(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function multiply(Float a, Float b) internal pure returns (Float) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + (int256 signedCoefficient, int256 exponent) = + multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return pack(signedCoefficient, exponent); } /// https://speleotrove.com/decimal/daops.html#refdivide @@ -725,9 +719,12 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function divide(Float memory a, Float memory b) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = - divide(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function divide(Float a, Float b) internal pure returns (Float) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + (int256 signedCoefficient, int256 exponent) = + divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return pack(signedCoefficient, exponent); } /// Inverts a float. Equivalent to `1 / x` with modest gas optimizations. @@ -745,8 +742,10 @@ library LibDecimalFloat { /// ergonomic for the caller. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function inv(Float memory float) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = inv(float.signedCoefficient, float.exponent); + function inv(Float float) internal pure returns (Float) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + (signedCoefficient, exponent) = inv(signedCoefficient, exponent); + return pack(signedCoefficient, exponent); } /// Numeric equality for floats. @@ -780,8 +779,10 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function eq(Float memory a, Float memory b) internal pure returns (bool) { - return eq(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function eq(Float a, Float b) internal pure returns (bool) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + return eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } /// Numeric less than for floats. @@ -815,8 +816,10 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function lt(Float memory a, Float memory b) internal pure returns (bool) { - return lt(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function lt(Float a, Float b) internal pure returns (bool) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + return lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } /// Numeric greater than for floats. @@ -850,8 +853,10 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function gt(Float memory a, Float memory b) internal pure returns (bool) { - return gt(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function gt(Float a, Float b) internal pure returns (bool) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + return gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } /// Fractional component of a float. @@ -873,8 +878,10 @@ library LibDecimalFloat { /// ergonomic for the caller. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function frac(Float memory float) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = frac(float.signedCoefficient, float.exponent); + function frac(Float float) internal pure returns (Float) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + (signedCoefficient, exponent) = frac(signedCoefficient, exponent); + return pack(signedCoefficient, exponent); } /// Integer component of a float. @@ -896,8 +903,10 @@ library LibDecimalFloat { /// ergonomic for the caller. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function floor(Float memory float) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = floor(float.signedCoefficient, float.exponent); + function floor(Float float) internal pure returns (Float) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + (signedCoefficient, exponent) = floor(signedCoefficient, exponent); + return pack(signedCoefficient, exponent); } /// 10^x for a float x. @@ -957,9 +966,10 @@ library LibDecimalFloat { /// logarithm tables. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function power10(address tablesDataContract, Float memory float) internal view returns (Float memory result) { - (result.signedCoefficient, result.exponent) = - power10(tablesDataContract, float.signedCoefficient, float.exponent); + function power10(address tablesDataContract, Float float) internal view returns (Float) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + (signedCoefficient, exponent) = power10(tablesDataContract, signedCoefficient, exponent); + return pack(signedCoefficient, exponent); } /// log10(x) for a float x. @@ -1079,8 +1089,10 @@ library LibDecimalFloat { /// logarithm tables. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function log10(address tablesDataContract, Float memory float) internal view returns (Float memory result) { - (result.signedCoefficient, result.exponent) = log10(tablesDataContract, float.signedCoefficient, float.exponent); + function log10(address tablesDataContract, Float float) internal view returns (Float) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + (signedCoefficient, exponent) = log10(tablesDataContract, signedCoefficient, exponent); + return pack(signedCoefficient, exponent); } /// x^y = 10^(y * log10(x)) @@ -1120,13 +1132,12 @@ library LibDecimalFloat { /// exponent of the base. /// @param b The Float struct containing the signed coefficient and /// exponent of the exponent. - function power(address tablesDataContract, Float memory a, Float memory b) - internal - view - returns (Float memory result) - { - (result.signedCoefficient, result.exponent) = - power(tablesDataContract, a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function power(address tablesDataContract, Float a, Float b) internal view returns (Float) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + (int256 signedCoefficient, int256 exponent) = + power(tablesDataContract, signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return pack(signedCoefficient, exponent); } /// Returns the minimum of two values. @@ -1158,9 +1169,11 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function min(Float memory a, Float memory b) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = - min(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function min(Float a, Float b) internal pure returns (Float) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + (int256 signedCoefficient, int256 exponent) = min(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return pack(signedCoefficient, exponent); } /// Returns the maximum of two values. @@ -1192,9 +1205,11 @@ library LibDecimalFloat { /// exponent of the first floating point number. /// @param b The Float struct containing the signed coefficient and /// exponent of the second floating point number. - function max(Float memory a, Float memory b) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = - max(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function max(Float a, Float b) internal pure returns (Float result) { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + (int256 signedCoefficient, int256 exponent) = max(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return pack(signedCoefficient, exponent); } /// Same as normalize, but accepts a Float struct instead of separate values. @@ -1202,8 +1217,10 @@ library LibDecimalFloat { /// ergonomic for the caller. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function normalize(Float memory float) internal pure returns (Float memory result) { - (result.signedCoefficient, result.exponent) = - LibDecimalFloatImplementation.normalize(float.signedCoefficient, float.exponent); + function normalize(Float float) internal pure returns (Float) { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + (int256 signedCoefficientNormalized, int256 exponentNormalized) = + LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); + return pack(signedCoefficientNormalized, exponentNormalized); } } diff --git a/src/lib/format/LibFormatDecimalFloat.sol b/src/lib/format/LibFormatDecimalFloat.sol index a5623149..c1765906 100644 --- a/src/lib/format/LibFormatDecimalFloat.sol +++ b/src/lib/format/LibFormatDecimalFloat.sol @@ -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); } } diff --git a/src/lib/parse/LibParseDecimalFloat.sol b/src/lib/parse/LibParseDecimalFloat.sol index 1540d9fa..0fb95e6e 100644 --- a/src/lib/parse/LibParseDecimalFloat.sol +++ b/src/lib/parse/LibParseDecimalFloat.sol @@ -13,34 +13,18 @@ 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"; 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.pack(signedCoefficient, exponent)); } function parseDecimalFloat(uint256 start, uint256 end) @@ -103,7 +87,7 @@ library LibParseDecimalFloat { // fractional part. exponent = int256(fracStart) - int256(nonZeroCursor); uint256 scale = uint256(-exponent); - if (scale >= 77 && signedCoefficient != 0) { + if (scale >= 67 && signedCoefficient != 0) { return (ParseDecimalPrecisionLoss.selector, cursor, 0, 0); } scale = 10 ** scale; @@ -141,15 +125,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.pack(signedCoefficient, exponent)); } } diff --git a/test/src/lib/LibDecimalFloat.abs.t.sol b/test/src/lib/LibDecimalFloat.abs.t.sol index 7391ad4c..e6fc2bb0 100644 --- a/test/src/lib/LibDecimalFloat.abs.t.sol +++ b/test/src/lib/LibDecimalFloat.abs.t.sol @@ -12,19 +12,15 @@ contract LibDecimalFloatAbsTest is Test { return LibDecimalFloat.abs(signedCoefficient, exponent); } - function absExternal(Float memory float) external pure returns (Float memory) { + function absExternal(Float float) external pure returns (Float) { 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); + + function testAbsPacked(Float float) external { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.unpack(float); + try this.absExternal(signedCoefficient, exponent) returns (int256 signedCoefficientAbs, int256 exponentAbs) { + Float floatAbs = this.absExternal(float); + assertEq(Float.unwrap(floatAbs), Float.unwrap(LibDecimalFloat.pack(signedCoefficientAbs, exponentAbs))); } catch (bytes memory err) { vm.expectRevert(err); this.absExternal(float); diff --git a/test/src/lib/LibDecimalFloat.add.t.sol b/test/src/lib/LibDecimalFloat.add.t.sol index ff06cabf..fd755850 100644 --- a/test/src/lib/LibDecimalFloat.add.t.sol +++ b/test/src/lib/LibDecimalFloat.add.t.sol @@ -17,24 +17,39 @@ contract LibDecimalFloatDecimalAddTest is Test { return LibDecimalFloat.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function addExternal(Float memory a, Float memory b) external pure returns (int256, int256) { - return LibDecimalFloat.add(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + function addExternal(Float a, Float b) external pure returns (Float) { + return LibDecimalFloat.add(a, b); } - /// Stack and mem are the same. - function testAddMem(Float memory a, Float memory b) external { - try this.addExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns ( + function testAddPacked(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = LibDecimalFloat.unpack(a); + (int256 signedCoefficientB, int256 exponentB) = LibDecimalFloat.unpack(b); + try this.addExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - Float memory resultMem = a.add(b); - assertEq(signedCoefficient, resultMem.signedCoefficient); - assertEq(exponent, resultMem.exponent); + Float resultMem = a.add(b); + assertEq(Float.unwrap(resultMem), Float.unwrap(LibDecimalFloat.pack(signedCoefficient, exponent))); } catch (bytes memory err) { vm.expectRevert(err); a.add(b); } } + function testAddUnpacked(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + { + try this.addExternal( + LibDecimalFloat.pack(signedCoefficientA, exponentA), LibDecimalFloat.pack(signedCoefficientB, exponentB) + ) returns (Float resultPacked) { + (int256 signedCoefficient, int256 exponent) = + this.addExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + assertEq(Float.unwrap(resultPacked), Float.unwrap(LibDecimalFloat.pack(signedCoefficient, exponent))); + } catch (bytes memory err) { + vm.expectRevert(err); + this.addExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + } + /// Simple 0 add 0 /// 0 + 0 = 0 function testAddZero() external pure { diff --git a/test/src/lib/LibDecimalFloat.decimal.t.sol b/test/src/lib/LibDecimalFloat.decimal.t.sol index 24290692..a7dc2c81 100644 --- a/test/src/lib/LibDecimalFloat.decimal.t.sol +++ b/test/src/lib/LibDecimalFloat.decimal.t.sol @@ -24,24 +24,26 @@ contract LibDecimalFloatDecimalTest is Test { return LibDecimalFloat.toFixedDecimalLossy(signedCoefficient, exponent, decimals); } - function toFixedDecimalLossyExternal(Float memory float, uint8 decimals) external pure returns (uint256, bool) { + function toFixedDecimalLossyExternal(Float float, uint8 decimals) external pure returns (uint256, bool) { return float.toFixedDecimalLossy(decimals); } /// Memory version of from behaves same as stack version. - function testFromFixedDecimalLossyMem(uint256 value, uint8 decimals) external pure { + function testFromFixedDecimalLossyPacked(uint256 value, uint8 decimals) external pure { (int256 signedCoefficient, int256 exponent, bool lossless) = LibDecimalFloat.fromFixedDecimalLossy(value, decimals); - (Float memory float, bool floatLossless) = LibDecimalFloat.fromFixedDecimalLossyMem(value, decimals); - assertEq(float.signedCoefficient, signedCoefficient, "signedCoefficient"); - assertEq(float.exponent, exponent, "exponent"); + (Float float, bool floatLossless) = LibDecimalFloat.fromFixedDecimalLossyPacked(value, decimals); + (int256 signedCoefficientFloat, int256 exponentFloat) = LibDecimalFloat.unpack(float); + assertEq(signedCoefficientFloat, signedCoefficient, "signedCoefficient"); + assertEq(exponentFloat, exponent, "exponent"); assertEq(floatLossless, lossless, "lossless"); } /// Memory version of to behaves same as stack version. - function testToFixedDecimalLossyMem(Float memory float, uint8 decimals) external { - try this.toFixedDecimalLossyExternal(float.signedCoefficient, float.exponent, decimals) returns ( + function testToFixedDecimalLossyPacked(Float float, uint8 decimals) external { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.unpack(float); + try this.toFixedDecimalLossyExternal(signedCoefficient, exponent, decimals) returns ( uint256 value, bool lossless ) { (uint256 valueOut, bool losslessOut) = float.toFixedDecimalLossy(decimals); diff --git a/test/src/lib/LibDecimalFloat.decimalLossless.t.sol b/test/src/lib/LibDecimalFloat.decimalLossless.t.sol index f54bc543..03e4b916 100644 --- a/test/src/lib/LibDecimalFloat.decimalLossless.t.sol +++ b/test/src/lib/LibDecimalFloat.decimalLossless.t.sol @@ -18,8 +18,8 @@ contract LibDecimalFloatDecimalLosslessTest is Test { return LibDecimalFloat.fromFixedDecimalLossless(value, decimals); } - function fromFixedDecimalLosslessMemExternal(uint256 value, uint8 decimals) external pure returns (Float memory) { - return LibDecimalFloat.fromFixedDecimalLosslessMem(value, decimals); + function fromFixedDecimalLosslessPackedExternal(uint256 value, uint8 decimals) external pure returns (Float) { + return LibDecimalFloat.fromFixedDecimalLosslessPacked(value, decimals); } function toFixedDecimalLosslessExternal(int256 signedCoefficient, int256 exponent, uint8 decimals) @@ -30,7 +30,7 @@ contract LibDecimalFloatDecimalLosslessTest is Test { return LibDecimalFloat.toFixedDecimalLossless(signedCoefficient, exponent, decimals); } - function toFixedDecimalLosslessMemExternal(Float memory float, uint8 decimals) external pure returns (uint256) { + function toFixedDecimalLosslessMemExternal(Float float, uint8 decimals) external pure returns (uint256) { return float.toFixedDecimalLossless(decimals); } @@ -42,17 +42,17 @@ contract LibDecimalFloatDecimalLosslessTest is Test { abi.encodeWithSelector(LossyConversionToFloat.selector, value / 10, 1 - int256(uint256(decimals))) ); } - (Float memory float) = LibDecimalFloat.fromFixedDecimalLosslessMem(value, decimals); + Float float = LibDecimalFloat.fromFixedDecimalLosslessPacked(value, decimals); (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.fromFixedDecimalLossless(value, decimals); - assertEq(float.signedCoefficient, signedCoefficient); - assertEq(float.exponent, exponent); + (int256 signedCoefficientPacked, int256 exponentPacked) = float.unpack(); + assertEq(signedCoefficient, signedCoefficientPacked); + assertEq(exponent, exponentPacked); } /// Memory version of to behaves the same as stack version. - function testToFixedDecimalLosslessMem(Float memory float, uint8 decimals) external { - try this.toFixedDecimalLosslessExternal(float.signedCoefficient, float.exponent, decimals) returns ( - uint256 value - ) { + function testToFixedDecimalLosslessMem(Float float, uint8 decimals) external { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + try this.toFixedDecimalLosslessExternal(signedCoefficient, exponent, decimals) returns (uint256 value) { uint256 valueFloat = float.toFixedDecimalLossless(decimals); assertEq(valueFloat, value); } catch (bytes memory err) { diff --git a/test/src/lib/LibDecimalFloat.divide.t.sol b/test/src/lib/LibDecimalFloat.divide.t.sol index 89bdc631..780184d4 100644 --- a/test/src/lib/LibDecimalFloat.divide.t.sol +++ b/test/src/lib/LibDecimalFloat.divide.t.sol @@ -17,18 +17,20 @@ contract LibDecimalFloatDivideTest is Test { return LibDecimalFloat.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function divideExternal(Float memory floatA, Float memory floatB) external pure returns (Float memory) { + function divideExternal(Float floatA, Float floatB) external pure returns (Float) { return LibDecimalFloat.divide(floatA, floatB); } - /// Stack and mem are the same. - function testDivideMem(Float memory a, Float memory b) external { - try this.divideExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns ( + function testDividePacked(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.divideExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - Float memory float = this.divideExternal(a, b); - assertEq(signedCoefficient, float.signedCoefficient); - assertEq(exponent, float.exponent); + Float float = this.divideExternal(a, b); + (int256 signedCoefficientFloat, int256 exponentFloat) = float.unpack(); + assertEq(signedCoefficient, signedCoefficientFloat); + assertEq(exponent, exponentFloat); } catch (bytes memory err) { vm.expectRevert(err); this.divideExternal(a, b); diff --git a/test/src/lib/LibDecimalFloat.eq.t.sol b/test/src/lib/LibDecimalFloat.eq.t.sol index b18485ac..8ff74dd6 100644 --- a/test/src/lib/LibDecimalFloat.eq.t.sol +++ b/test/src/lib/LibDecimalFloat.eq.t.sol @@ -18,13 +18,14 @@ contract LibDecimalFloatEqTest is Test { return LibDecimalFloat.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function eqExternal(Float memory floatA, Float memory floatB) external pure returns (bool) { + function eqExternal(Float floatA, Float floatB) external pure returns (bool) { return LibDecimalFloat.eq(floatA, floatB); } - /// Test to verify that stack-based and memory-based implementations produce the same results. - function testEqMem(Float memory a, Float memory b) external { - try this.eqExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns (bool eq) { + function testEqPacked(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.eqExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns (bool eq) { bool actual = this.eqExternal(a, b); assertEq(eq, actual); } catch (bytes memory err) { diff --git a/test/src/lib/LibDecimalFloat.floor.t.sol b/test/src/lib/LibDecimalFloat.floor.t.sol index d22abe8d..6394169c 100644 --- a/test/src/lib/LibDecimalFloat.floor.t.sol +++ b/test/src/lib/LibDecimalFloat.floor.t.sol @@ -12,18 +12,20 @@ contract LibDecimalFloatFloorTest is Test { return LibDecimalFloat.floor(signedCoefficient, exponent); } - function floorExternal(Float memory float) external pure returns (Float memory) { + function floorExternal(Float float) external pure returns (Float) { return LibDecimalFloat.floor(float); } /// Stack and mem are the same. - function testFloorMem(Float memory float) external { - try this.floorExternal(float.signedCoefficient, float.exponent) returns ( - int256 signedCoefficient, int256 exponent + function testFloorMem(Float float) external { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + try this.floorExternal(signedCoefficient, exponent) returns ( + int256 signedCoefficientFloor, int256 exponentFloor ) { - Float memory floatFloor = this.floorExternal(float); - assertEq(signedCoefficient, floatFloor.signedCoefficient); - assertEq(exponent, floatFloor.exponent); + Float floatFloor = this.floorExternal(float); + (int256 signedCoefficientFloorUnpacked, int256 exponentFloorUnpacked) = floatFloor.unpack(); + assertEq(signedCoefficientFloor, signedCoefficientFloorUnpacked); + assertEq(exponentFloor, exponentFloorUnpacked); } catch (bytes memory err) { vm.expectRevert(err); this.floorExternal(float); diff --git a/test/src/lib/LibDecimalFloat.frac.t.sol b/test/src/lib/LibDecimalFloat.frac.t.sol index 72a10abe..33b87d59 100644 --- a/test/src/lib/LibDecimalFloat.frac.t.sol +++ b/test/src/lib/LibDecimalFloat.frac.t.sol @@ -12,18 +12,19 @@ contract LibDecimalFloatFracTest is Test { return LibDecimalFloat.frac(signedCoefficient, exponent); } - function fracExternal(Float memory float) external pure returns (Float memory) { + function fracExternal(Float float) external pure returns (Float) { return LibDecimalFloat.frac(float); } - /// Test to verify that stack-based and memory-based implementations produce the same results. - function testFracMem(Float memory float) external { - try this.fracExternal(float.signedCoefficient, float.exponent) returns ( - int256 signedCoefficient, int256 exponent + function testFracMem(Float float) external { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + try this.fracExternal(signedCoefficient, exponent) returns ( + int256 signedCoefficientResult, int256 exponentResult ) { - Float memory floatFrac = this.fracExternal(float); - assertEq(signedCoefficient, floatFrac.signedCoefficient); - assertEq(exponent, floatFrac.exponent); + Float floatFrac = this.fracExternal(float); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatFrac.unpack(); + assertEq(signedCoefficientResult, signedCoefficientUnpacked); + assertEq(exponentResult, exponentUnpacked); } catch (bytes memory err) { vm.expectRevert(err); this.fracExternal(float); diff --git a/test/src/lib/LibDecimalFloat.gt.t.sol b/test/src/lib/LibDecimalFloat.gt.t.sol index 73d7c3d5..34c49aa4 100644 --- a/test/src/lib/LibDecimalFloat.gt.t.sol +++ b/test/src/lib/LibDecimalFloat.gt.t.sol @@ -18,13 +18,15 @@ contract LibDecimalFloatGtTest is Test { return LibDecimalFloat.gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function gtExternal(Float memory floatA, Float memory floatB) external pure returns (bool) { + function gtExternal(Float floatA, Float floatB) external pure returns (bool) { return LibDecimalFloat.gt(floatA, floatB); } /// Stack and mem are the same. - function testGtMem(Float memory a, Float memory b) external { - try this.gtExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns (bool gt) { + function testGtMem(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.gtExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns (bool gt) { bool actual = this.gtExternal(a, b); assertEq(gt, actual); } catch (bytes memory err) { diff --git a/test/src/lib/LibDecimalFloat.inv.t.sol b/test/src/lib/LibDecimalFloat.inv.t.sol index f258b3bb..09c744a4 100644 --- a/test/src/lib/LibDecimalFloat.inv.t.sol +++ b/test/src/lib/LibDecimalFloat.inv.t.sol @@ -12,18 +12,20 @@ contract LibDecimalFloatInvTest is Test { return LibDecimalFloat.inv(signedCoefficient, exponent); } - function invExternal(Float memory float) external pure returns (Float memory) { + function invExternal(Float float) external pure returns (Float) { return LibDecimalFloat.inv(float); } /// Stack and mem are the same. - function testInvMem(Float memory float) external { - try this.invExternal(float.signedCoefficient, float.exponent) returns ( - int256 signedCoefficient, int256 exponent + function testInvMem(Float float) external { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + try this.invExternal(signedCoefficient, exponent) returns ( + int256 signedCoefficientResult, int256 exponentResult ) { - Float memory floatInv = this.invExternal(float); - assertEq(signedCoefficient, floatInv.signedCoefficient); - assertEq(exponent, floatInv.exponent); + Float floatInv = this.invExternal(float); + (int256 signedCoefficientResultUnpacked, int256 exponentResultUnpacked) = floatInv.unpack(); + assertEq(signedCoefficientResultUnpacked, signedCoefficientResult); + assertEq(exponentResultUnpacked, exponentResult); } catch (bytes memory err) { vm.expectRevert(err); this.invExternal(float); diff --git a/test/src/lib/LibDecimalFloat.log10.t.sol b/test/src/lib/LibDecimalFloat.log10.t.sol index 8638aded..db67e1e4 100644 --- a/test/src/lib/LibDecimalFloat.log10.t.sol +++ b/test/src/lib/LibDecimalFloat.log10.t.sol @@ -14,19 +14,21 @@ contract LibDecimalFloatLog10Test is LogTest { return LibDecimalFloat.log10(tables, signedCoefficient, exponent); } - function log10External(Float memory float) external returns (Float memory) { + function log10External(Float float) external returns (Float) { address tables = logTables(); return LibDecimalFloat.log10(tables, float); } /// Stack and mem are the same. - function testLog10Mem(Float memory float) external { - try this.log10External(float.signedCoefficient, float.exponent) returns ( - int256 signedCoefficient, int256 exponent + function testLog10Mem(Float float) external { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + try this.log10External(signedCoefficient, exponent) returns ( + int256 signedCoefficientResult, int256 exponentResult ) { - Float memory floatLog10 = this.log10External(float); - assertEq(signedCoefficient, floatLog10.signedCoefficient); - assertEq(exponent, floatLog10.exponent); + Float floatLog10 = this.log10External(float); + (int256 signedCoefficientResultUnpacked, int256 exponentResultUnpacked) = floatLog10.unpack(); + assertEq(signedCoefficientResultUnpacked, signedCoefficientResult); + assertEq(exponentResultUnpacked, exponentResult); } catch (bytes memory err) { vm.expectRevert(err); this.log10External(float); diff --git a/test/src/lib/LibDecimalFloat.lt.t.sol b/test/src/lib/LibDecimalFloat.lt.t.sol index 588eceae..3d43e901 100644 --- a/test/src/lib/LibDecimalFloat.lt.t.sol +++ b/test/src/lib/LibDecimalFloat.lt.t.sol @@ -18,13 +18,14 @@ contract LibDecimalFloatLtTest is Test { return LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function ltExternal(Float memory floatA, Float memory floatB) external pure returns (bool) { + function ltExternal(Float floatA, Float floatB) external pure returns (bool) { return LibDecimalFloat.lt(floatA, floatB); } - /// Test to verify that stack-based and memory-based implementations produce the same results. - function testLtMem(Float memory a, Float memory b) external { - try this.ltExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns (bool lt) { + function testLtMem(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.ltExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns (bool lt) { bool actual = this.ltExternal(a, b); assertEq(lt, actual); } catch (bytes memory err) { diff --git a/test/src/lib/LibDecimalFloat.max.t.sol b/test/src/lib/LibDecimalFloat.max.t.sol index d46bbc26..2d426916 100644 --- a/test/src/lib/LibDecimalFloat.max.t.sol +++ b/test/src/lib/LibDecimalFloat.max.t.sol @@ -15,57 +15,64 @@ contract LibDecimalFloatMaxTest is Test { return LibDecimalFloat.max(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function maxExternal(Float memory floatA, Float memory floatB) external pure returns (Float memory) { + function maxExternal(Float floatA, Float floatB) external pure returns (Float) { return floatA.max(floatB); } - /// Test to verify that stack-based and memory-based implementations produce the same results. - function testMaxMem(Float memory a, Float memory b) external { - try this.maxExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns ( - int256 signedCoefficient, int256 exponent + function testMaxMem(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.maxExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( + int256 signedCoefficientResult, int256 exponentResult ) { - Float memory actual = this.maxExternal(a, b); - assertEq(signedCoefficient, actual.signedCoefficient); - assertEq(exponent, actual.exponent); + Float floatResult = this.maxExternal(a, b); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatResult.unpack(); + assertEq(signedCoefficientResult, signedCoefficientUnpacked); + assertEq(exponentResult, exponentUnpacked); } catch (bytes memory err) { vm.expectRevert(err); this.maxExternal(a, b); } } - /// x.max(x) - function testMaxX(Float memory x) external pure { - Float memory y = x.max(x); + /// x.max(x) + function testMaxX(Float x) external pure { + Float y = x.max(x); assertTrue(y.eq(x), "x.max(x) != x"); } - /// x.max(y) == y.max(x) - function testMaxXY(Float memory x, Float memory y) external pure { - Float memory maxXY = x.max(y); - Float memory maxYX = y.max(x); + /// x.max(y) == y.max(x) + function testMaxXY(Float x, Float y) external pure { + Float maxXY = x.max(y); + Float maxYX = y.max(x); assertTrue(maxXY.eq(maxYX), "maxXY != maxYX"); } + /// x.max(y) for x == y + function testMaxXYEqual(int256 signedCoefficientX, int256 exponentX) external pure { + int256 signedCoefficientY = signedCoefficientX; + int256 exponentY = exponentX; + (int256 signedCoefficientZ, int256 exponentZ) = + LibDecimalFloat.max(signedCoefficientX, exponentX, signedCoefficientY, exponentY); - function testMaxXYEqual(Float memory x) external pure { - Float memory y = Float(x.signedCoefficient, x.exponent); - Float memory z = x.max(y); - assertTrue(z.eq(x), "x.max(y) != x"); - assertTrue(z.eq(y), "x.max(y) != y"); + assertEq(signedCoefficientZ, signedCoefficientX, "signedCoefficientZ != signedCoefficientX"); + assertEq(exponentZ, exponentX, "exponentZ != exponentX"); + assertEq(signedCoefficientZ, signedCoefficientY, "signedCoefficientZ != signedCoefficientY"); + assertEq(exponentZ, exponentY, "exponentZ != exponentY"); } - /// x.max(y) for x > y - function testMaxXYGreater(Float memory x, Float memory y) external pure { + /// x.max(y) for x > y + function testMaxXYGreater(Float x, Float y) external pure { vm.assume(x.gt(y)); - Float memory z = x.max(y); + Float z = x.max(y); assertTrue(z.eq(x), "x.max(y) == x"); assertTrue(!z.eq(y), "x.max(y) != y"); } - /// x.max(y) for x < y - function testMaxXYLess(Float memory x, Float memory y) external pure { + /// x.max(y) for x < y + function testMaxXYLess(Float x, Float y) external pure { vm.assume(x.lt(y)); - Float memory z = x.max(y); + Float z = x.max(y); assertTrue(!z.eq(x), "x.max(y) != x"); assertTrue(z.eq(y), "x.max(y) == y"); } diff --git a/test/src/lib/LibDecimalFloat.min.t.sol b/test/src/lib/LibDecimalFloat.min.t.sol index cc1af8dd..34bcc271 100644 --- a/test/src/lib/LibDecimalFloat.min.t.sol +++ b/test/src/lib/LibDecimalFloat.min.t.sol @@ -15,18 +15,21 @@ contract LibDecimalFloatMinTest is Test { return LibDecimalFloat.min(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function minExternal(Float memory floatA, Float memory floatB) external pure returns (Float memory) { + function minExternal(Float floatA, Float floatB) external pure returns (Float) { return floatA.min(floatB); } /// Test to verify that stack-based and memory-based implementations produce the same results. - function testMinMem(Float memory a, Float memory b) external { - try this.minExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns ( + function testMinMem(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.minExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - Float memory actual = this.minExternal(a, b); - assertEq(signedCoefficient, actual.signedCoefficient); - assertEq(exponent, actual.exponent); + Float actual = this.minExternal(a, b); + (int256 signedCoefficientResult, int256 exponentResult) = actual.unpack(); + assertEq(signedCoefficient, signedCoefficientResult); + assertEq(exponent, exponentResult); } catch (bytes memory err) { vm.expectRevert(err); this.minExternal(a, b); @@ -34,38 +37,42 @@ contract LibDecimalFloatMinTest is Test { } /// x.min(x) - function testMinX(Float memory x) external pure { - Float memory y = x.min(x); + function testMinX(Float x) external pure { + Float y = x.min(x); assertTrue(y.eq(x), "x.min(x) != x"); } /// x.min(y) == y.min(x) - function testMinXY(Float memory x, Float memory y) external pure { - Float memory minXY = x.min(y); - Float memory minYX = y.min(x); + function testMinXY(Float x, Float y) external pure { + Float minXY = x.min(y); + Float minYX = y.min(x); assertTrue(minXY.eq(minYX), "minXY != minYX"); } /// x.min(y) for x == y - function testMinXYEqual(Float memory x) external pure { - Float memory y = Float(x.signedCoefficient, x.exponent); - Float memory z = x.min(y); - assertTrue(z.eq(x), "x.min(y) != x"); - assertTrue(z.eq(y), "x.min(y) != y"); + function testMinXYEqual(int256 signedCoefficientX, int256 exponentX) external pure { + int256 signedCoefficientY = signedCoefficientX; + int256 exponentY = exponentX; + (int256 signedCoefficientZ, int256 exponentZ) = + LibDecimalFloat.min(signedCoefficientX, exponentX, signedCoefficientY, exponentY); + assertEq(signedCoefficientZ, signedCoefficientX, "signedCoefficientZ != signedCoefficientX"); + assertEq(exponentZ, exponentX, "exponentZ != exponentX"); + assertEq(signedCoefficientZ, signedCoefficientY, "signedCoefficientZ != signedCoefficientY"); + assertEq(exponentZ, exponentY, "exponentZ != exponentY"); } /// x.min(y) for x < y - function testMinXYLess(Float memory x, Float memory y) external pure { + function testMinXYLess(Float x, Float y) external pure { vm.assume(x.lt(y)); - Float memory z = x.min(y); + Float z = x.min(y); assertTrue(z.eq(x), "x.min(y) != x"); assertTrue(!z.eq(y), "x.min(y) == y"); } /// x.min(y) for x > y - function testMinXYGreater(Float memory x, Float memory y) external pure { + function testMinXYGreater(Float x, Float y) external pure { vm.assume(x.gt(y)); - Float memory z = x.min(y); + Float z = x.min(y); assertTrue(z.eq(y), "x.min(y) != y"); assertTrue(!z.eq(x), "x.min(y) == x"); } diff --git a/test/src/lib/LibDecimalFloat.minus.t.sol b/test/src/lib/LibDecimalFloat.minus.t.sol index ae9f2e0a..b44ed8e2 100644 --- a/test/src/lib/LibDecimalFloat.minus.t.sol +++ b/test/src/lib/LibDecimalFloat.minus.t.sol @@ -13,18 +13,20 @@ contract LibDecimalFloatMinusTest is Test { return LibDecimalFloat.minus(signedCoefficient, exponent); } - function minusExternal(Float memory float) external pure returns (Float memory) { + function minusExternal(Float float) external pure returns (Float) { return LibDecimalFloat.minus(float); } /// Stack and mem are the same. - function testMinusMem(Float memory float) external { - try this.minusExternal(float.signedCoefficient, float.exponent) returns ( + function testMinusMem(Float float) external { + (int256 signedCoefficientFloat, int256 exponentFloat) = float.unpack(); + try this.minusExternal(signedCoefficientFloat, exponentFloat) returns ( int256 signedCoefficient, int256 exponent ) { - Float memory floatMinus = this.minusExternal(float); - assertEq(signedCoefficient, floatMinus.signedCoefficient); - assertEq(exponent, floatMinus.exponent); + Float floatMinus = this.minusExternal(float); + (int256 signedCoefficientMinus, int256 exponentMinus) = floatMinus.unpack(); + assertEq(signedCoefficient, signedCoefficientMinus); + assertEq(exponent, exponentMinus); } catch (bytes memory err) { vm.expectRevert(err); this.minusExternal(float); diff --git a/test/src/lib/LibDecimalFloat.multiply.t.sol b/test/src/lib/LibDecimalFloat.multiply.t.sol index 572e72a6..aa00aa94 100644 --- a/test/src/lib/LibDecimalFloat.multiply.t.sol +++ b/test/src/lib/LibDecimalFloat.multiply.t.sol @@ -25,18 +25,21 @@ contract LibDecimalFloatMultiplyTest is Test { return LibDecimalFloat.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function multiplyExternal(Float memory floatA, Float memory floatB) external pure returns (Float memory) { + function multiplyExternal(Float floatA, Float floatB) external pure returns (Float) { return LibDecimalFloat.multiply(floatA, floatB); } /// Stack and mem are the same. - function testMultiplyMem(Float memory a, Float memory b) external { - try this.multiplyExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns ( + function testMultiplyMem(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.multiplyExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - Float memory float = this.multiplyExternal(a, b); - assertEq(signedCoefficient, float.signedCoefficient); - assertEq(exponent, float.exponent); + Float float = this.multiplyExternal(a, b); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = float.unpack(); + assertEq(signedCoefficient, signedCoefficientUnpacked); + assertEq(exponent, exponentUnpacked); } catch (bytes memory err) { vm.expectRevert(err); this.multiplyExternal(a, b); diff --git a/test/src/lib/LibDecimalFloat.normalize.t.sol b/test/src/lib/LibDecimalFloat.normalize.t.sol index aa2bf10e..08e80331 100644 --- a/test/src/lib/LibDecimalFloat.normalize.t.sol +++ b/test/src/lib/LibDecimalFloat.normalize.t.sol @@ -12,18 +12,20 @@ contract LibDecimalFloatNormalizeTest is Test { return LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); } - function normalizeExternal(Float memory float) external pure returns (Float memory) { + function normalizeExternal(Float float) external pure returns (Float) { return LibDecimalFloat.normalize(float); } /// Stack and mem are the same. - function testNormalizeMem(Float memory float) external { - try this.normalizeExternal(float.signedCoefficient, float.exponent) returns ( - int256 signedCoefficient, int256 exponent + function testNormalizeMem(Float float) external { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + try this.normalizeExternal(signedCoefficient, exponent) returns ( + int256 signedCoefficientNormalized, int256 exponentNormalized ) { - Float memory floatNormalized = this.normalizeExternal(float); - assertEq(signedCoefficient, floatNormalized.signedCoefficient); - assertEq(exponent, floatNormalized.exponent); + Float floatNormalized = this.normalizeExternal(float); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatNormalized.unpack(); + assertEq(signedCoefficientNormalized, signedCoefficientUnpacked); + assertEq(exponentNormalized, exponentUnpacked); } catch (bytes memory err) { vm.expectRevert(err); this.normalizeExternal(float); diff --git a/test/src/lib/LibDecimalFloat.pack.t.sol b/test/src/lib/LibDecimalFloat.pack.t.sol index d8131d40..cd43913a 100644 --- a/test/src/lib/LibDecimalFloat.pack.t.sol +++ b/test/src/lib/LibDecimalFloat.pack.t.sol @@ -1,31 +1,23 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, ExponentOverflow, PackedFloat, Float, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, ExponentOverflow, Float, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatPackTest is Test { - function packExternal(int256 signedCoefficient, int256 exponent) external pure returns (PackedFloat) { + function packExternal(int256 signedCoefficient, int256 exponent) external pure returns (Float) { return LibDecimalFloat.pack(signedCoefficient, exponent); } - function packExternal(Float memory float) external pure returns (PackedFloat) { - return LibDecimalFloat.pack(float); - } - - function unpackExternal(PackedFloat packed) external pure returns (int256, int256) { - return LibDecimalFloat.unpack(packed); - } - - function unpackMemExternal(PackedFloat packed) external pure returns (Float memory) { - return LibDecimalFloat.unpackMem(packed); + function unpackExternal(Float float) external pure returns (int256, int256) { + return LibDecimalFloat.unpack(float); } /// Round trip from/to parts. function testPartsRoundTrip(int224 signedCoefficient, int32 exponent) external pure { - PackedFloat packed = LibDecimalFloat.pack(signedCoefficient, exponent); - (int256 signedCoefficientOut, int256 exponentOut) = LibDecimalFloat.unpack(packed); + Float float = LibDecimalFloat.pack(signedCoefficient, exponent); + (int256 signedCoefficientOut, int256 exponentOut) = LibDecimalFloat.unpack(float); assertEq(signedCoefficient, signedCoefficientOut, "coefficient"); if (signedCoefficient != 0) { @@ -42,8 +34,8 @@ contract LibDecimalFloatPackTest is Test { LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); vm.assume(int32(exponentNormalized) == exponentNormalized); - PackedFloat packed = LibDecimalFloat.pack(signedCoefficientNormalized, exponentNormalized); - (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = LibDecimalFloat.unpack(packed); + Float float = LibDecimalFloat.pack(signedCoefficientNormalized, exponentNormalized); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = LibDecimalFloat.unpack(float); assertTrue( LibDecimalFloat.eq( @@ -52,27 +44,4 @@ contract LibDecimalFloatPackTest is Test { "eq" ); } - - /// Mem and stack versions behave the same for pack. - function testPackMem(Float memory float) external { - try this.packExternal(float.signedCoefficient, float.exponent) returns (PackedFloat packed) { - PackedFloat packedMem = LibDecimalFloat.pack(float); - assertEq(PackedFloat.unwrap(packed), PackedFloat.unwrap(packedMem), "packed"); - } catch (bytes memory err) { - vm.expectRevert(err); - LibDecimalFloat.pack(float); - } - } - - /// Mem and stack versions behave the same for unpack. - function testUnpackMem(PackedFloat packed) external { - try this.unpackExternal(packed) returns (int256 signedCoefficient, int256 exponent) { - Float memory float = this.unpackMemExternal(packed); - assertEq(signedCoefficient, float.signedCoefficient, "coefficient"); - assertEq(exponent, float.exponent, "exponent"); - } catch (bytes memory err) { - vm.expectRevert(err); - this.unpackMemExternal(packed); - } - } } diff --git a/test/src/lib/LibDecimalFloat.power.t.sol b/test/src/lib/LibDecimalFloat.power.t.sol index e3278ee9..19bf67a4 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -17,18 +17,21 @@ contract LibDecimalFloatPowerTest is LogTest { return LibDecimalFloat.power(logTables(), signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function powerExternal(Float memory floatA, Float memory floatB) external returns (Float memory) { + function powerExternal(Float floatA, Float floatB) external returns (Float) { return LibDecimalFloat.power(logTables(), floatA, floatB); } /// Stack and mem are the same. - function testPowerMem(Float memory a, Float memory b) external { - try this.powerExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns ( + function testPowerMem(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.powerExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - Float memory float = this.powerExternal(a, b); - assertEq(signedCoefficient, float.signedCoefficient); - assertEq(exponent, float.exponent); + Float float = this.powerExternal(a, b); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = float.unpack(); + assertEq(signedCoefficient, signedCoefficientUnpacked); + assertEq(exponent, exponentUnpacked); } catch (bytes memory err) { vm.expectRevert(err); this.powerExternal(a, b); diff --git a/test/src/lib/LibDecimalFloat.power10.t.sol b/test/src/lib/LibDecimalFloat.power10.t.sol index d326c371..730dce5a 100644 --- a/test/src/lib/LibDecimalFloat.power10.t.sol +++ b/test/src/lib/LibDecimalFloat.power10.t.sol @@ -12,19 +12,21 @@ contract LibDecimalFloatPower10Test is LogTest { return LibDecimalFloat.power10(tables, signedCoefficient, exponent); } - function power10External(Float memory float) external returns (Float memory) { + function power10External(Float float) external returns (Float) { address tables = logTables(); return LibDecimalFloat.power10(tables, float); } /// Stack and mem are the same. - function testPower10Mem(Float memory float) external { - try this.power10External(float.signedCoefficient, float.exponent) returns ( + function testPower10Mem(Float float) external { + (int256 signedCoefficientFloat, int256 exponentFloat) = float.unpack(); + try this.power10External(signedCoefficientFloat, exponentFloat) returns ( int256 signedCoefficient, int256 exponent ) { - Float memory floatPower10 = this.power10External(float); - assertEq(signedCoefficient, floatPower10.signedCoefficient); - assertEq(exponent, floatPower10.exponent); + Float floatPower10 = this.power10External(float); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatPower10.unpack(); + assertEq(signedCoefficient, signedCoefficientUnpacked); + assertEq(exponent, exponentUnpacked); } catch (bytes memory err) { vm.expectRevert(err); this.power10External(float); diff --git a/test/src/lib/LibDecimalFloat.sub.t.sol b/test/src/lib/LibDecimalFloat.sub.t.sol index e57078ea..5cd68528 100644 --- a/test/src/lib/LibDecimalFloat.sub.t.sol +++ b/test/src/lib/LibDecimalFloat.sub.t.sol @@ -17,18 +17,21 @@ contract LibDecimalFloatSubTest is Test { return LibDecimalFloat.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } - function subExternal(Float memory floatA, Float memory floatB) external pure returns (Float memory) { + function subExternal(Float floatA, Float floatB) external pure returns (Float) { return LibDecimalFloat.sub(floatA, floatB); } /// Test to verify that stack-based and memory-based implementations produce the same results. - function testSubMem(Float memory a, Float memory b) external { - try this.subExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns ( + function testSubMem(Float a, Float b) external { + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + try this.subExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - Float memory float = this.subExternal(a, b); - assertEq(signedCoefficient, float.signedCoefficient); - assertEq(exponent, float.exponent); + Float float = this.subExternal(a, b); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = float.unpack(); + assertEq(signedCoefficient, signedCoefficientUnpacked); + assertEq(exponent, exponentUnpacked); } catch (bytes memory err) { vm.expectRevert(err); this.subExternal(a, b); diff --git a/test/src/lib/format/LibFormatDecimalFloat.t.sol b/test/src/lib/format/LibFormatDecimalFloat.t.sol index 2057bff6..111fc8c0 100644 --- a/test/src/lib/format/LibFormatDecimalFloat.t.sol +++ b/test/src/lib/format/LibFormatDecimalFloat.t.sol @@ -17,13 +17,14 @@ contract LibFormatDecimalFloatTest is Test { return LibFormatDecimalFloat.toDecimalString(signedCoefficient, exponent); } - function toString(Float memory float) external pure returns (string memory) { + function toString(Float float) external pure returns (string memory) { return LibFormatDecimalFloat.toDecimalString(float); } /// Check that the memory version matches the stack version. - function testFormatMem(Float memory float) external { - try this.toDecimalStringExternal(float.signedCoefficient, float.exponent) returns (string memory formatted) { + function testFormatMem(Float float) external { + (int256 signedCoefficient, int256 exponent) = float.unpack(); + try this.toDecimalStringExternal(signedCoefficient, exponent) returns (string memory formatted) { string memory actual = this.toString(float); assertEq(formatted, actual, "Formatted value mismatch"); } catch (bytes memory err) { @@ -37,9 +38,9 @@ contract LibFormatDecimalFloatTest is Test { // Dividing by 10 here keeps us clearly within the range of lossless // conversions. value = bound(value, 0, type(uint256).max / 10); - Float memory float = LibDecimalFloat.fromFixedDecimalLosslessMem(value, 18); + Float float = LibDecimalFloat.fromFixedDecimalLosslessPacked(value, 18); string memory formatted = LibFormatDecimalFloat.toDecimalString(float); - (bytes4 errorCode, Float memory parsed) = LibParseDecimalFloat.parseDecimalFloat(formatted); + (bytes4 errorCode, Float parsed) = LibParseDecimalFloat.parseDecimalFloat(formatted); assertEq(errorCode, 0, "Parse error"); assertTrue(float.eq(parsed), "Round trip failed"); } diff --git a/test/src/lib/parse/LibParseDecimalFloat.t.sol b/test/src/lib/parse/LibParseDecimalFloat.t.sol index 4e59443c..bcd4d2e1 100644 --- a/test/src/lib/parse/LibParseDecimalFloat.t.sol +++ b/test/src/lib/parse/LibParseDecimalFloat.t.sol @@ -8,11 +8,12 @@ import {LibBytes, Pointer} from "rain.solmem/lib/LibBytes.sol"; import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; import {ParseEmptyDecimalString} from "rain.string/error/ErrParse.sol"; import {MalformedExponentDigits, ParseDecimalPrecisionLoss, MalformedDecimalPoint} from "src/error/ErrParse.sol"; -import {Float} from "src/lib/LibDecimalFloat.sol"; +import {Float, LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; contract LibParseDecimalFloatTest is Test { using LibBytes for bytes; using Strings for uint256; + using LibDecimalFloat for Float; function parseDecimalFloatExternal(string memory data) external @@ -24,10 +25,10 @@ contract LibParseDecimalFloatTest is Test { LibParseDecimalFloat.parseDecimalFloat(cursor, Pointer.unwrap(bytes(data).endDataPointer())); } - function parseDecimalFloatExternalMem(string memory data) + function parseDecimalFloatExternalPacked(string memory data) external pure - returns (bytes4 errorSelector, Float memory float) + returns (bytes4 errorSelector, Float float) { (errorSelector, float) = LibParseDecimalFloat.parseDecimalFloat(data); } @@ -38,13 +39,14 @@ contract LibParseDecimalFloatTest is Test { bytes4 errorSelector, uint256 cursorAfter, int256 signedCoefficient, int256 exponent ) { (cursorAfter); - (bytes4 errorSelectorMem, Float memory float) = this.parseDecimalFloatExternalMem(data); - assertEq(errorSelector, errorSelectorMem, "Error selector mismatch"); - assertEq(signedCoefficient, float.signedCoefficient, "Signed coefficient mismatch"); - assertEq(exponent, float.exponent, "Exponent mismatch"); + (bytes4 errorSelectorPacked, Float float) = this.parseDecimalFloatExternalPacked(data); + assertEq(errorSelector, errorSelectorPacked, "Error selector mismatch"); + (int256 signedCoefficientPacked, int256 exponentPacked) = float.unpack(); + assertEq(signedCoefficient, signedCoefficientPacked, "Signed coefficient mismatch"); + assertEq(exponent, exponentPacked, "Exponent mismatch"); } catch (bytes memory err) { vm.expectRevert(err); - this.parseDecimalFloatExternalMem(data); + this.parseDecimalFloatExternal(data); } } From d5147aa39ea3603a9a40e2b8b8ca2b30027039d6 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Fri, 18 Apr 2025 13:32:10 +0400 Subject: [PATCH 02/13] wip on consolidation --- src/lib/LibDecimalFloat.sol | 263 +++++++----------- .../lib/format/LibFormatDecimalFloat.t.sol | 2 +- 2 files changed, 97 insertions(+), 168 deletions(-) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index cd9f11fe..0d2bd2bd 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -301,23 +301,43 @@ library LibDecimalFloat { /// @param exponent The exponent of the floating point representation. /// @return float The packed representation of the signed coefficient and /// exponent. - function pack(int256 signedCoefficient, int256 exponent) internal pure returns (Float float) { - if (int224(signedCoefficient) != signedCoefficient) { - revert CoefficientOverflow(signedCoefficient, exponent); - } + function packLossy(int256 signedCoefficient, int256 exponent) internal pure returns (Float float, bool lossless) { + unchecked { + lossless = int224(signedCoefficient) == signedCoefficient; + + // The reason that we can do unchecked exponent addition here is that + // when it overflows it will wrap to a very large negative number. + // This will get caught below when we check if the exponent fits in + // int32. + if (!lossless) { + if (signedCoefficient / 1e72 != 0) { + signedCoefficient /= 1e5; + exponent += 5; + } - if (int32(exponent) != exponent) { - revert ExponentOverflow(signedCoefficient, exponent); - } + while (int224(signedCoefficient) != signedCoefficient) { + signedCoefficient /= 10; + ++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") { - float := or(and(signedCoefficient, mask), shl(0xe0, exponent)) + 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") { + float := or(and(signedCoefficient, mask), shl(0xe0, exponent)) + } } } + function packLossless(int256 signedCoefficient, int256 exponent) internal pure returns (Float float) { + return packLossy(signedCoefficient, exponent); + } + /// Unpack a packed bytes32 into a signed coefficient and exponent. This is /// the inverse of `pack`. Note that the unpacked values are not necessarily /// normalized, especially if their provenance is unknown or user input. @@ -846,67 +866,44 @@ library LibDecimalFloat { return signedCoefficientA > signedCoefficientB; } - /// Same as gt, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param a The Float struct containing the signed coefficient and - /// exponent of the first floating point number. - /// @param b The Float struct containing the signed coefficient and - /// exponent of the second floating point number. + /// Numeric greater than for floats. + /// A float is greater than another if its numeric value is greater than the + /// other. For example, 1e3 is greater than 1e2, and 2e2 is greater than 1e2. + /// Any representable value can be compared without precision loss, e.g. no + /// normalization is done internally. + /// @param a The first float to compare. + /// @param b The second float to compare. function gt(Float a, Float b) internal pure returns (bool) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - return gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (signedCoefficientA, signedCoefficientB) = + LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return signedCoefficientA > signedCoefficientB; } /// Fractional component of a float. - /// @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 fractional - /// component. - /// @return exponent The exponent of the fractional component. - function frac(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { - (int256 characteristic, int256 mantissa) = - LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent); - (characteristic); - return (mantissa, exponent); - } - - /// Same as frac, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param float The Float struct containing the signed coefficient and - /// exponent of the floating point number. + /// @param float The float to frac. function frac(Float float) internal pure returns (Float) { (int256 signedCoefficient, int256 exponent) = float.unpack(); - (signedCoefficient, exponent) = frac(signedCoefficient, exponent); - return pack(signedCoefficient, exponent); - } - - /// Integer component of a float. - /// @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 integer - /// component. - /// @return exponent The exponent of the integer component. - function floor(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { (int256 characteristic, int256 mantissa) = LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent); - (mantissa); - return (characteristic, exponent); + (characteristic); + (Float result, bool lossless) = packLossy(mantissa, exponent); + // Frac is lossy by definition. + (lossless); + return result; } - /// Same as floor, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param float The Float struct containing the signed coefficient and - /// exponent of the floating point number. + /// Integer component of a float. + /// @param float The float to floor. function floor(Float float) internal pure returns (Float) { (int256 signedCoefficient, int256 exponent) = float.unpack(); - (signedCoefficient, exponent) = floor(signedCoefficient, exponent); - return pack(signedCoefficient, exponent); + (int256 characteristic, int256 mantissa) = + LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent); + (Float result, bool lossless) = packLossy(characteristic, exponent); + // Flooring is lossy by definition. + (lossless); + return result; } /// 10^x for a float x. @@ -969,7 +966,11 @@ library LibDecimalFloat { function power10(address tablesDataContract, Float float) internal view returns (Float) { (int256 signedCoefficient, int256 exponent) = float.unpack(); (signedCoefficient, exponent) = power10(tablesDataContract, signedCoefficient, exponent); - return pack(signedCoefficient, exponent); + (Float result, bool lossless) = packLossy(signedCoefficient, exponent); + // We don't care if power10 is lossy because it's an approximation + // anyway. + (lossless); + return result; } /// log10(x) for a float x. @@ -1089,13 +1090,16 @@ library LibDecimalFloat { /// logarithm tables. /// @param float The Float struct containing the signed coefficient and /// exponent of the floating point number. - function log10(address tablesDataContract, Float float) internal view returns (Float) { - (int256 signedCoefficient, int256 exponent) = float.unpack(); + function log10(address tablesDataContract, Float a) internal view returns (Float) { + (int256 signedCoefficient, int256 exponent) = a.unpack(); (signedCoefficient, exponent) = log10(tablesDataContract, signedCoefficient, exponent); - return pack(signedCoefficient, exponent); + (Float result, bool lossless) = packLossy(signedCoefficient, exponent); + // We don't care if log10 is lossy because it's an approximation anyway. + (lossless); + return result; } - /// x^y = 10^(y * log10(x)) + /// a^b = 10^(b * log10(a)) /// /// Due to the inaccuraces of log10 and power10, this is not perfectly /// accurate, a round trip like x^y^(1/y) will typically be within half a @@ -1104,123 +1108,48 @@ library LibDecimalFloat { /// /// Doesn't lose precision due to the exponent, for a wide range of /// exponents. - /// - /// @param signedCoefficientA The signed coefficient of the base. - /// @param exponentA The exponent of the base. - /// @param signedCoefficientB The signed coefficient of the exponent. - /// @param exponentB The exponent of the exponent. - /// @return signedCoefficient The signed coefficient of the result. - /// @return exponent The exponent of the result. - function power( - address tablesDataContract, - int256 signedCoefficientA, - int256 exponentA, - int256 signedCoefficientB, - int256 exponentB - ) internal view returns (int256, int256) { - (int256 signedCoefficient, int256 exponent) = log10(tablesDataContract, signedCoefficientA, exponentA); - (signedCoefficient, exponent) = multiply(signedCoefficient, exponent, signedCoefficientB, exponentB); - return power10(tablesDataContract, signedCoefficient, exponent); - } - - /// Same as power, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. /// @param tablesDataContract The address of the contract containing the /// logarithm tables. - /// @param a The Float struct containing the signed coefficient and - /// exponent of the base. - /// @param b The Float struct containing the signed coefficient and - /// exponent of the exponent. + /// @param a The float `a` in `a^b`. + /// @param b The float `b` in `a^b`. function power(address tablesDataContract, Float a, Float b) internal view returns (Float) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + (int256 signedCoefficientC, int256 exponentC) = log10(tablesDataContract, signedCoefficientA, exponentA); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - (int256 signedCoefficient, int256 exponent) = - power(tablesDataContract, signedCoefficientA, exponentA, signedCoefficientB, exponentB); - return pack(signedCoefficient, exponent); + (signedCoefficientC, exponentC) = multiply(signedCoefficientC, exponentC, signedCoefficientB, exponentB); + (signedCoefficientC, exponentC) = power10(tablesDataContract, signedCoefficientC, exponentC); + (Float c, bool lossless) = packLossy(signedCoefficientC, exponentC); + // We don't care if power is lossy because it's an approximation anyway. + (lossless); + return c; } /// Returns the minimum of two values. /// Convenience for `a < b ? a : b`. - /// @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 minimum value. - /// @return exponent The exponent of the minimum value. - function min(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (int256, int256) - { - if (lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB)) { - return (signedCoefficientA, exponentA); - } else { - return (signedCoefficientB, exponentB); - } - } - - /// Same as min, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param a The Float struct containing the signed coefficient and - /// exponent of the first floating point number. - /// @param b The Float struct containing the signed coefficient and - /// exponent of the second floating point number. + /// @param a The first float to compare. + /// @param b The second float to compare. + /// @return The minimum of the two floats. function min(Float a, Float b) internal pure returns (Float) { - (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - (int256 signedCoefficient, int256 exponent) = min(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - return pack(signedCoefficient, exponent); + return lt(a, b) ? a : b; } /// Returns the maximum of two values. /// Convenience for `a > b ? a : b`. - /// @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 maximum value. - /// @return exponent The exponent of the maximum value. - function max(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (int256, int256) - { - if (gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB)) { - return (signedCoefficientA, exponentA); - } else { - return (signedCoefficientB, exponentB); - } - } - - /// Same as max, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param a The Float struct containing the signed coefficient and - /// exponent of the first floating point number. - /// @param b The Float struct containing the signed coefficient and - /// exponent of the second floating point number. - function max(Float a, Float b) internal pure returns (Float result) { - (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - (int256 signedCoefficient, int256 exponent) = max(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - return pack(signedCoefficient, exponent); + /// @param a The first float to compare. + /// @param b The second float to compare. + function max(Float a, Float b) internal pure returns (Float) { + return gt(a, b) ? a : b; } - /// Same as normalize, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param float The Float struct containing the signed coefficient and - /// exponent of the floating point number. - function normalize(Float float) internal pure returns (Float) { - (int256 signedCoefficient, int256 exponent) = float.unpack(); - (int256 signedCoefficientNormalized, int256 exponentNormalized) = - LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); - return pack(signedCoefficientNormalized, exponentNormalized); - } + // /// Same as normalize, but accepts a Float struct instead of separate values. + // /// Costs more gas but helps mitigate stack depth issues, and is more + // /// ergonomic for the caller. + // /// @param float The Float struct containing the signed coefficient and + // /// exponent of the floating point number. + // function normalize(Float float) internal pure returns (Float) { + // (int256 signedCoefficient, int256 exponent) = float.unpack(); + // (int256 signedCoefficientNormalized, int256 exponentNormalized) = + // LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); + // return pack(signedCoefficientNormalized, exponentNormalized); + // } } diff --git a/test/src/lib/format/LibFormatDecimalFloat.t.sol b/test/src/lib/format/LibFormatDecimalFloat.t.sol index 111fc8c0..d254843e 100644 --- a/test/src/lib/format/LibFormatDecimalFloat.t.sol +++ b/test/src/lib/format/LibFormatDecimalFloat.t.sol @@ -34,7 +34,7 @@ contract LibFormatDecimalFloatTest is Test { } /// Test round tripping a value through parse and format. - function testRoundTrip(uint256 value) external pure { + function testFormatDecimalRoundTrip(uint256 value) external pure { // Dividing by 10 here keeps us clearly within the range of lossless // conversions. value = bound(value, 0, type(uint256).max / 10); From 207730b2ccc3193d041d9c174c88a48dce6d0bed Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Fri, 18 Apr 2025 21:34:04 +0400 Subject: [PATCH 03/13] wip on stack float --- src/lib/LibDecimalFloat.sol | 690 ++---------------- .../LibDecimalFloatImplementation.sol | 500 ++++++++++++- src/lib/parse/LibParseDecimalFloat.sol | 4 +- test/abstract/LogTest.sol | 3 +- test/lib/LibDecimalFloatSlow.sol | 6 +- test/src/lib/LibDecimalFloat.abs.t.sol | 55 +- test/src/lib/LibDecimalFloat.add.t.sol | 241 +----- test/src/lib/LibDecimalFloat.divide.t.sol | 120 +-- test/src/lib/LibDecimalFloat.eq.t.sol | 109 +-- test/src/lib/LibDecimalFloat.floor.t.sol | 40 +- test/src/lib/LibDecimalFloat.frac.t.sol | 43 +- test/src/lib/LibDecimalFloat.gt.t.sol | 116 +-- test/src/lib/LibDecimalFloat.inv.t.sol | 29 +- test/src/lib/LibDecimalFloat.log10.t.sol | 56 +- test/src/lib/LibDecimalFloat.lt.t.sol | 118 ++- test/src/lib/LibDecimalFloat.max.t.sol | 43 +- test/src/lib/LibDecimalFloat.min.t.sol | 43 +- test/src/lib/LibDecimalFloat.minus.t.sol | 20 +- test/src/lib/LibDecimalFloat.mixed.t.sol | 13 +- test/src/lib/LibDecimalFloat.multiply.t.sol | 109 +-- test/src/lib/LibDecimalFloat.normalize.t.sol | 34 - test/src/lib/LibDecimalFloat.pack.t.sol | 24 +- test/src/lib/LibDecimalFloat.power.t.sol | 60 +- test/src/lib/LibDecimalFloat.power10.t.sol | 74 +- test/src/lib/LibDecimalFloat.sub.t.sol | 43 +- .../LibDecimalFloatImplementation.add.t.sol | 222 ++++++ ...LibDecimalFloatImplementation.divide.t.sol | 126 ++++ .../LibDecimalFloatImplementation.eq.t.sol | 104 +++ .../LibDecimalFloatImplementation.inv.t.sol | 36 + .../LibDecimalFloatImplementation.log10.t.sol | 52 ++ .../LibDecimalFloatImplementation.minus.t.sol | 25 + ...bDecimalFloatImplementation.multiply.t.sol | 108 +++ ...ibDecimalFloatImplementation.power10.t.sol | 79 ++ .../LibDecimalFloatImplementation.sub.t.sol | 49 ++ 34 files changed, 1610 insertions(+), 1784 deletions(-) delete mode 100644 test/src/lib/LibDecimalFloat.normalize.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.divide.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.eq.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.minus.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.multiply.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol create mode 100644 test/src/lib/implementation/LibDecimalFloatImplementation.sub.t.sol diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 0d2bd2bd..f196d0f8 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -11,7 +11,6 @@ import { import { ExponentOverflow, CoefficientOverflow, - Log10Negative, Log10Zero, NegativeFixedDecimalConversion, LossyConversionFromFloat, @@ -31,25 +30,12 @@ import { type Float is bytes32; -uint256 constant ADD_MAX_EXPONENT_DIFF = 37; - /// @dev When normalizing a number, how far we "leap" when very far from /// normalized. int256 constant EXPONENT_LEAP_SIZE = 24; /// @dev The multiplier for the leap size, calculated at compile time. int256 constant EXPONENT_LEAP_MULTIPLIER = int256(uint256(10 ** uint256(EXPONENT_LEAP_SIZE))); -// /// @dev An in memory representation of a float. This is more gas intensive but -// /// helps mitigate stack depth issues, and is more ergonomic for the caller. -// /// This is not a packed representation, so it is not normalized. -// /// @param signedCoefficient The signed coefficient of the floating point -// /// representation. -// /// @param exponent The exponent of the floating point representation. -// struct Float { -// int256 signedCoefficient; -// int256 exponent; -// } - /// @title LibDecimalFloat /// Floating point math library for Rainlang. /// Broadly implements decimal floating point math with 224 signed bits for the @@ -136,7 +122,8 @@ library LibDecimalFloat { /// exponent. function fromFixedDecimalLossyPacked(uint256 value, uint8 decimals) internal pure returns (Float, bool) { (int256 signedCoefficient, int256 exponent, bool lossless) = fromFixedDecimalLossy(value, decimals); - return (pack(signedCoefficient, exponent), lossless); + (Float float, bool losslessPack) = packLossy(signedCoefficient, exponent); + return (float, lossless && losslessPack); } /// Lossless version of `fromFixedDecimalLossy`. This will revert if the @@ -161,7 +148,7 @@ library LibDecimalFloat { /// exponent. function fromFixedDecimalLosslessPacked(uint256 value, uint8 decimals) internal pure returns (Float) { (int256 signedCoefficient, int256 exponent) = fromFixedDecimalLossless(value, decimals); - return pack(signedCoefficient, exponent); + return packLossless(signedCoefficient, exponent); } /// Convert a signed coefficient and exponent to a fixed point decimal value. @@ -334,8 +321,12 @@ library LibDecimalFloat { } } - function packLossless(int256 signedCoefficient, int256 exponent) internal pure returns (Float float) { - return packLossy(signedCoefficient, exponent); + function packLossless(int256 signedCoefficient, int256 exponent) internal pure returns (Float) { + (Float c, bool lossless) = packLossy(signedCoefficient, exponent); + if (!lossless) { + revert CoefficientOverflow(signedCoefficient, exponent); + } + return c; } /// Unpack a packed bytes32 into a signed coefficient and exponent. This is @@ -354,127 +345,6 @@ library LibDecimalFloat { } } - /// Add two floats together as a normalized result. - /// - /// Note that because the input values can have arbitrary exponents that may - /// be very far apart, the normalization process is necessarily lossy. - /// For example, normalized 1 is 1e37 coefficient and -37 exponent. - /// Consider adding 1e37 coefficient with exponent 1. - /// These two numbers are identical in coefficient but their exponents are - /// 38 OOMs apart. While we can perform the addition and get the correct - /// result internally, as soon as we normalize the result, we will lose - /// precision and the result will be 1e37 coefficient with -37 exponent. - /// The precision of addition is therefore best case the full 37 decimals - /// representable in normalized form, if the two numbers share the same - /// exponent, but each step of exponent difference will lose a decimal of - /// precision in the output. In practise, this rarely matters as the onchain - /// conventions for amounts are typically 18 decimals or less, and so entire - /// token supplies are typically representable within ~26-33 decimals of - /// precision, making addition lossless for all actual possible values. - /// - /// https://speleotrove.com/decimal/daops.html#refaddsub - /// > add and subtract both take two operands. If either operand is a special - /// > value then the general rules apply. - /// > - /// > Otherwise, the operands are added (after inverting the sign used for - /// > the second operand if the operation is a subtraction), as follows: - /// > - /// > The coefficient of the result is computed by adding or subtracting the - /// > aligned coefficients of the two operands. The aligned coefficients are - /// > computed by comparing the exponents of the operands: - /// > - /// > - If they have the same exponent, the aligned coefficients are the same - /// > as the original coefficients. - /// > - Otherwise the aligned coefficient of the number with the larger - /// > exponent is its original coefficient multiplied by 10^n, where n is the - /// > absolute difference between the exponents, and the aligned coefficient - /// > of the other operand is the same as its original coefficient. - /// > - /// > If the signs of the operands differ then the smaller aligned - /// > coefficient is subtracted from the larger; otherwise they are added. - /// > - /// > The exponent of the result is the minimum of the exponents of the two - /// > operands. - /// > - /// > The sign of the result is determined as follows: - /// > - /// > - If the result is non-zero then the sign of the result is the sign of - /// > the operand having the larger absolute value. - /// > - Otherwise, the sign of a zero result is 0 unless either both operands - /// > were negative or the signs of the operands were different and the - /// > rounding is round-floor. - /// - /// @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 add(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (int256, int256) - { - // Zero for either is the edge case but we have to guard against it. - // Doing it eagerly with assembly is less gas than lazily with jumps. - bool eitherZero; - assembly ("memory-safe") { - eitherZero := or(iszero(signedCoefficientA), iszero(signedCoefficientB)) - } - if (eitherZero) { - if (signedCoefficientA == 0) { - return (signedCoefficientB, exponentB); - } else { - return (signedCoefficientA, exponentA); - } - } - - // Normalizing A and B gives us similar coefficients, which simplifies - // detecting when their exponents are too far apart to add without - // simply ignoring one of them. - (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); - - // We want A to represent the larger exponent. If this is not the case - // then swap them. - if (exponentB > exponentA) { - int256 tmp = signedCoefficientA; - signedCoefficientA = signedCoefficientB; - signedCoefficientB = tmp; - - tmp = exponentA; - exponentA = exponentB; - exponentB = tmp; - } - - // After normalization the signed coefficients are the same OOM in - // magnitude. However, what we need is for the exponents to be the same. - // If the exponents are close enough we can multiply coefficient A by - // some power of 10 to align their exponents without precision loss. - // If the exponents are too far apart, then all the information in B - // would be lost by the final normalization step, so we can just ignore - // B and return A. - uint256 multiplier; - unchecked { - uint256 alignmentExponentDiff = uint256(exponentA - exponentB); - // 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); - } - multiplier = 10 ** alignmentExponentDiff; - } - signedCoefficientA *= int256(multiplier); - - // The actual addition step. - unchecked { - signedCoefficientA += signedCoefficientB; - } - return (signedCoefficientA, exponentB); - } - /// Same as add, but accepts a Float struct instead of separate values. /// Costs more gas but helps mitigate stack depth issues, and is more /// ergonomic for the caller. @@ -485,75 +355,29 @@ library LibDecimalFloat { function add(Float a, Float b) internal pure returns (Float) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - (int256 signedCoefficient, int256 exponent) = add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - return pack(signedCoefficient, exponent); + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (Float c, bool lossless) = packLossy(signedCoefficient, exponent); + // Addition can be lossy. + (lossless); + return c; } /// 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 - returns (int256, int256) - { - (signedCoefficientB, exponentB) = minus(signedCoefficientB, exponentB); - return add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - - /// Same as sub, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param a The Float struct containing the signed coefficient and - /// exponent of the first floating point number. - /// @param b The Float struct containing the signed coefficient and - /// exponent of the second floating point number. - function sub(Float a, Float b) internal pure returns (Float result) { + /// @param a The float to subtract from. + /// @param b The float to subtract. + function sub(Float a, Float b) internal pure returns (Float) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - (int256 signedCoefficient, int256 exponent) = sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - return pack(signedCoefficient, exponent); - } - - /// 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. - /// > - /// > The operations are evaluated using the same rules as add and subtract; - /// > the operations plus(a) and minus(a) - /// > (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) { - unchecked { - // This is the only edge case that can't be simply negated. - if (signedCoefficient == type(int256).min) { - if (exponent == type(int256).max) { - revert ExponentOverflow(signedCoefficient, exponent); - } - signedCoefficient /= 10; - ++exponent; - } - return (-signedCoefficient, exponent); - } + (int256 signedCoefficientC, int256 exponentC) = + LibDecimalFloatImplementation.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (Float c, bool lossless) = packLossy(signedCoefficientC, exponentC); + // Subtraction can be lossy. + (lossless); + return c; } /// Same as minus, but accepts a Float struct instead of separate values. @@ -563,8 +387,11 @@ library LibDecimalFloat { /// exponent of the floating point number. function minus(Float float) internal pure returns (Float) { (int256 signedCoefficient, int256 exponent) = float.unpack(); - (signedCoefficient, exponent) = minus(signedCoefficient, exponent); - return pack(signedCoefficient, exponent); + (signedCoefficient, exponent) = LibDecimalFloatImplementation.minus(signedCoefficient, exponent); + (Float result, bool lossless) = packLossy(signedCoefficient, exponent); + // Minus is a lossy operation due to the asymmetry of signed integers. + (lossless); + return result; } /// Returns the absolute value of a float. @@ -576,80 +403,19 @@ library LibDecimalFloat { /// > abs takes one operand. If the operand is negative, the result is the /// > same as using the minus operation on the operand. Otherwise, the result /// > is the same as using the plus operation on the operand. - function abs(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { - unchecked { - if (signedCoefficient < 0) { - return minus(signedCoefficient, exponent); - } - - return (signedCoefficient, exponent); - } - } - - /// Same as abs, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param float The Float struct containing the signed coefficient and - /// exponent of the floating point number. + /// @param float The float to take the absolute value of. function abs(Float float) internal pure returns (Float) { (int256 signedCoefficient, int256 exponent) = float.unpack(); - (signedCoefficient, exponent) = abs(signedCoefficient, exponent); - return pack(signedCoefficient, exponent); - } - - /// https://speleotrove.com/decimal/daops.html#refmult - /// > multiply takes two operands. If either operand is a special value then - /// > the general rules apply. - /// > - /// > Otherwise, the operands are multiplied together - /// > (‘long multiplication’), resulting in a number which may be as long as - /// > the sum of the lengths of the two operands, as follows: - /// > - /// > - The coefficient of the result, before rounding, is computed by - /// > multiplying together the coefficients of the operands. - /// > - The exponent of the result, before rounding, is the sum of the - /// > exponents of the two operands. - /// > - The sign of the result is the exclusive or of the signs of the - /// > operands. - /// > - /// > The result is then rounded to precision digits if necessary, counting - /// > from the most significant digit of the result. - function multiply(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (int256, int256) - { - unchecked { - // Unchecked mul the coefficients and add the exponents. - int256 signedCoefficient = signedCoefficientA * signedCoefficientB; - - // Need to return early if the result is zero to avoid divide by - // zero in the overflow check. - if (signedCoefficient == 0) { - return (NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT); - } - - int256 exponent = exponentA + exponentB; - // No jumps to see if we overflowed. - bool didOverflow; - assembly ("memory-safe") { - didOverflow := - or( - iszero(eq(sdiv(signedCoefficient, signedCoefficientA), signedCoefficientB)), - iszero(eq(sub(exponent, exponentA), exponentB)) - ) - } - // If we did overflow, normalize and try again. Normalized values - // cannot overflow, so this will always succeed, provided the - // exponents are not out of bounds. - if (didOverflow) { - (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); - return multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - return (signedCoefficient, exponent); + if (signedCoefficient < 0) { + (signedCoefficient, exponent) = LibDecimalFloatImplementation.minus(signedCoefficient, exponent); } + + (Float result, bool lossless) = packLossy(signedCoefficient, exponent); + // At the limit of signed values there is the potential for a lossy + // conversion when negating. + (lossless); + return result; } /// Same as multiply, but accepts a Float struct instead of separate values. @@ -663,73 +429,11 @@ library LibDecimalFloat { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); (int256 signedCoefficient, int256 exponent) = - multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - return pack(signedCoefficient, exponent); - } - - /// https://speleotrove.com/decimal/daops.html#refdivide - /// > divide takes two operands. If either operand is a special value then - /// > the general rules apply. - /// > Otherwise, if the divisor is zero then either the Division undefined - /// > condition is raised (if the dividend is zero) and the result is NaN, - /// > or the Division by zero condition is raised and the result is an - /// > Infinity with a sign which is the exclusive or of the signs of the - /// > operands. - /// > - /// > Otherwise, a ‘long division’ is effected, as follows: - /// > - /// > - An integer variable, adjust, is initialized to 0. - /// > - If the dividend is non-zero, the coefficient of the result is - /// > computed as follows (using working copies of the operand - /// > coefficients, as necessary): - /// > - The operand coefficients are adjusted so that the coefficient of - /// > the dividend is greater than or equal to the coefficient of the - /// > divisor and is also less than ten times the coefficient of the - /// > divisor, thus: - /// > - While the coefficient of the dividend is less than the - /// > coefficient of the divisor it is multiplied by 10 and adjust is - /// > incremented by 1. - /// > - While the coefficient of the dividend is greater than or equal to - /// > ten times the coefficient of the divisor the coefficient of the - /// > divisor is multiplied by 10 and adjust is decremented by 1. - /// > - The result coefficient is initialized to 0. - /// > - The following steps are then repeated until the division is - /// > complete: - /// > - While the coefficient of the divisor is smaller than or equal to - /// > the coefficient of the dividend the former is subtracted from the - /// > latter and the coefficient of the result is incremented by 1. - /// > - If the coefficient of the dividend is now 0 and adjust is greater - /// > than or equal to 0, or if the coefficient of the result has - /// > precision digits, the division is complete. Otherwise, the - /// > coefficients of the result and the dividend are multiplied by 10 - /// > and adjust is incremented by 1. - /// > - Any remainder (the final coefficient of the dividend) is recorded - /// > and taken into account for rounding.[3] - /// > Otherwise (the dividend is zero), the coefficient of the result is - /// > zero and adjust is unchanged (is 0). - /// > - The exponent of the result is computed by subtracting the sum of the - /// > original exponent of the divisor and the value of adjust at the end - /// > of the coefficient calculation from the original exponent of the - /// > dividend. - /// > - The sign of the result is the exclusive or of the signs of the - /// > operands. - /// > - /// > The result is then rounded to precision digits, if necessary, according - /// > to the rounding algorithm and taking into account the remainder from - /// > the division. - function divide(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (int256, int256) - { - unchecked { - (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); - - int256 signedCoefficient = (signedCoefficientA * 1e38) / signedCoefficientB; - int256 exponent = exponentA - exponentB - 38; - return (signedCoefficient, exponent); - } + LibDecimalFloatImplementation.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (Float c, bool lossless) = packLossy(signedCoefficient, exponent); + // Multiplication is typically lossless, but can be lossy in edge cases. + (lossless); + return c; } /// Same as divide, but accepts a Float struct instead of separate values. @@ -743,18 +447,12 @@ library LibDecimalFloat { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); (int256 signedCoefficient, int256 exponent) = - divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - return pack(signedCoefficient, exponent); - } - - /// Inverts a float. Equivalent to `1 / x` with modest gas optimizations. - function inv(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); - - signedCoefficient = 1e75 / signedCoefficient; - exponent = -exponent - 75; - - return (signedCoefficient, exponent); + LibDecimalFloatImplementation.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (Float c, bool lossless) = packLossy(signedCoefficient, exponent); + // Division is often lossy because it is very easy to end up with + // infinite decimal representations. + (lossless); + return c; } /// Same as inv, but accepts a Float struct instead of separate values. @@ -764,45 +462,22 @@ library LibDecimalFloat { /// exponent of the floating point number. function inv(Float float) internal pure returns (Float) { (int256 signedCoefficient, int256 exponent) = float.unpack(); - (signedCoefficient, exponent) = inv(signedCoefficient, exponent); - return pack(signedCoefficient, exponent); - } - - /// Numeric equality for floats. - /// Two floats are equal if their numeric value is equal. - /// For example, 1e2, 10e1, and 100e0 are all equal. Also implies that 0eX - /// and 0eY are equal for all X and Y. - /// Any representable value can be equality checked without precision loss, - /// e.g. no normalization is done internally. - /// @param signedCoefficientA The signed coefficient of the first floating - /// point number. - /// @param exponentA The exponent of the first floating point number. - /// @param signedCoefficientB The signed coefficient of the second floating - /// point number. - /// @param exponentB The exponent of the second floating point number. - /// @return `true` if the two floats are equal, `false` otherwise. - function eq(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (bool) - { - (signedCoefficientA, signedCoefficientB) = - LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - - return signedCoefficientA == signedCoefficientB; + (signedCoefficient, exponent) = LibDecimalFloatImplementation.inv(signedCoefficient, exponent); + (Float result, bool lossless) = packLossy(signedCoefficient, exponent); + // Inversion cannot be lossy as long as the denominator is normalized. + (lossless); + return result; } /// Same as eq, but accepts a Float struct instead of separate values. /// Costs more gas but helps mitigate stack depth issues, and is more /// ergonomic for the caller. - /// @param a The Float struct containing the signed coefficient and - /// exponent of the first floating point number. - /// @param b The Float struct containing the signed coefficient and - /// exponent of the second floating point number. + /// @param a The first float to compare. + /// @param b The second float to compare. function eq(Float a, Float b) internal pure returns (bool) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - return eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return LibDecimalFloatImplementation.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } /// Numeric less than for floats. @@ -810,60 +485,15 @@ library LibDecimalFloat { /// For example, 1e2 is less than 1e3, and 1e2 is less than 2e2. /// Any representable value can be compared without precision loss, e.g. no /// normalization is done internally. - /// @param signedCoefficientA The signed coefficient of the first floating - /// point number. - /// @param exponentA The exponent of the first floating point number. - /// @param signedCoefficientB The signed coefficient of the second floating - /// point number. - /// @param exponentB The exponent of the second floating point number. - /// @return `true` if the first float is less than the second, `false` - /// otherwise. - function lt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (bool) - { - (signedCoefficientA, signedCoefficientB) = - LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - - return signedCoefficientA < signedCoefficientB; - } - - /// Same as lt, but accepts a Float struct instead of separate values. - /// Costs more gas but helps mitigate stack depth issues, and is more - /// ergonomic for the caller. - /// @param a The Float struct containing the signed coefficient and - /// exponent of the first floating point number. - /// @param b The Float struct containing the signed coefficient and - /// exponent of the second floating point number. + /// @param a The first float to compare. + /// @param b The second float to compare. function lt(Float a, Float b) internal pure returns (bool) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - return lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - - /// Numeric greater than for floats. - /// A float is greater than another if its numeric value is greater than the - /// other. For example, 1e3 is greater than 1e2, and 2e2 is greater than 1e2. - /// Any representable value can be compared without precision loss, e.g. no - /// normalization is done internally. - /// @param signedCoefficientA The signed coefficient of the first floating - /// point number. - /// @param exponentA The exponent of the first floating point number. - /// @param signedCoefficientB The signed coefficient of the second floating - /// point number. - /// @param exponentB The exponent of the second floating point number. - /// @return `true` if the first float is greater than the second, `false` - /// otherwise. - function gt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - internal - pure - returns (bool) - { (signedCoefficientA, signedCoefficientB) = LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - return signedCoefficientA > signedCoefficientB; + return signedCoefficientA < signedCoefficientB; } /// Numeric greater than for floats. @@ -902,60 +532,10 @@ library LibDecimalFloat { LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent); (Float result, bool lossless) = packLossy(characteristic, exponent); // Flooring is lossy by definition. - (lossless); + (lossless, mantissa); return result; } - /// 10^x for a float x. - /// - /// Internally uses log tables so is not perfectly accurate, but also doesn't - /// require any loops or iterations, and works across a wide range of - /// exponents without precision loss. - /// - /// @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 power10(address tablesDataContract, int256 signedCoefficient, int256 exponent) - internal - view - returns (int256, int256) - { - unchecked { - if (signedCoefficient < 0) { - (signedCoefficient, exponent) = minus(signedCoefficient, exponent); - (signedCoefficient, exponent) = power10(tablesDataContract, signedCoefficient, exponent); - return inv(signedCoefficient, exponent); - } - - // Table lookup. - (int256 characteristicCoefficient, int256 mantissaCoefficient) = - LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent); - int256 characteristicExponent = exponent; - { - (int256 idx, bool interpolate) = LibDecimalFloatImplementation.mantissa4(mantissaCoefficient, exponent); - (int256 y1Coefficient, int256 y2Coefficient) = - LibDecimalFloatImplementation.lookupAntilogTableY1Y2(tablesDataContract, uint256(idx), interpolate); - - if (interpolate) { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.unitLinearInterpolation( - mantissaCoefficient, exponent, idx, -4, -41, y1Coefficient, y2Coefficient, -4 - ); - } else { - signedCoefficient = y1Coefficient; - exponent = -4; - } - } - - return ( - signedCoefficient, - 1 + exponent - + LibDecimalFloatImplementation.withTargetExponent(characteristicCoefficient, characteristicExponent, 0) - ); - } - } - /// Same as power10, but accepts a Float struct instead of separate values. /// Costs more gas but helps mitigate stack depth issues, and is more /// ergonomic for the caller. @@ -965,7 +545,8 @@ library LibDecimalFloat { /// exponent of the floating point number. function power10(address tablesDataContract, Float float) internal view returns (Float) { (int256 signedCoefficient, int256 exponent) = float.unpack(); - (signedCoefficient, exponent) = power10(tablesDataContract, signedCoefficient, exponent); + (signedCoefficient, exponent) = + LibDecimalFloatImplementation.power10(tablesDataContract, signedCoefficient, exponent); (Float result, bool lossless) = packLossy(signedCoefficient, exponent); // We don't care if power10 is lossy because it's an approximation // anyway. @@ -973,126 +554,16 @@ library LibDecimalFloat { return result; } - /// log10(x) for a float x. - /// - /// Internally uses log tables so is not perfectly accurate, but also doesn't - /// require any loops or iterations, and works across a wide range of - /// exponents without precision loss. - /// - /// @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 log10(address tablesDataContract, int256 signedCoefficient, int256 exponent) - internal - view - returns (int256, int256) - { - unchecked { - { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); - - if (signedCoefficient <= 0) { - if (signedCoefficient == 0) { - revert Log10Zero(); - } else { - revert Log10Negative(signedCoefficient, exponent); - } - } - } - - // This is a positive log. i.e. log(x) where x >= 1. - if (exponent > -38) { - // This is an exact power of 10. - if (signedCoefficient == 1e37) { - return (exponent + 37, 0); - } - - int256 y1Coefficient; - int256 y2Coefficient; - int256 x1Coefficient; - int256 x1Exponent = exponent; - bool interpolate; - - // Table lookup. - { - uint256 scale = 1e34; - assembly ("memory-safe") { - //slither-disable-next-line divide-before-multiply - function lookupTableVal(tables, index) -> result { - // First byte of the data contract must be skipped. - let mainOffset := add(1, mul(div(index, 10), 2)) - mstore(0, 0) - extcodecopy(tables, 30, mainOffset, 2) - let mainTableVal := mload(0) - - result := and(mainTableVal, 0x7FFF) - // Skip first byte of data contract then 1800 bytes - // of the log tables. - let smallTableOffset := 1801 - if iszero(iszero(and(mainTableVal, 0x8000))) { - // Small table is half the size of the main - // table. - smallTableOffset := add(smallTableOffset, 900) - } - - mstore(0, 0) - extcodecopy( - tables, 31, add(smallTableOffset, add(mul(div(index, 100), 10), mod(index, 10))), 1 - ) - result := add(result, mload(0)) - } - - // Truncate the signed coefficient to what we can look - // up in the table. - // Slither false positive because the truncation is - // deliberate here. - //slither-disable-next-line divide-before-multiply - x1Coefficient := div(signedCoefficient, scale) - let index := sub(x1Coefficient, 1000) - x1Coefficient := mul(x1Coefficient, scale) - interpolate := iszero(eq(x1Coefficient, signedCoefficient)) - - y1Coefficient := mul(scale, lookupTableVal(tablesDataContract, index)) - - if interpolate { - y2Coefficient := mul(scale, lookupTableVal(tablesDataContract, add(index, 1))) - } - } - } - - if (interpolate) { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.unitLinearInterpolation( - signedCoefficient, exponent, x1Coefficient, exponent, -39, y1Coefficient, y2Coefficient, -38 - ); - } else { - signedCoefficient = y1Coefficient; - exponent = -38; - } - - return add(signedCoefficient, exponent, x1Exponent + 37, 0); - } - // This is a negative log. i.e. log(x) where 0 < x < 1. - // log(x) = -log(1/x) - else { - (signedCoefficient, exponent) = divide(1e37, -37, signedCoefficient, exponent); - (signedCoefficient, exponent) = log10(tablesDataContract, signedCoefficient, exponent); - return minus(signedCoefficient, exponent); - } - } - } - /// Same as log10, but accepts a Float struct instead of separate values. /// Costs more gas but helps mitigate stack depth issues, and is more /// ergonomic for the caller. /// @param tablesDataContract The address of the contract containing the /// logarithm tables. - /// @param float The Float struct containing the signed coefficient and - /// exponent of the floating point number. - function log10(address tablesDataContract, Float a) internal view returns (Float) { + /// @param a The float to log10. + function log10(Float a, address tablesDataContract) internal view returns (Float) { (int256 signedCoefficient, int256 exponent) = a.unpack(); - (signedCoefficient, exponent) = log10(tablesDataContract, signedCoefficient, exponent); + (signedCoefficient, exponent) = + LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficient, exponent); (Float result, bool lossless) = packLossy(signedCoefficient, exponent); // We don't care if log10 is lossy because it's an approximation anyway. (lossless); @@ -1108,16 +579,19 @@ library LibDecimalFloat { /// /// Doesn't lose precision due to the exponent, for a wide range of /// exponents. - /// @param tablesDataContract The address of the contract containing the - /// logarithm tables. /// @param a The float `a` in `a^b`. /// @param b The float `b` in `a^b`. - function power(address tablesDataContract, Float a, Float b) internal view returns (Float) { + /// @param tablesDataContract The address of the contract containing the + /// logarithm tables. + function power(Float a, Float b, address tablesDataContract) internal view returns (Float) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - (int256 signedCoefficientC, int256 exponentC) = log10(tablesDataContract, signedCoefficientA, exponentA); + (int256 signedCoefficientC, int256 exponentC) = + LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficientA, exponentA); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - (signedCoefficientC, exponentC) = multiply(signedCoefficientC, exponentC, signedCoefficientB, exponentB); - (signedCoefficientC, exponentC) = power10(tablesDataContract, signedCoefficientC, exponentC); + (signedCoefficientC, exponentC) = + LibDecimalFloatImplementation.multiply(signedCoefficientC, exponentC, signedCoefficientB, exponentB); + (signedCoefficientC, exponentC) = + LibDecimalFloatImplementation.power10(tablesDataContract, signedCoefficientC, exponentC); (Float c, bool lossless) = packLossy(signedCoefficientC, exponentC); // We don't care if power is lossy because it's an approximation anyway. (lossless); @@ -1140,16 +614,4 @@ library LibDecimalFloat { function max(Float a, Float b) internal pure returns (Float) { return gt(a, b) ? a : b; } - - // /// Same as normalize, but accepts a Float struct instead of separate values. - // /// Costs more gas but helps mitigate stack depth issues, and is more - // /// ergonomic for the caller. - // /// @param float The Float struct containing the signed coefficient and - // /// exponent of the floating point number. - // function normalize(Float float) internal pure returns (Float) { - // (int256 signedCoefficient, int256 exponent) = float.unpack(); - // (int256 signedCoefficientNormalized, int256 exponentNormalized) = - // LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); - // return pack(signedCoefficientNormalized, exponentNormalized); - // } } diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index dbbca078..a1fd3090 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity ^0.8.25; -import {ExponentOverflow} from "../../error/ErrDecimalFloat.sol"; +import {ExponentOverflow, Log10Negative, Log10Zero} from "../../error/ErrDecimalFloat.sol"; import { LOG_TABLES, LOG_TABLES_SMALL, @@ -13,6 +13,8 @@ import {LibDecimalFloat} from "../LibDecimalFloat.sol"; error WithTargetExponentOverflow(int256 signedCoefficient, int256 exponent, int256 targetExponent); +uint256 constant ADD_MAX_EXPONENT_DIFF = 37; + /// @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. @@ -55,6 +57,490 @@ int256 constant NORMALIZED_ZERO_SIGNED_COEFFICIENT = 0; int256 constant NORMALIZED_ZERO_EXPONENT = 0; library LibDecimalFloatImplementation { + /// 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. + /// > + /// > The operations are evaluated using the same rules as add and subtract; + /// > the operations plus(a) and minus(a) + /// > (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) { + unchecked { + // This is the only edge case that can't be simply negated. + if (signedCoefficient == type(int256).min) { + if (exponent == type(int256).max) { + revert ExponentOverflow(signedCoefficient, exponent); + } + signedCoefficient /= 10; + ++exponent; + } + return (-signedCoefficient, exponent); + } + } + + /// https://speleotrove.com/decimal/daops.html#refmult + /// > multiply takes two operands. If either operand is a special value then + /// > the general rules apply. + /// > + /// > Otherwise, the operands are multiplied together + /// > (‘long multiplication’), resulting in a number which may be as long as + /// > the sum of the lengths of the two operands, as follows: + /// > + /// > - The coefficient of the result, before rounding, is computed by + /// > multiplying together the coefficients of the operands. + /// > - The exponent of the result, before rounding, is the sum of the + /// > exponents of the two operands. + /// > - The sign of the result is the exclusive or of the signs of the + /// > operands. + /// > + /// > The result is then rounded to precision digits if necessary, counting + /// > from the most significant digit of the result. + function multiply(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (int256, int256) + { + unchecked { + // Unchecked mul the coefficients and add the exponents. + int256 signedCoefficient = signedCoefficientA * signedCoefficientB; + + // Need to return early if the result is zero to avoid divide by + // zero in the overflow check. + if (signedCoefficient == 0) { + return (NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT); + } + + int256 exponent = exponentA + exponentB; + + // No jumps to see if we overflowed. + bool didOverflow; + assembly ("memory-safe") { + didOverflow := + or( + iszero(eq(sdiv(signedCoefficient, signedCoefficientA), signedCoefficientB)), + iszero(eq(sub(exponent, exponentA), exponentB)) + ) + } + // If we did overflow, normalize and try again. Normalized values + // cannot overflow, so this will always succeed, provided the + // exponents are not out of bounds. + if (didOverflow) { + (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + return multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + return (signedCoefficient, exponent); + } + } + + /// https://speleotrove.com/decimal/daops.html#refdivide + /// > divide takes two operands. If either operand is a special value then + /// > the general rules apply. + /// > Otherwise, if the divisor is zero then either the Division undefined + /// > condition is raised (if the dividend is zero) and the result is NaN, + /// > or the Division by zero condition is raised and the result is an + /// > Infinity with a sign which is the exclusive or of the signs of the + /// > operands. + /// > + /// > Otherwise, a ‘long division’ is effected, as follows: + /// > + /// > - An integer variable, adjust, is initialized to 0. + /// > - If the dividend is non-zero, the coefficient of the result is + /// > computed as follows (using working copies of the operand + /// > coefficients, as necessary): + /// > - The operand coefficients are adjusted so that the coefficient of + /// > the dividend is greater than or equal to the coefficient of the + /// > divisor and is also less than ten times the coefficient of the + /// > divisor, thus: + /// > - While the coefficient of the dividend is less than the + /// > coefficient of the divisor it is multiplied by 10 and adjust is + /// > incremented by 1. + /// > - While the coefficient of the dividend is greater than or equal to + /// > ten times the coefficient of the divisor the coefficient of the + /// > divisor is multiplied by 10 and adjust is decremented by 1. + /// > - The result coefficient is initialized to 0. + /// > - The following steps are then repeated until the division is + /// > complete: + /// > - While the coefficient of the divisor is smaller than or equal to + /// > the coefficient of the dividend the former is subtracted from the + /// > latter and the coefficient of the result is incremented by 1. + /// > - If the coefficient of the dividend is now 0 and adjust is greater + /// > than or equal to 0, or if the coefficient of the result has + /// > precision digits, the division is complete. Otherwise, the + /// > coefficients of the result and the dividend are multiplied by 10 + /// > and adjust is incremented by 1. + /// > - Any remainder (the final coefficient of the dividend) is recorded + /// > and taken into account for rounding.[3] + /// > Otherwise (the dividend is zero), the coefficient of the result is + /// > zero and adjust is unchanged (is 0). + /// > - The exponent of the result is computed by subtracting the sum of the + /// > original exponent of the divisor and the value of adjust at the end + /// > of the coefficient calculation from the original exponent of the + /// > dividend. + /// > - The sign of the result is the exclusive or of the signs of the + /// > operands. + /// > + /// > The result is then rounded to precision digits, if necessary, according + /// > to the rounding algorithm and taking into account the remainder from + /// > the division. + function divide(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (int256, int256) + { + unchecked { + (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + + int256 signedCoefficient = (signedCoefficientA * 1e38) / signedCoefficientB; + int256 exponent = exponentA - exponentB - 38; + return (signedCoefficient, exponent); + } + } + + /// Add two floats together as a normalized result. + /// + /// Note that because the input values can have arbitrary exponents that may + /// be very far apart, the normalization process is necessarily lossy. + /// For example, normalized 1 is 1e37 coefficient and -37 exponent. + /// Consider adding 1e37 coefficient with exponent 1. + /// These two numbers are identical in coefficient but their exponents are + /// 38 OOMs apart. While we can perform the addition and get the correct + /// result internally, as soon as we normalize the result, we will lose + /// precision and the result will be 1e37 coefficient with -37 exponent. + /// The precision of addition is therefore best case the full 37 decimals + /// representable in normalized form, if the two numbers share the same + /// exponent, but each step of exponent difference will lose a decimal of + /// precision in the output. In practise, this rarely matters as the onchain + /// conventions for amounts are typically 18 decimals or less, and so entire + /// token supplies are typically representable within ~26-33 decimals of + /// precision, making addition lossless for all actual possible values. + /// + /// https://speleotrove.com/decimal/daops.html#refaddsub + /// > add and subtract both take two operands. If either operand is a special + /// > value then the general rules apply. + /// > + /// > Otherwise, the operands are added (after inverting the sign used for + /// > the second operand if the operation is a subtraction), as follows: + /// > + /// > The coefficient of the result is computed by adding or subtracting the + /// > aligned coefficients of the two operands. The aligned coefficients are + /// > computed by comparing the exponents of the operands: + /// > + /// > - If they have the same exponent, the aligned coefficients are the same + /// > as the original coefficients. + /// > - Otherwise the aligned coefficient of the number with the larger + /// > exponent is its original coefficient multiplied by 10^n, where n is the + /// > absolute difference between the exponents, and the aligned coefficient + /// > of the other operand is the same as its original coefficient. + /// > + /// > If the signs of the operands differ then the smaller aligned + /// > coefficient is subtracted from the larger; otherwise they are added. + /// > + /// > The exponent of the result is the minimum of the exponents of the two + /// > operands. + /// > + /// > The sign of the result is determined as follows: + /// > + /// > - If the result is non-zero then the sign of the result is the sign of + /// > the operand having the larger absolute value. + /// > - Otherwise, the sign of a zero result is 0 unless either both operands + /// > were negative or the signs of the operands were different and the + /// > rounding is round-floor. + /// + /// @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 add(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (int256, int256) + { + // Zero for either is the edge case but we have to guard against it. + // Doing it eagerly with assembly is less gas than lazily with jumps. + bool eitherZero; + assembly ("memory-safe") { + eitherZero := or(iszero(signedCoefficientA), iszero(signedCoefficientB)) + } + if (eitherZero) { + if (signedCoefficientA == 0) { + return (signedCoefficientB, exponentB); + } else { + return (signedCoefficientA, exponentA); + } + } + + // Normalizing A and B gives us similar coefficients, which simplifies + // detecting when their exponents are too far apart to add without + // simply ignoring one of them. + (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + + // We want A to represent the larger exponent. If this is not the case + // then swap them. + if (exponentB > exponentA) { + int256 tmp = signedCoefficientA; + signedCoefficientA = signedCoefficientB; + signedCoefficientB = tmp; + + tmp = exponentA; + exponentA = exponentB; + exponentB = tmp; + } + + // After normalization the signed coefficients are the same OOM in + // magnitude. However, what we need is for the exponents to be the same. + // If the exponents are close enough we can multiply coefficient A by + // some power of 10 to align their exponents without precision loss. + // If the exponents are too far apart, then all the information in B + // would be lost by the final normalization step, so we can just ignore + // B and return A. + uint256 multiplier; + unchecked { + uint256 alignmentExponentDiff = uint256(exponentA - exponentB); + // 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); + } + multiplier = 10 ** alignmentExponentDiff; + } + signedCoefficientA *= int256(multiplier); + + // The actual addition step. + unchecked { + signedCoefficientA += signedCoefficientB; + } + return (signedCoefficientA, exponentB); + } + + /// @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 + returns (int256, int256) + { + (signedCoefficientB, exponentB) = minus(signedCoefficientB, exponentB); + return add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + + /// Numeric equality for floats. + /// Two floats are equal if their numeric value is equal. + /// For example, 1e2, 10e1, and 100e0 are all equal. Also implies that 0eX + /// and 0eY are equal for all X and Y. + /// Any representable value can be equality checked without precision loss, + /// e.g. no normalization is done internally. + /// @param signedCoefficientA The signed coefficient of the first floating + /// point number. + /// @param exponentA The exponent of the first floating point number. + /// @param signedCoefficientB The signed coefficient of the second floating + /// point number. + /// @param exponentB The exponent of the second floating point number. + /// @return `true` if the two floats are equal, `false` otherwise. + function eq(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + pure + returns (bool) + { + (signedCoefficientA, signedCoefficientB) = + LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + return signedCoefficientA == signedCoefficientB; + } + + /// Inverts a float. Equivalent to `1 / x` with modest gas optimizations. + function inv(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { + (signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); + + signedCoefficient = 1e75 / signedCoefficient; + exponent = -exponent - 75; + + return (signedCoefficient, exponent); + } + + /// log10(x) for a float x. + /// + /// Internally uses log tables so is not perfectly accurate, but also doesn't + /// require any loops or iterations, and works across a wide range of + /// exponents without precision loss. + /// + /// @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 log10(address tablesDataContract, int256 signedCoefficient, int256 exponent) + internal + view + returns (int256, int256) + { + unchecked { + { + (signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); + + if (signedCoefficient <= 0) { + if (signedCoefficient == 0) { + revert Log10Zero(); + } else { + revert Log10Negative(signedCoefficient, exponent); + } + } + } + + // This is a positive log. i.e. log(x) where x >= 1. + if (exponent > -38) { + // This is an exact power of 10. + if (signedCoefficient == 1e37) { + return (exponent + 37, 0); + } + + int256 y1Coefficient; + int256 y2Coefficient; + int256 x1Coefficient; + int256 x1Exponent = exponent; + bool interpolate; + + // Table lookup. + { + uint256 scale = 1e34; + assembly ("memory-safe") { + //slither-disable-next-line divide-before-multiply + function lookupTableVal(tables, index) -> result { + // First byte of the data contract must be skipped. + let mainOffset := add(1, mul(div(index, 10), 2)) + mstore(0, 0) + extcodecopy(tables, 30, mainOffset, 2) + let mainTableVal := mload(0) + + result := and(mainTableVal, 0x7FFF) + // Skip first byte of data contract then 1800 bytes + // of the log tables. + let smallTableOffset := 1801 + if iszero(iszero(and(mainTableVal, 0x8000))) { + // Small table is half the size of the main + // table. + smallTableOffset := add(smallTableOffset, 900) + } + + mstore(0, 0) + extcodecopy( + tables, 31, add(smallTableOffset, add(mul(div(index, 100), 10), mod(index, 10))), 1 + ) + result := add(result, mload(0)) + } + + // Truncate the signed coefficient to what we can look + // up in the table. + // Slither false positive because the truncation is + // deliberate here. + //slither-disable-next-line divide-before-multiply + x1Coefficient := div(signedCoefficient, scale) + let index := sub(x1Coefficient, 1000) + x1Coefficient := mul(x1Coefficient, scale) + interpolate := iszero(eq(x1Coefficient, signedCoefficient)) + + y1Coefficient := mul(scale, lookupTableVal(tablesDataContract, index)) + + if interpolate { + y2Coefficient := mul(scale, lookupTableVal(tablesDataContract, add(index, 1))) + } + } + } + + if (interpolate) { + (signedCoefficient, exponent) = LibDecimalFloatImplementation.unitLinearInterpolation( + signedCoefficient, exponent, x1Coefficient, exponent, -39, y1Coefficient, y2Coefficient, -38 + ); + } else { + signedCoefficient = y1Coefficient; + exponent = -38; + } + + return add(signedCoefficient, exponent, x1Exponent + 37, 0); + } + // This is a negative log. i.e. log(x) where 0 < x < 1. + // log(x) = -log(1/x) + else { + (signedCoefficient, exponent) = divide(1e37, -37, signedCoefficient, exponent); + (signedCoefficient, exponent) = log10(tablesDataContract, signedCoefficient, exponent); + return minus(signedCoefficient, exponent); + } + } + } + + /// 10^x for a float x. + /// + /// Internally uses log tables so is not perfectly accurate, but also doesn't + /// require any loops or iterations, and works across a wide range of + /// exponents without precision loss. + /// + /// @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 power10(address tablesDataContract, int256 signedCoefficient, int256 exponent) + internal + view + returns (int256, int256) + { + unchecked { + if (signedCoefficient < 0) { + (signedCoefficient, exponent) = minus(signedCoefficient, exponent); + (signedCoefficient, exponent) = power10(tablesDataContract, signedCoefficient, exponent); + return inv(signedCoefficient, exponent); + } + + // Table lookup. + (int256 characteristicCoefficient, int256 mantissaCoefficient) = + LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent); + int256 characteristicExponent = exponent; + { + (int256 idx, bool interpolate) = LibDecimalFloatImplementation.mantissa4(mantissaCoefficient, exponent); + (int256 y1Coefficient, int256 y2Coefficient) = + LibDecimalFloatImplementation.lookupAntilogTableY1Y2(tablesDataContract, uint256(idx), interpolate); + + if (interpolate) { + (signedCoefficient, exponent) = LibDecimalFloatImplementation.unitLinearInterpolation( + mantissaCoefficient, exponent, idx, -4, -41, y1Coefficient, y2Coefficient, -4 + ); + } else { + signedCoefficient = y1Coefficient; + exponent = -4; + } + } + + return ( + signedCoefficient, + 1 + exponent + + LibDecimalFloatImplementation.withTargetExponent(characteristicCoefficient, characteristicExponent, 0) + ); + } + } + function isNormalized(int256 signedCoefficient, int256 exponent) internal pure returns (bool) { bool result; uint256 normalizedMaxPlusOne = NORMALIZED_MAX_PLUS_ONE; @@ -359,25 +845,23 @@ library LibDecimalFloatImplementation { { // x - x1 - (int256 xDiffCoefficient, int256 xDiffExponent) = - LibDecimalFloat.sub(xCoefficient, xExponent, x1Coefficient, x1Exponent); + (int256 xDiffCoefficient, int256 xDiffExponent) = sub(xCoefficient, xExponent, x1Coefficient, x1Exponent); // y2 - y1 - (int256 yDiffCoefficient, int256 yDiffExponent) = - LibDecimalFloat.sub(y2Coefficient, yExponent, y1Coefficient, yExponent); + (int256 yDiffCoefficient, int256 yDiffExponent) = sub(y2Coefficient, yExponent, y1Coefficient, yExponent); // (x - x1) * (y2 - y1) (numeratorSignedCoefficient, numeratorExponent) = - LibDecimalFloat.multiply(xDiffCoefficient, xDiffExponent, yDiffCoefficient, yDiffExponent); + multiply(xDiffCoefficient, xDiffExponent, yDiffCoefficient, yDiffExponent); } // Diff between x2 and x1 is always 1 unit. (int256 yMarginalSignedCoefficient, int256 yMarginalExponent) = - LibDecimalFloat.divide(numeratorSignedCoefficient, numeratorExponent, 1e37, xUnitExponent); + divide(numeratorSignedCoefficient, numeratorExponent, 1e37, xUnitExponent); // y1 + ((x - x1) * (y2 - y1)) / (x2 - x1) (int256 signedCoefficient, int256 exponent) = - LibDecimalFloat.add(yMarginalSignedCoefficient, yMarginalExponent, y1Coefficient, yExponent); + add(yMarginalSignedCoefficient, yMarginalExponent, y1Coefficient, yExponent); return (signedCoefficient, exponent); } } diff --git a/src/lib/parse/LibParseDecimalFloat.sol b/src/lib/parse/LibParseDecimalFloat.sol index 0fb95e6e..74f02031 100644 --- a/src/lib/parse/LibParseDecimalFloat.sol +++ b/src/lib/parse/LibParseDecimalFloat.sol @@ -24,7 +24,7 @@ library LibParseDecimalFloat { return (errorSelector, cursor, Float.wrap(0)); } - return (0, cursor, LibDecimalFloat.pack(signedCoefficient, exponent)); + return (0, cursor, LibDecimalFloat.packLossless(signedCoefficient, exponent)); } function parseDecimalFloat(uint256 start, uint256 end) @@ -135,6 +135,6 @@ library LibParseDecimalFloat { (bytes4 errorSelector, uint256 cursor, int256 signedCoefficient, int256 exponent) = parseDecimalFloat(start, end); (cursor); - return (errorSelector, LibDecimalFloat.pack(signedCoefficient, exponent)); + return (errorSelector, LibDecimalFloat.packLossless(signedCoefficient, exponent)); } } diff --git a/test/abstract/LogTest.sol b/test/abstract/LogTest.sol index 7b1f0a11..2728854c 100644 --- a/test/abstract/LogTest.sol +++ b/test/abstract/LogTest.sol @@ -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"; diff --git a/test/lib/LibDecimalFloatSlow.sol b/test/lib/LibDecimalFloatSlow.sol index e2b1f975..7de77264 100644 --- a/test/lib/LibDecimalFloatSlow.sol +++ b/test/lib/LibDecimalFloatSlow.sol @@ -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 @@ -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) diff --git a/test/src/lib/LibDecimalFloat.abs.t.sol b/test/src/lib/LibDecimalFloat.abs.t.sol index e6fc2bb0..afd9ad63 100644 --- a/test/src/lib/LibDecimalFloat.abs.t.sol +++ b/test/src/lib/LibDecimalFloat.abs.t.sol @@ -8,46 +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 float) external pure returns (Float) { - return LibDecimalFloat.abs(float); - } - - function testAbsPacked(Float float) external { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.unpack(float); - try this.absExternal(signedCoefficient, exponent) returns (int256 signedCoefficientAbs, int256 exponentAbs) { - Float floatAbs = this.absExternal(float); - assertEq(Float.unwrap(floatAbs), Float.unwrap(LibDecimalFloat.pack(signedCoefficientAbs, exponentAbs))); - } 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); } } diff --git a/test/src/lib/LibDecimalFloat.add.t.sol b/test/src/lib/LibDecimalFloat.add.t.sol index fd755850..f1ff7754 100644 --- a/test/src/lib/LibDecimalFloat.add.t.sol +++ b/test/src/lib/LibDecimalFloat.add.t.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, ADD_MAX_EXPONENT_DIFF, Float} from "src/lib/LibDecimalFloat.sol"; -import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; +import { + LibDecimalFloatImplementation, + ADD_MAX_EXPONENT_DIFF +} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {Test} from "forge-std/Test.sol"; @@ -14,7 +17,7 @@ contract LibDecimalFloatDecimalAddTest is Test { pure returns (int256, int256) { - return LibDecimalFloat.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } function addExternal(Float a, Float b) external pure returns (Float) { @@ -27,235 +30,13 @@ contract LibDecimalFloatDecimalAddTest is Test { try this.addExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - Float resultMem = a.add(b); - assertEq(Float.unwrap(resultMem), Float.unwrap(LibDecimalFloat.pack(signedCoefficient, exponent))); + Float result = this.addExternal(a, b); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = LibDecimalFloat.unpack(result); + assertEq(signedCoefficient, signedCoefficientUnpacked); + assertEq(exponent, exponentUnpacked); } catch (bytes memory err) { vm.expectRevert(err); - a.add(b); + this.addExternal(a, b); } } - - function testAddUnpacked(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - { - try this.addExternal( - LibDecimalFloat.pack(signedCoefficientA, exponentA), LibDecimalFloat.pack(signedCoefficientB, exponentB) - ) returns (Float resultPacked) { - (int256 signedCoefficient, int256 exponent) = - this.addExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - assertEq(Float.unwrap(resultPacked), Float.unwrap(LibDecimalFloat.pack(signedCoefficient, exponent))); - } catch (bytes memory err) { - vm.expectRevert(err); - this.addExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - } - - /// Simple 0 add 0 - /// 0 + 0 = 0 - function testAddZero() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(0, 0, 0, 0); - assertEq(signedCoefficient, 0); - assertEq(exponent, 0); - } - - /// 0 add 0 any exponent - /// 0 + 0 = 0 - function testAddZeroAnyExponent(int128 inputExponent) external pure { - inputExponent = int128(bound(inputExponent, EXPONENT_MIN, EXPONENT_MAX)); - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(0, inputExponent, 0, 0); - assertEq(signedCoefficient, 0); - assertEq(exponent, 0); - } - - /// 0 add 1 - /// 0 + 1 = 1 - function testAddZeroOne() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(0, 0, 1, 0); - assertEq(signedCoefficient, 1); - assertEq(exponent, 0); - } - - /// 1 add 0 - /// 1 + 0 = 1 - function testAddOneZero() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(1, 0, 0, 0); - assertEq(signedCoefficient, 1); - assertEq(exponent, 0); - } - - /// 1 add 1 - /// 1 + 1 = 2 - function testAddOneOneNotNormalized() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(1, 0, 1, 0); - assertEq(signedCoefficient, 2e37); - assertEq(exponent, -37); - } - - function testAddOneOnePreNormalized() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(1e37, -37, 1e37, -37); - assertEq(signedCoefficient, 2e37); - assertEq(exponent, -37); - } - - /// 123456789 add 987654321 - /// 123456789 + 987654321 = 1111111110 - function testAdd123456789987654321() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(123456789, 0, 987654321, 0); - assertEq(signedCoefficient, 1.11111111e38); - assertEq(exponent, -38 + 9); - } - - /// 123456789e9 add 987654321 - /// 123456789e9 + 987654321 = 123456789987654321 - function testAdd123456789e9987654321() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(123456789, 9, 987654321, 0); - assertEq(signedCoefficient, 1.23456789987654321e46); - assertEq(exponent, -46 + 17); - } - - function testGasAddZero() external pure { - LibDecimalFloat.add(0, 0, 0, 0); - } - - function testGasAddOne() external pure { - LibDecimalFloat.add(1e37, -37, 1e37, -37); - } - - /// Provided our exponents are in range we should never revert. - function testAddNeverRevert( - 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); - - (int256 signedCoefficient, int256 exponent) = - LibDecimalFloat.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - (signedCoefficient, exponent); - } - - function testAddingSmallToLargeReturnsLargeFuzz( - int256 signedCoefficientA, - int256 exponentA, - int256 signedCoefficientB, - int256 exponentB - ) public pure { - exponentA = bound(exponentA, EXPONENT_MIN / 10, EXPONENT_MAX / 10); - exponentB = bound(exponentB, EXPONENT_MIN / 10, EXPONENT_MAX / 10); - vm.assume(signedCoefficientA != 0); - vm.assume(signedCoefficientB != 0); - - (int256 normalizedSignedCoefficientA, int256 normalizedExponentA) = - LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); - (int256 expectedSignedCoefficient, int256 expectedExponent) = - LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); - - vm.assume(normalizedSignedCoefficientA != 0); - vm.assume(expectedSignedCoefficient != 0); - - vm.assume((expectedExponent - normalizedExponentA) > int256(ADD_MAX_EXPONENT_DIFF)); - - (int256 signedCoefficient, int256 exponent) = - LibDecimalFloat.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - assertEq(signedCoefficient, expectedSignedCoefficient); - assertEq(exponent, expectedExponent); - } - - function testAddingSmallToLargeReturnsLargeExamples() external pure { - // Establish a baseline. - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.add(1e37, 0, 1e37, -37); - assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); - assertEq(exponent, -37); - // Show baseline with reversed order. - (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -37, 1e37, 0); - assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); - assertEq(exponent, -37); - - // Show full precision loss. - (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, 0, 1e37, -38); - assertEq(signedCoefficient, 1e37); - assertEq(exponent, 0); - // Show same thing again with reversed order. - (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -38, 1e37, 0); - assertEq(signedCoefficient, 1e37); - assertEq(exponent, 0); - - // Same precision loss happens for negative numbers. - (signedCoefficient, exponent) = LibDecimalFloat.add(-1e37, 0, -1e37, -38); - assertEq(signedCoefficient, -1e37); - assertEq(exponent, 0); - // Reverse order. - (signedCoefficient, exponent) = LibDecimalFloat.add(-1e37, -38, -1e37, 0); - assertEq(signedCoefficient, -1e37); - assertEq(exponent, 0); - - // Only the difference in exponents matters. Show the baseline. - (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -20, 1e37, -57); - assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); - assertEq(exponent, -57); - // Reverse order. - (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -57, 1e37, -20); - assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); - assertEq(exponent, -57); - - // Only the difference in exponents matters. - (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -20, 1e37, -58); - assertEq(signedCoefficient, 1e37); - assertEq(exponent, -20); - // Reverse order. - (signedCoefficient, exponent) = LibDecimalFloat.add(1e37, -58, 1e37, -20); - assertEq(signedCoefficient, 1e37); - - // Only the difference in exponents matters. Show negative numbers. - (signedCoefficient, exponent) = LibDecimalFloat.add(-1e37, -20, -1e37, -58); - assertEq(signedCoefficient, -1e37); - assertEq(exponent, -20); - // Reverse order. - (signedCoefficient, exponent) = LibDecimalFloat.add(-1e37, -58, -1e37, -20); - assertEq(signedCoefficient, -1e37); - } - - /// If the exponents are the same and the coefficients are the same, then - /// addition is simply adding the coefficients. - function testAddSameExponentSameCoefficient(int256 signedCoefficientA, int256 signedCoefficientB) external pure { - int256 exponentA; - int256 exponentB; - (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, 0); - (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, 0); - - if (signedCoefficientA == 0 || signedCoefficientB == 0) { - exponentA = 0; - } - exponentB = exponentA; - - (int256 signedCoefficient, int256 exponent) = - LibDecimalFloat.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - - int256 expectedSignedCoefficient = signedCoefficientA + signedCoefficientB; - int256 expectedExponent = exponentA; - - assertEq(signedCoefficient, expectedSignedCoefficient); - assertEq(exponent, expectedExponent); - } - - /// Adding any zero to any value returns the non-zero value. - function testAddZeroToAnyNonZero(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); - - vm.assume(signedCoefficient != 0); - - (int256 expectedSignedCoefficient, int256 expectedExponent) = (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); - } } diff --git a/test/src/lib/LibDecimalFloat.divide.t.sol b/test/src/lib/LibDecimalFloat.divide.t.sol index 780184d4..5f5b4c25 100644 --- a/test/src/lib/LibDecimalFloat.divide.t.sol +++ b/test/src/lib/LibDecimalFloat.divide.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {THREES, ONES} from "../../lib/LibCommonResults.sol"; -import {LibDecimalFloat, Float, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {Test} from "forge-std/Test.sol"; @@ -14,7 +14,7 @@ contract LibDecimalFloatDivideTest is Test { pure returns (int256, int256) { - return LibDecimalFloat.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return LibDecimalFloatImplementation.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } function divideExternal(Float floatA, Float floatB) external pure returns (Float) { @@ -36,118 +36,4 @@ contract LibDecimalFloatDivideTest is Test { this.divideExternal(a, b); } } - - function checkDivision( - int256 signedCoefficientA, - int256 exponentA, - int256 signedCoefficientB, - int256 exponentB, - int256 signedCoefficientC, - int256 exponentC - ) internal pure { - (int256 signedCoefficient, int256 exponent) = - LibDecimalFloat.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - assertEq(signedCoefficient, signedCoefficientC, "coefficient"); - assertEq(exponent, exponentC, "exponent"); - } - - /// 1 / 3 - function testDivide1Over3() external pure { - checkDivision(1, 0, 3, 0, THREES, -38); - } - - /// - 1 / 3 - function testDivideNegative1Over3() external pure { - checkDivision(-1, 0, 3, 0, -THREES, -38); - } - - /// 1 / 3 gas - function testDivide1Over3Gas0() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.divide(1e37, -37, 3e37, -37); - (signedCoefficient, exponent); - } - - /// 1 / 3 gas by parts 10 - function testDivide1Over3Gas10() external pure { - (int256 c, int256 e) = LibDecimalFloat.divide(1, 0, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - (c, e) = LibDecimalFloat.divide(c, e, 3e37, -37); - } - - /// 1e18 / 3 - function testDivide1e18Over3() external pure { - checkDivision(1e18, 0, 3, 0, THREES, -20); - } - - /// 10,0 / 1e38,-37 == 1 - function testDivideTenOverOOMs() external pure { - checkDivision(10, 0, 1e38, -37, 1e38, -38); - } - - /// 1e38,-37 / 2,0 == 5 - function testDivideOOMsOverTen() external pure { - checkDivision(1e38, -37, 2, 0, 5e37, -37); - } - - /// 5e37,-37 / 2e37,-37 == 2.5 - function testDivideOOMs5and2() external pure { - checkDivision(5e37, -37, 2e37, -37, 25e37, -38); - } - - /// (1 / 9) / (1 / 3) == 0.333.. - function testDivide1Over9Over1Over3() external pure { - // 1 / 9 - (int256 signedCoefficientA, int256 exponentA) = LibDecimalFloat.divide(1, 0, 9, 0); - assertEq(signedCoefficientA, ONES); - assertEq(exponentA, -38); - - // 1 / 3 - (int256 signedCoefficientB, int256 exponentB) = LibDecimalFloat.divide(1, 0, 3, 0); - assertEq(signedCoefficientB, THREES); - assertEq(exponentB, -38); - - // (1 / 9) / (1 / 3) - (int256 signedCoefficient, int256 exponent) = - LibDecimalFloat.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - assertEq(signedCoefficient, THREES); - assertEq(exponent, -38); - } - - /// forge-config: default.fuzz.runs = 100 - function testUnnormalizedThreesDivision0(int256 exponentA, int256 exponentB) external pure { - exponentA = bound(exponentA, EXPONENT_MIN / 2, EXPONENT_MAX / 2); - exponentB = bound(exponentB, EXPONENT_MIN / 2, EXPONENT_MAX / 2); - - int256 d = 3; - int256 di = 0; - while (true) { - int256 i = 1; - int256 j = -38 - di; - while (true) { - // want to see full precision on the THREES regardless of the - // scale of the numerator and denominator. - checkDivision(i, exponentA, d, exponentB, THREES, exponentA - exponentB + j); - - if (i == 1e76) { - break; - } - - i *= 10; - ++j; - } - - if (d == 3e76) { - break; - } - d *= 10; - ++di; - } - } } diff --git a/test/src/lib/LibDecimalFloat.eq.t.sol b/test/src/lib/LibDecimalFloat.eq.t.sol index 8ff74dd6..ca2760e6 100644 --- a/test/src/lib/LibDecimalFloat.eq.t.sol +++ b/test/src/lib/LibDecimalFloat.eq.t.sol @@ -3,9 +3,8 @@ pragma solidity =0.8.25; import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; -import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; - import {Test} from "forge-std/Test.sol"; +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; contract LibDecimalFloatEqTest is Test { using LibDecimalFloat for Float; @@ -15,7 +14,7 @@ contract LibDecimalFloatEqTest is Test { pure returns (bool) { - return LibDecimalFloat.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return LibDecimalFloatImplementation.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } function eqExternal(Float floatA, Float floatB) external pure returns (bool) { @@ -34,108 +33,12 @@ contract LibDecimalFloatEqTest is Test { } } - function testEqReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - { - bool actual = LibDecimalFloat.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - bool expected = LibDecimalFloatSlow.eqSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - - assertEq(actual, expected); - } - - function testEqNotReverts(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { - LibDecimalFloat.eq(x, exponentX, y, exponentY); - } - - /// x == x - function testEqX(int256 x) external pure { - bool eq = LibDecimalFloat.eq(x, 0, x, 0); - assertTrue(eq); - } - - /// xeX == xeX - function testEqOneEAny(int256 x, int256 exponent) external pure { - bool eq = LibDecimalFloat.eq(x, exponent, x, exponent); - assertTrue(eq); - } - - /// xeX != xeY if X != Y && x != 0 - function testEqXEAnyVsXEAny(int256 x, int256 exponentX, int256 exponentY) external pure { - vm.assume(x != 0); - bool eq = LibDecimalFloat.eq(x, exponentX, x, exponentY); - - assertEq(eq, exponentX == exponentY); - - // Reverse the order. - eq = LibDecimalFloat.eq(x, exponentY, x, exponentX); - assertEq(eq, exponentX == exponentY); - } - - /// xeX == xeY if x == 0 - function testEqZero(int256 exponentX, int256 exponentY) external pure { - bool eq = LibDecimalFloat.eq(0, exponentX, 0, exponentY); - assertTrue(eq); - } - - /// xeX != yeY if x != y - function testEqXNotY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { - vm.assume(x != y); - bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); - assertTrue(!eq); - } - /// xeX != yeY if xeX < xeY || xeX > xeY - function testEqXNotYExponents(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { - bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); - bool lt = LibDecimalFloat.lt(x, exponentX, y, exponentY); - bool gt = LibDecimalFloat.gt(x, exponentX, y, exponentY); + function testEqXNotYExponents(Float a, Float b) external pure { + bool eq = a.eq(b); + bool lt = a.lt(b); + bool gt = a.gt(b); assertEq(eq, !lt && !gt); } - - /// if xeX == yeY, then x / y == 10^(X - Y) || y / x == 10^(Y - X) - function testEqXEqY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { - bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); - - if (eq) { - if (x == y) { - assertTrue(exponentX == exponentY || x == 0); - } else if (y > x) { - assertTrue(exponentY < exponentX, "y > x but exponentY >= exponentX"); - assertTrue(exponentX - exponentY < 77, "y > x but exponentX - exponentY >= 77"); - assertEq(x / y, int256(10 ** uint256(exponentX - exponentY)), "y > x but x / y != 10^(X - Y)"); - assertEq(x % y, 0, "y > x but x % y != 0"); - } else { - assertTrue(exponentX < exponentY, "x < y but exponentX >= exponentY"); - assertTrue(exponentY - exponentX < 77, "x < y but exponentY - exponentX >= 77"); - assertEq(y / x, int256(10 ** uint256(exponentY - exponentX)), "x < y but y / x != 10^(Y - X)"); - assertEq(y % x, 0, "x < y but y % x != 0"); - } - } else { - if (x == y) { - assertTrue(exponentX != exponentY); - } - } - } - - function testEqGasDifferentSigns() external pure { - LibDecimalFloat.eq(1, 0, -1, 0); - } - - function testEqGasAZero() external pure { - LibDecimalFloat.eq(0, 0, 1, 0); - } - - function testEqGasBZero() external pure { - LibDecimalFloat.eq(1, 0, 0, 0); - } - - function testEqGasBothZero() external pure { - LibDecimalFloat.eq(0, 0, 0, 0); - } - - function testEqGasExponentDiffOverflow() external pure { - LibDecimalFloat.eq(1, type(int256).max, 1, type(int256).min); - } } diff --git a/test/src/lib/LibDecimalFloat.floor.t.sol b/test/src/lib/LibDecimalFloat.floor.t.sol index 6394169c..89a57d2e 100644 --- a/test/src/lib/LibDecimalFloat.floor.t.sol +++ b/test/src/lib/LibDecimalFloat.floor.t.sol @@ -8,36 +8,13 @@ import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatFloorTest is Test { using LibDecimalFloat for Float; - function floorExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) { - return LibDecimalFloat.floor(signedCoefficient, exponent); - } - - function floorExternal(Float float) external pure returns (Float) { - return LibDecimalFloat.floor(float); - } - /// Stack and mem are the same. - - function testFloorMem(Float float) external { - (int256 signedCoefficient, int256 exponent) = float.unpack(); - try this.floorExternal(signedCoefficient, exponent) returns ( - int256 signedCoefficientFloor, int256 exponentFloor - ) { - Float floatFloor = this.floorExternal(float); - (int256 signedCoefficientFloorUnpacked, int256 exponentFloorUnpacked) = floatFloor.unpack(); - assertEq(signedCoefficientFloor, signedCoefficientFloorUnpacked); - assertEq(exponentFloor, exponentFloorUnpacked); - } catch (bytes memory err) { - vm.expectRevert(err); - this.floorExternal(float); - } - } - - function testFloorNotReverts(int256 x, int256 exponentX) external pure { - LibDecimalFloat.floor(x, exponentX); + function testFloorNotReverts(Float x) external pure { + x.floor(); } function checkFloor(int256 x, int256 exponent, int256 expectedFrac, int256 expectedFracExponent) internal pure { - (x, exponent) = LibDecimalFloat.floor(x, exponent); + Float a = LibDecimalFloat.packLossless(x, exponent); + (x, exponent) = a.floor().unpack(); assertEq(x, expectedFrac); assertEq(exponent, expectedFracExponent); } @@ -99,14 +76,17 @@ contract LibDecimalFloatFloorTest is Test { } function testFloorGasZero() external pure { - LibDecimalFloat.floor(0, 0); + Float a = LibDecimalFloat.packLossless(0, 0); + a.floor(); } function testFloorGasTiny() external pure { - LibDecimalFloat.floor(1, -100); + Float a = LibDecimalFloat.packLossless(1, -100); + a.floor(); } function testFloorGas0() external pure { - LibDecimalFloat.floor(2.5e37, -37); + Float a = LibDecimalFloat.packLossless(2.5e37, -37); + a.floor(); } } diff --git a/test/src/lib/LibDecimalFloat.frac.t.sol b/test/src/lib/LibDecimalFloat.frac.t.sol index 33b87d59..69e95698 100644 --- a/test/src/lib/LibDecimalFloat.frac.t.sol +++ b/test/src/lib/LibDecimalFloat.frac.t.sol @@ -8,37 +8,15 @@ import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatFracTest is Test { using LibDecimalFloat for Float; - function fracExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) { - return LibDecimalFloat.frac(signedCoefficient, exponent); - } - - function fracExternal(Float float) external pure returns (Float) { - return LibDecimalFloat.frac(float); - } - - function testFracMem(Float float) external { - (int256 signedCoefficient, int256 exponent) = float.unpack(); - try this.fracExternal(signedCoefficient, exponent) returns ( - int256 signedCoefficientResult, int256 exponentResult - ) { - Float floatFrac = this.fracExternal(float); - (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatFrac.unpack(); - assertEq(signedCoefficientResult, signedCoefficientUnpacked); - assertEq(exponentResult, exponentUnpacked); - } catch (bytes memory err) { - vm.expectRevert(err); - this.fracExternal(float); - } - } - - function testFracNotReverts(int256 x, int256 exponentX) external pure { - LibDecimalFloat.frac(x, exponentX); + function testFracNotReverts(Float x) external pure { + x.frac(); } function checkFrac(int256 x, int256 exponent, int256 expectedFrac, int256 expectedFracExponent) internal pure { - (x, exponent) = LibDecimalFloat.frac(x, exponent); - assertEq(x, expectedFrac); - assertEq(exponent, expectedFracExponent); + Float a = LibDecimalFloat.packLossless(x, exponent); + (int256 actualFrac, int256 actualFracExponent) = a.frac().unpack(); + assertEq(actualFrac, expectedFrac); + assertEq(actualFracExponent, expectedFracExponent); } /// Every non negative exponent has no fractional component. @@ -92,14 +70,17 @@ contract LibDecimalFloatFracTest is Test { } function testFracGasZero() external pure { - LibDecimalFloat.frac(0, 0); + Float a = LibDecimalFloat.packLossless(0, 0); + a.frac(); } function testFracGasTiny() external pure { - LibDecimalFloat.frac(1, -100); + Float a = LibDecimalFloat.packLossless(1, -100); + a.frac(); } function testFracGas0() external pure { - LibDecimalFloat.frac(2.5e37, -37); + Float a = LibDecimalFloat.packLossless(2.5e37, -37); + a.frac(); } } diff --git a/test/src/lib/LibDecimalFloat.gt.t.sol b/test/src/lib/LibDecimalFloat.gt.t.sol index 34c49aa4..0adf29e4 100644 --- a/test/src/lib/LibDecimalFloat.gt.t.sol +++ b/test/src/lib/LibDecimalFloat.gt.t.sol @@ -10,132 +10,132 @@ import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatGtTest is Test { using LibDecimalFloat for Float; - function gtExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - returns (bool) - { - return LibDecimalFloat.gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - - function gtExternal(Float floatA, Float floatB) external pure returns (bool) { - return LibDecimalFloat.gt(floatA, floatB); - } - /// Stack and mem are the same. - - function testGtMem(Float a, Float b) external { - (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - try this.gtExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns (bool gt) { - bool actual = this.gtExternal(a, b); - assertEq(gt, actual); - } catch (bytes memory err) { - vm.expectRevert(err); - this.gtExternal(a, b); - } - } - function testGtReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure { - bool actual = LibDecimalFloat.gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + Float a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA); + Float b = LibDecimalFloat.packLossless(signedCoefficientB, exponentB); + bool actual = a.gt(b); bool expected = LibDecimalFloatSlow.gtSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); assertEq(actual, expected); } /// x !> x - function testGtX(int256 x) external pure { - bool gt = LibDecimalFloat.gt(x, 0, x, 0); + function testGtX(int224 x) external pure { + Float a = LibDecimalFloat.packLossless(x, 0); + bool gt = a.gt(a); assertTrue(!gt); } /// xeX !> xeX - function testGtOneEAny(int256 x, int256 exponent) external pure { - bool gt = LibDecimalFloat.gt(x, exponent, x, exponent); + function testGtOneEAny(Float a) external pure { + bool gt = a.gt(a); assertTrue(!gt); } /// xeX > xeY if X > Y && x > 0 - function testGtXEAnyVsXEAny(int256 x, int256 exponentA, int256 exponentB) external pure { - x = bound(x, 1, type(int256).max); - bool gt = LibDecimalFloat.gt(x, exponentA, x, exponentB); + function testGtXEAnyVsXEAny(int256 x, int32 exponentA, int32 exponentB) external pure { + x = bound(x, 1, type(int224).max); + Float a = LibDecimalFloat.packLossless(x, exponentA); + Float b = LibDecimalFloat.packLossless(x, exponentB); + bool gt = a.gt(b); assertEq(gt, exponentA > exponentB); // Reverse the order. - gt = LibDecimalFloat.gt(x, exponentB, x, exponentA); + gt = b.gt(a); assertEq(gt, exponentB > exponentA); } /// xeX > xeY if X < Y && x < 0 - function testGtXEAnyVsXEAnyNegative(int256 x, int256 exponentA, int256 exponentB) external pure { - x = bound(x, type(int256).min, -1); - bool gt = LibDecimalFloat.gt(x, exponentA, x, exponentB); + function testGtXEAnyVsXEAnyNegative(int256 x, int32 exponentA, int32 exponentB) external pure { + x = bound(x, type(int224).min, -1); + Float a = LibDecimalFloat.packLossless(x, exponentA); + Float b = LibDecimalFloat.packLossless(x, exponentB); + bool gt = a.gt(b); assertEq(gt, exponentA < exponentB); // Reverse the order. - gt = LibDecimalFloat.gt(x, exponentB, x, exponentA); + gt = b.gt(a); assertEq(gt, exponentB < exponentA); } /// xeX !> xeY if x == 0 - function testGtZero(int256 exponentA, int256 exponentB) external pure { - bool gt = LibDecimalFloat.gt(0, exponentA, 0, exponentB); + function testGtZero(int32 exponentA, int32 exponentB) external pure { + Float a = LibDecimalFloat.packLossless(0, exponentA); + Float b = LibDecimalFloat.packLossless(0, exponentB); + bool gt = a.gt(b); + assertTrue(!gt); + // Reverse the order. + gt = b.gt(a); assertTrue(!gt); } /// xeX > yeY if x >= 0 && y < 0 - function testGtXPositiveYNegative(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { - x = bound(x, 0, type(int256).max); - y = bound(y, type(int256).min, -1); - bool gt = LibDecimalFloat.gt(x, exponentX, y, exponentY); + function testGtXPositiveYNegative(int256 x, int32 exponentX, int256 y, int32 exponentY) external pure { + x = bound(x, 0, type(int224).max); + y = bound(y, type(int224).min, -1); + Float a = LibDecimalFloat.packLossless(x, exponentX); + Float b = LibDecimalFloat.packLossless(y, exponentY); + bool gt = a.gt(b); assertTrue(gt); // Reverse the order. - gt = LibDecimalFloat.gt(y, exponentY, x, exponentX); + gt = b.gt(a); assertTrue(!gt); } /// xeX > yeY if xeX != yeY && xeX !< yeY - function testGtXNotY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { - bool gt = LibDecimalFloat.gt(x, exponentX, y, exponentY); - bool eq = LibDecimalFloat.eq(x, exponentX, y, exponentY); - bool lt = LibDecimalFloat.lt(x, exponentX, y, exponentY); + function testGtXNotY(Float a, Float b) external pure { + bool gt = a.gt(b); + bool eq = a.eq(b); + bool lt = a.lt(b); assertEq(gt, !lt && !eq); } /// xeX > yeY if xeX > 0 && yeY == 0 - function testGtXPositiveYZero(int256 x, int256 exponentX, int256 exponentZero) external pure { - x = bound(x, 1, type(int256).max); - bool gt = LibDecimalFloat.gt(x, exponentX, 0, exponentZero); + function testGtXPositiveYZero(int256 x, int32 exponentX, int32 exponentZero) external pure { + x = bound(x, 1, type(int224).max); + Float a = LibDecimalFloat.packLossless(x, exponentX); + Float b = LibDecimalFloat.packLossless(0, exponentZero); + bool gt = a.gt(b); assertTrue(gt); // Reverse the order. - gt = LibDecimalFloat.gt(0, exponentZero, x, exponentX); + gt = b.gt(a); assertTrue(!gt); } function testGtGasDifferentSigns() external pure { - LibDecimalFloat.gt(1, 0, -1, 0); + Float a = LibDecimalFloat.packLossless(1, 0); + Float b = LibDecimalFloat.packLossless(-1, 0); + a.gt(b); } function testGtGasAZero() external pure { - LibDecimalFloat.gt(0, 0, 1, 0); + Float a = LibDecimalFloat.packLossless(0, 0); + Float b = LibDecimalFloat.packLossless(1, 0); + a.gt(b); } function testGtGasBZero() external pure { - LibDecimalFloat.gt(1, 0, 0, 0); + Float a = LibDecimalFloat.packLossless(1, 0); + Float b = LibDecimalFloat.packLossless(0, 0); + a.gt(b); } function testGtGasBothZero() external pure { - LibDecimalFloat.gt(0, 0, 0, 0); + Float a = LibDecimalFloat.packLossless(0, 0); + a.gt(a); } function testGtGasExponentDiffOverflow() external pure { - LibDecimalFloat.gt(1, type(int256).max, 1, type(int256).min); + Float a = LibDecimalFloat.packLossless(1, type(int32).max); + Float b = LibDecimalFloat.packLossless(1, type(int32).min); + a.gt(b); } } diff --git a/test/src/lib/LibDecimalFloat.inv.t.sol b/test/src/lib/LibDecimalFloat.inv.t.sol index 09c744a4..4b191709 100644 --- a/test/src/lib/LibDecimalFloat.inv.t.sol +++ b/test/src/lib/LibDecimalFloat.inv.t.sol @@ -2,14 +2,14 @@ pragma solidity =0.8.25; import {Test} from "forge-std/Test.sol"; -import {LibDecimalFloat, Float, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; -import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; +import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; contract LibDecimalFloatInvTest is Test { using LibDecimalFloat for Float; function invExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) { - return LibDecimalFloat.inv(signedCoefficient, exponent); + return LibDecimalFloatImplementation.inv(signedCoefficient, exponent); } function invExternal(Float float) external pure returns (Float) { @@ -31,27 +31,4 @@ contract LibDecimalFloatInvTest is Test { this.invExternal(float); } } - - /// Compare reference. - function testInvReference(int256 signedCoefficient, int256 exponent) external pure { - vm.assume(signedCoefficient != 0); - exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - - (int256 outputSignedCoefficient, int256 outputExponent) = LibDecimalFloat.inv(signedCoefficient, exponent); - (int256 referenceSignedCoefficient, int256 referenceExponent) = - LibDecimalFloatSlow.invSlow(signedCoefficient, exponent); - - assertEq(outputSignedCoefficient, referenceSignedCoefficient, "coefficient"); - assertEq(outputExponent, referenceExponent, "exponent"); - } - - function testInvGas0() external pure { - (int256 outputSignedCoefficient, int256 outputExponent) = LibDecimalFloat.inv(3e37, -37); - (outputSignedCoefficient, outputExponent); - } - - function testInvSlowGas0() external pure { - (int256 outputSignedCoefficient, int256 outputExponent) = LibDecimalFloatSlow.invSlow(3e37, -37); - (outputSignedCoefficient, outputExponent); - } } diff --git a/test/src/lib/LibDecimalFloat.log10.t.sol b/test/src/lib/LibDecimalFloat.log10.t.sol index db67e1e4..3317e40a 100644 --- a/test/src/lib/LibDecimalFloat.log10.t.sol +++ b/test/src/lib/LibDecimalFloat.log10.t.sol @@ -2,25 +2,22 @@ pragma solidity =0.8.25; import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {LogTest} from "../../abstract/LogTest.sol"; -import {console} from "forge-std/Test.sol"; - contract LibDecimalFloatLog10Test is LogTest { using LibDecimalFloat for Float; function log10External(int256 signedCoefficient, int256 exponent) external returns (int256, int256) { address tables = logTables(); - return LibDecimalFloat.log10(tables, signedCoefficient, exponent); + return LibDecimalFloatImplementation.log10(tables, signedCoefficient, exponent); } function log10External(Float float) external returns (Float) { - address tables = logTables(); - return LibDecimalFloat.log10(tables, float); + return float.log10(logTables()); } - /// Stack and mem are the same. - function testLog10Mem(Float float) external { + function testLog10Packed(Float float) external { (int256 signedCoefficient, int256 exponent) = float.unpack(); try this.log10External(signedCoefficient, exponent) returns ( int256 signedCoefficientResult, int256 exponentResult @@ -34,49 +31,4 @@ contract LibDecimalFloatLog10Test is LogTest { this.log10External(float); } } - - function checkLog10( - int256 signedCoefficient, - int256 exponent, - int256 expectedSignedCoefficient, - int256 expectedExponent - ) internal { - address tables = logTables(); - uint256 a = gasleft(); - (int256 actualSignedCoefficient, int256 actualExponent) = - LibDecimalFloat.log10(tables, signedCoefficient, exponent); - uint256 b = gasleft(); - console.log("%d %d Gas used: %d", uint256(signedCoefficient), uint256(exponent), a - b); - assertEq(actualSignedCoefficient, expectedSignedCoefficient); - assertEq(actualExponent, expectedExponent); - } - - function testExactLogs() external { - checkLog10(1, 0, 0, 0); - checkLog10(10, 0, 1, 0); - checkLog10(100, 0, 2, 0); - checkLog10(1000, 0, 3, 0); - checkLog10(10000, 0, 4, 0); - checkLog10(1e37, -37, 0, 0); - } - - function testExactLookups() external { - checkLog10(1001, 0, 3.0004e41, -41); - checkLog10(100.1e1, -1, 2.0004e41, -41); - checkLog10(10.01e2, -2, 1.0004e41, -41); - checkLog10(1.001e3, -3, 0.0004e38, -38); - - checkLog10(10.02e2, -2, 1.0009e41, -41); - checkLog10(10.99e2, -2, 1.0411e39, -39); - - checkLog10(6566, 0, 3.8173e38, -38); - } - - function testInterpolatedLookups() external { - checkLog10(10.015e3, -3, 1.00065e41, -41); - } - - function testSub1() external { - checkLog10(0.1001e4, -4, -0.9996e38, -38); - } } diff --git a/test/src/lib/LibDecimalFloat.lt.t.sol b/test/src/lib/LibDecimalFloat.lt.t.sol index 3d43e901..cde2ed21 100644 --- a/test/src/lib/LibDecimalFloat.lt.t.sol +++ b/test/src/lib/LibDecimalFloat.lt.t.sol @@ -10,141 +10,133 @@ import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatLtTest is Test { using LibDecimalFloat for Float; - function ltExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - returns (bool) - { - return LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - - function ltExternal(Float floatA, Float floatB) external pure returns (bool) { - return LibDecimalFloat.lt(floatA, floatB); - } - - function testLtMem(Float a, Float b) external { + function testLtReference(Float a, Float b) external pure { + bool actual = LibDecimalFloat.lt(a, b); (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - try this.ltExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns (bool lt) { - bool actual = this.ltExternal(a, b); - assertEq(lt, actual); - } catch (bytes memory err) { - vm.expectRevert(err); - this.ltExternal(a, b); - } - } - - function testLtReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - { - bool actual = LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); bool expected = LibDecimalFloatSlow.ltSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); assertEq(actual, expected); } /// x !< x - function testLtX(int256 x) external pure { - bool lt = LibDecimalFloat.lt(x, 0, x, 0); + function testLtX(int224 x) external pure { + Float a = LibDecimalFloat.packLossless(x, 0); + bool lt = LibDecimalFloat.lt(a, a); assertTrue(!lt); } /// xeX !< xeX - function testLtOneEAny(int256 x, int256 exponent) external pure { - bool lt = LibDecimalFloat.lt(x, exponent, x, exponent); + function testLtOneEAny(int224 x, int32 exponent) external pure { + Float a = LibDecimalFloat.packLossless(x, exponent); + bool lt = LibDecimalFloat.lt(a, a); assertTrue(!lt); } /// xeX < xeY if X < Y && x > 0 function testLtXEAnyVsXEAny(int256 x, int256 exponentA, int256 exponentB) external pure { - x = bound(x, 1, type(int256).max); - bool lt = LibDecimalFloat.lt(x, exponentA, x, exponentB); + x = bound(x, 1, type(int224).max); + + Float a = LibDecimalFloat.packLossless(x, exponentA); + Float b = LibDecimalFloat.packLossless(x, exponentB); + + // Compare the two floats. + bool lt = a.lt(b); assertEq(lt, exponentA < exponentB); // Reverse the order. - lt = LibDecimalFloat.lt(x, exponentB, x, exponentA); + lt = b.lt(a); assertEq(lt, exponentB < exponentA); } /// xeX < xeY if X > Y && x < 0 - function testLtXEAnyVsXEAnyNegative(int256 x, int256 exponentA, int256 exponentB) external pure { - x = bound(x, type(int256).min, -1); - bool lt = LibDecimalFloat.lt(x, exponentA, x, exponentB); + function testLtXEAnyVsXEAnyNegative(int256 x, int32 exponentA, int32 exponentB) external pure { + x = bound(x, type(int224).min, -1); + Float a = LibDecimalFloat.packLossless(x, exponentA); + Float b = LibDecimalFloat.packLossless(x, exponentB); + // Compare the two floats. + bool lt = a.lt(b); assertEq(lt, exponentA > exponentB); // Reverse the order. - lt = LibDecimalFloat.lt(x, exponentB, x, exponentA); + lt = b.lt(a); assertEq(lt, exponentB > exponentA); } /// xeX !< xeY if x == 0 - function testLtZero(int256 exponentA, int256 exponentB) external pure { - bool lt = LibDecimalFloat.lt(0, exponentA, 0, exponentB); + function testLtZero(int32 exponentA, int32 exponentB) external pure { + Float a = LibDecimalFloat.packLossless(0, exponentA); + Float b = LibDecimalFloat.packLossless(0, exponentB); + // Compare the two floats. + bool lt = a.lt(b); assertTrue(!lt); } /// xeX < yeY if x < 0 && y >= 0 function testLtNegativeVsPositive( int256 signedCoefficientNeg, - int256 exponentNeg, + int32 exponentNeg, int256 signedCoefficientPos, - int256 exponentPos + int32 exponentPos ) external pure { - signedCoefficientNeg = bound(signedCoefficientNeg, type(int256).min, -1); - signedCoefficientPos = bound(signedCoefficientPos, 0, type(int256).max); + signedCoefficientNeg = bound(signedCoefficientNeg, type(int224).min, -1); + signedCoefficientPos = bound(signedCoefficientPos, 0, type(int224).max); - bool lt = LibDecimalFloat.lt(signedCoefficientNeg, exponentNeg, signedCoefficientPos, exponentPos); + Float a = LibDecimalFloat.packLossless(signedCoefficientNeg, exponentNeg); + Float b = LibDecimalFloat.packLossless(signedCoefficientPos, exponentPos); + // Compare the two floats. + bool lt = a.lt(b); assertTrue(lt); // Reverse the order. - lt = LibDecimalFloat.lt(signedCoefficientPos, exponentPos, signedCoefficientNeg, exponentNeg); + lt = b.lt(a); assertTrue(!lt); } /// X < Y if Y !< X && X != Y - function testLtVsEqualVsGt(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - { - bool lt = LibDecimalFloat.lt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - bool equal = LibDecimalFloat.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - bool gt = LibDecimalFloat.gt(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + function testLtVsEqualVsGt(Float a, Float b) external pure { + bool lt = a.lt(b); + bool equal = a.eq(b); + bool gt = a.gt(b); assertEq(lt, !equal && !gt); } /// X < Y if X < 0 && Y == 0 - function testLtNegativeVsZero(int256 signedCoefficientNeg, int256 exponentNeg, int256 exponentZero) external pure { - signedCoefficientNeg = bound(signedCoefficientNeg, type(int256).min, -1); - - bool lt = LibDecimalFloat.lt(signedCoefficientNeg, exponentNeg, 0, exponentZero); + function testLtNegativeVsZero(int256 signedCoefficientNeg, int32 exponentNeg, int32 exponentZero) external pure { + signedCoefficientNeg = bound(signedCoefficientNeg, type(int224).min, -1); + Float a = LibDecimalFloat.packLossless(signedCoefficientNeg, exponentNeg); + Float b = LibDecimalFloat.packLossless(0, exponentZero); + // Compare the two floats. + bool lt = LibDecimalFloat.lt(a, b); assertTrue(lt); // Reverse the order. - lt = LibDecimalFloat.lt(0, exponentZero, signedCoefficientNeg, exponentNeg); + lt = LibDecimalFloat.lt(b, a); assertTrue(!lt); } function testLtGasDifferentSigns() external pure { - LibDecimalFloat.lt(1, 0, -1, 0); + LibDecimalFloat.lt(LibDecimalFloat.packLossless(1, 0), LibDecimalFloat.packLossless(-1, 0)); } function testLtGasAZero() external pure { - LibDecimalFloat.lt(0, 0, 1, 0); + LibDecimalFloat.lt(LibDecimalFloat.packLossless(0, 0), LibDecimalFloat.packLossless(1, 0)); } function testLtGasBZero() external pure { - LibDecimalFloat.lt(1, 0, 0, 0); + LibDecimalFloat.lt(LibDecimalFloat.packLossless(1, 0), LibDecimalFloat.packLossless(0, 0)); } function testLtGasBothZero() external pure { - LibDecimalFloat.lt(0, 0, 0, 0); + LibDecimalFloat.lt(LibDecimalFloat.packLossless(0, 0), LibDecimalFloat.packLossless(0, 0)); } function testLtGasExponentDiffOverflow() external pure { - LibDecimalFloat.lt(1, type(int256).max, 1, type(int256).min); + LibDecimalFloat.lt( + LibDecimalFloat.packLossless(1, type(int32).max), LibDecimalFloat.packLossless(1, type(int32).min) + ); } } diff --git a/test/src/lib/LibDecimalFloat.max.t.sol b/test/src/lib/LibDecimalFloat.max.t.sol index 2d426916..4a36d3e0 100644 --- a/test/src/lib/LibDecimalFloat.max.t.sol +++ b/test/src/lib/LibDecimalFloat.max.t.sol @@ -7,34 +7,6 @@ import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; contract LibDecimalFloatMaxTest is Test { using LibDecimalFloat for Float; - function maxExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - returns (int256, int256) - { - return LibDecimalFloat.max(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - - function maxExternal(Float floatA, Float floatB) external pure returns (Float) { - return floatA.max(floatB); - } - - function testMaxMem(Float a, Float b) external { - (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - try this.maxExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( - int256 signedCoefficientResult, int256 exponentResult - ) { - Float floatResult = this.maxExternal(a, b); - (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatResult.unpack(); - assertEq(signedCoefficientResult, signedCoefficientUnpacked); - assertEq(exponentResult, exponentUnpacked); - } catch (bytes memory err) { - vm.expectRevert(err); - this.maxExternal(a, b); - } - } - /// x.max(x) function testMaxX(Float x) external pure { Float y = x.max(x); @@ -49,16 +21,11 @@ contract LibDecimalFloatMaxTest is Test { } /// x.max(y) for x == y - function testMaxXYEqual(int256 signedCoefficientX, int256 exponentX) external pure { - int256 signedCoefficientY = signedCoefficientX; - int256 exponentY = exponentX; - (int256 signedCoefficientZ, int256 exponentZ) = - LibDecimalFloat.max(signedCoefficientX, exponentX, signedCoefficientY, exponentY); - - assertEq(signedCoefficientZ, signedCoefficientX, "signedCoefficientZ != signedCoefficientX"); - assertEq(exponentZ, exponentX, "exponentZ != exponentX"); - assertEq(signedCoefficientZ, signedCoefficientY, "signedCoefficientZ != signedCoefficientY"); - assertEq(exponentZ, exponentY, "exponentZ != exponentY"); + function testMaxXYEqual(Float x) external pure { + Float y = x; + Float z = x.max(y); + assertTrue(z.eq(x), "x.max(y) != x"); + assertTrue(z.eq(y), "x.max(y) != y"); } /// x.max(y) for x > y diff --git a/test/src/lib/LibDecimalFloat.min.t.sol b/test/src/lib/LibDecimalFloat.min.t.sol index 34bcc271..a9b0fff5 100644 --- a/test/src/lib/LibDecimalFloat.min.t.sol +++ b/test/src/lib/LibDecimalFloat.min.t.sol @@ -7,35 +7,6 @@ import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; contract LibDecimalFloatMinTest is Test { using LibDecimalFloat for Float; - function minExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - pure - returns (int256, int256) - { - return LibDecimalFloat.min(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - - function minExternal(Float floatA, Float floatB) external pure returns (Float) { - return floatA.min(floatB); - } - - /// Test to verify that stack-based and memory-based implementations produce the same results. - function testMinMem(Float a, Float b) external { - (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - try this.minExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( - int256 signedCoefficient, int256 exponent - ) { - Float actual = this.minExternal(a, b); - (int256 signedCoefficientResult, int256 exponentResult) = actual.unpack(); - assertEq(signedCoefficient, signedCoefficientResult); - assertEq(exponent, exponentResult); - } catch (bytes memory err) { - vm.expectRevert(err); - this.minExternal(a, b); - } - } - /// x.min(x) function testMinX(Float x) external pure { Float y = x.min(x); @@ -50,15 +21,11 @@ contract LibDecimalFloatMinTest is Test { } /// x.min(y) for x == y - function testMinXYEqual(int256 signedCoefficientX, int256 exponentX) external pure { - int256 signedCoefficientY = signedCoefficientX; - int256 exponentY = exponentX; - (int256 signedCoefficientZ, int256 exponentZ) = - LibDecimalFloat.min(signedCoefficientX, exponentX, signedCoefficientY, exponentY); - assertEq(signedCoefficientZ, signedCoefficientX, "signedCoefficientZ != signedCoefficientX"); - assertEq(exponentZ, exponentX, "exponentZ != exponentX"); - assertEq(signedCoefficientZ, signedCoefficientY, "signedCoefficientZ != signedCoefficientY"); - assertEq(exponentZ, exponentY, "exponentZ != exponentY"); + function testMinXYEqual(Float x) external pure { + Float y = x; + Float z = x.min(y); + assertTrue(z.eq(x), "x.min(y) != x"); + assertTrue(z.eq(y), "x.min(y) != y"); } /// x.min(y) for x < y diff --git a/test/src/lib/LibDecimalFloat.minus.t.sol b/test/src/lib/LibDecimalFloat.minus.t.sol index b44ed8e2..d5ee1ee7 100644 --- a/test/src/lib/LibDecimalFloat.minus.t.sol +++ b/test/src/lib/LibDecimalFloat.minus.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, Float, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {Test} from "forge-std/Test.sol"; @@ -10,15 +10,14 @@ contract LibDecimalFloatMinusTest is Test { using LibDecimalFloat for Float; function minusExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) { - return LibDecimalFloat.minus(signedCoefficient, exponent); + return LibDecimalFloatImplementation.minus(signedCoefficient, exponent); } function minusExternal(Float float) external pure returns (Float) { return LibDecimalFloat.minus(float); } - /// Stack and mem are the same. - function testMinusMem(Float float) external { + function testMinusPacked(Float float) external { (int256 signedCoefficientFloat, int256 exponentFloat) = float.unpack(); try this.minusExternal(signedCoefficientFloat, exponentFloat) returns ( int256 signedCoefficient, int256 exponent @@ -32,17 +31,4 @@ contract LibDecimalFloatMinusTest is Test { this.minusExternal(float); } } - - /// 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); - } } diff --git a/test/src/lib/LibDecimalFloat.mixed.t.sol b/test/src/lib/LibDecimalFloat.mixed.t.sol index e12e5b32..d9b7bdf1 100644 --- a/test/src/lib/LibDecimalFloat.mixed.t.sol +++ b/test/src/lib/LibDecimalFloat.mixed.t.sol @@ -1,20 +1,25 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; import {THREES, ONES} from "../../lib/LibCommonResults.sol"; import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatMixedTest is Test { + using LibDecimalFloat for Float; + /// (1 / 3) * 555e18 function testDivide1Over3() external pure { - (int256 signedCoefficientDiv, int256 exponentDiv) = LibDecimalFloat.divide(1, 0, 3, 0); + Float a = LibDecimalFloat.packLossless(1, 0); + Float b = LibDecimalFloat.packLossless(3, 0); + Float c = a.divide(b); + (int256 signedCoefficientDiv, int256 exponentDiv) = LibDecimalFloat.unpack(c); assertEq(signedCoefficientDiv, THREES, "coefficient"); assertEq(exponentDiv, -38, "exponent"); - (int256 signedCoefficientMul, int256 exponentMul) = - LibDecimalFloat.multiply(signedCoefficientDiv, exponentDiv, 555, 18); + Float d = c.multiply(LibDecimalFloat.packLossless(555, 18)); + (int256 signedCoefficientMul, int256 exponentMul) = LibDecimalFloat.unpack(d); assertEq(signedCoefficientMul, 18499999999999999999999999999999999999815); assertEq(exponentMul, -20); diff --git a/test/src/lib/LibDecimalFloat.multiply.t.sol b/test/src/lib/LibDecimalFloat.multiply.t.sol index aa00aa94..f4a43355 100644 --- a/test/src/lib/LibDecimalFloat.multiply.t.sol +++ b/test/src/lib/LibDecimalFloat.multiply.t.sol @@ -1,16 +1,8 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import { - LibDecimalFloat, - NORMALIZED_ZERO_SIGNED_COEFFICIENT, - NORMALIZED_ZERO_EXPONENT, - Float, - EXPONENT_MIN, - EXPONENT_MAX -} 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 {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; import {Test} from "forge-std/Test.sol"; @@ -22,15 +14,14 @@ contract LibDecimalFloatMultiplyTest is Test { pure returns (int256, int256) { - return LibDecimalFloat.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return LibDecimalFloatImplementation.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } function multiplyExternal(Float floatA, Float floatB) external pure returns (Float) { return LibDecimalFloat.multiply(floatA, floatB); } - /// Stack and mem are the same. - function testMultiplyMem(Float a, Float b) external { + function testMultiplyPacked(Float a, Float b) external { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); try this.multiplyExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( @@ -45,98 +36,4 @@ contract LibDecimalFloatMultiplyTest is Test { this.multiplyExternal(a, b); } } - - /// Simple 0 multiply 0 - /// 0 * 0 = 0 - function testMultiplyZero0Exponent() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(0, 0, 0, 0); - assertEq(signedCoefficient, NORMALIZED_ZERO_SIGNED_COEFFICIENT); - assertEq(exponent, NORMALIZED_ZERO_EXPONENT); - } - - /// 0 multiply 0 any exponent - /// 0 * 0 = 0 - function testMultiplyZeroAnyExponent(int64 exponentA, int64 exponentB) external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(0, exponentA, 0, exponentB); - assertEq(signedCoefficient, NORMALIZED_ZERO_SIGNED_COEFFICIENT); - assertEq(exponent, NORMALIZED_ZERO_EXPONENT); - } - - /// 0 multiply 1 - /// 0 * 1 = 0 - function testMultiplyZeroOne() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(0, 0, 1, 0); - assertEq(signedCoefficient, NORMALIZED_ZERO_SIGNED_COEFFICIENT); - assertEq(exponent, NORMALIZED_ZERO_EXPONENT); - } - - /// 1 multiply 0 - /// 1 * 0 = 0 - function testMultiplyOneZero() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(1, 0, 0, 0); - assertEq(signedCoefficient, NORMALIZED_ZERO_SIGNED_COEFFICIENT); - assertEq(exponent, NORMALIZED_ZERO_EXPONENT); - } - - /// 1 multiply 1 - /// 1 * 1 = 1 - function testMultiplyOneOne() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(1, 0, 1, 0); - assertEq(signedCoefficient, 1); - assertEq(exponent, 0); - } - - /// 123456789 multiply 987654321 - /// 123456789 * 987654321 = 121932631112635269 - function testMultiply123456789987654321() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(123456789, 0, 987654321, 0); - assertEq(signedCoefficient, 121932631112635269); - assertEq(exponent, 0); - } - - /// 123456789 multiply 987654321 with exponents - /// 123456789 * 987654321 = 121932631112635269 - function testMultiply123456789987654321WithExponents(int128 exponentA, int128 exponentB) external pure { - exponentA = int128(bound(exponentA, -127, 127)); - exponentB = int128(bound(exponentB, -127, 127)); - - (int256 signedCoefficient, int256 exponent) = - LibDecimalFloat.multiply(123456789, exponentA, 987654321, exponentB); - assertEq(signedCoefficient, 121932631112635269); - assertEq(exponent, exponentA + exponentB); - } - - /// 1e18 * 1e-19 = 1e-1 - function testMultiply1e181e19() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(1, 18, 1, -19); - assertEq(signedCoefficient, 1); - assertEq(exponent, -1); - } - - function testMultiplyGasZero() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(0, 0, 0, 0); - (signedCoefficient, exponent); - } - - function testMultiplyGasOne() external pure { - (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.multiply(1e37, -37, 1e37, -37); - (signedCoefficient, exponent); - } - - function testMultiplyNotRevertAnyExpectation( - int256 signedCoefficientA, - int256 exponentA, - int256 signedCoefficientB, - int256 exponentB - ) external pure { - exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX); - exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX); - (int256 signedCoefficient, int256 exponent) = - LibDecimalFloat.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - (int256 expectedSignedCoefficient, int256 expectedExponent) = - LibDecimalFloatSlow.multiplySlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - - assertEq(signedCoefficient, expectedSignedCoefficient); - assertEq(exponent, expectedExponent); - } } diff --git a/test/src/lib/LibDecimalFloat.normalize.t.sol b/test/src/lib/LibDecimalFloat.normalize.t.sol deleted file mode 100644 index 08e80331..00000000 --- a/test/src/lib/LibDecimalFloat.normalize.t.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: CAL -pragma solidity =0.8.25; - -import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; -import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; -import {Test} from "forge-std/Test.sol"; - -contract LibDecimalFloatNormalizeTest is Test { - using LibDecimalFloat for Float; - - function normalizeExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) { - return LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); - } - - function normalizeExternal(Float float) external pure returns (Float) { - return LibDecimalFloat.normalize(float); - } - /// Stack and mem are the same. - - function testNormalizeMem(Float float) external { - (int256 signedCoefficient, int256 exponent) = float.unpack(); - try this.normalizeExternal(signedCoefficient, exponent) returns ( - int256 signedCoefficientNormalized, int256 exponentNormalized - ) { - Float floatNormalized = this.normalizeExternal(float); - (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatNormalized.unpack(); - assertEq(signedCoefficientNormalized, signedCoefficientUnpacked); - assertEq(exponentNormalized, exponentUnpacked); - } catch (bytes memory err) { - vm.expectRevert(err); - this.normalizeExternal(float); - } - } -} diff --git a/test/src/lib/LibDecimalFloat.pack.t.sol b/test/src/lib/LibDecimalFloat.pack.t.sol index cd43913a..03e2991f 100644 --- a/test/src/lib/LibDecimalFloat.pack.t.sol +++ b/test/src/lib/LibDecimalFloat.pack.t.sol @@ -6,8 +6,8 @@ import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFl import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatPackTest is Test { - function packExternal(int256 signedCoefficient, int256 exponent) external pure returns (Float) { - return LibDecimalFloat.pack(signedCoefficient, exponent); + function packLossyExternal(int256 signedCoefficient, int256 exponent) external pure returns (Float, bool) { + return LibDecimalFloat.packLossy(signedCoefficient, exponent); } function unpackExternal(Float float) external pure returns (int256, int256) { @@ -16,9 +16,10 @@ contract LibDecimalFloatPackTest is Test { /// Round trip from/to parts. function testPartsRoundTrip(int224 signedCoefficient, int32 exponent) external pure { - Float float = LibDecimalFloat.pack(signedCoefficient, exponent); + (Float float, bool lossless) = LibDecimalFloat.packLossy(signedCoefficient, exponent); (int256 signedCoefficientOut, int256 exponentOut) = LibDecimalFloat.unpack(float); + assertTrue(lossless, "lossless"); assertEq(signedCoefficient, signedCoefficientOut, "coefficient"); if (signedCoefficient != 0) { assertEq(exponent, exponentOut, "exponent"); @@ -27,21 +28,4 @@ contract LibDecimalFloatPackTest is Test { assertEq(exponentOut, 0, "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); - - Float float = LibDecimalFloat.pack(signedCoefficientNormalized, exponentNormalized); - (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = LibDecimalFloat.unpack(float); - - assertTrue( - LibDecimalFloat.eq( - signedCoefficientNormalized, exponentNormalized, signedCoefficientUnpacked, exponentUnpacked - ), - "eq" - ); - } } diff --git a/test/src/lib/LibDecimalFloat.power.t.sol b/test/src/lib/LibDecimalFloat.power.t.sol index 19bf67a4..77581c30 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -4,40 +4,13 @@ pragma solidity =0.8.25; import {LogTest} from "../../abstract/LogTest.sol"; import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {console2} from "forge-std/Test.sol"; contract LibDecimalFloatPowerTest is LogTest { using LibDecimalFloat for Float; - function powerExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) - external - returns (int256, int256) - { - return LibDecimalFloat.power(logTables(), signedCoefficientA, exponentA, signedCoefficientB, exponentB); - } - - function powerExternal(Float floatA, Float floatB) external returns (Float) { - return LibDecimalFloat.power(logTables(), floatA, floatB); - } - /// Stack and mem are the same. - - function testPowerMem(Float a, Float b) external { - (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - try this.powerExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( - int256 signedCoefficient, int256 exponent - ) { - Float float = this.powerExternal(a, b); - (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = float.unpack(); - assertEq(signedCoefficient, signedCoefficientUnpacked); - assertEq(exponent, exponentUnpacked); - } catch (bytes memory err) { - vm.expectRevert(err); - this.powerExternal(a, b); - } - } - function checkPower( int256 signedCoefficientA, int256 exponentA, @@ -46,12 +19,14 @@ contract LibDecimalFloatPowerTest is LogTest { int256 expectedSignedCoefficient, int256 expectedExponent ) internal { + Float a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA); + Float b = LibDecimalFloat.packLossless(signedCoefficientB, exponentB); address tables = logTables(); - uint256 a = gasleft(); - (int256 actualSignedCoefficient, int256 actualExponent) = - LibDecimalFloat.power(tables, signedCoefficientA, exponentA, signedCoefficientB, exponentB); - uint256 b = gasleft(); - console2.log("%d %d Gas used: %d", uint256(signedCoefficientA), uint256(exponentA), a - b); + uint256 beforeGas = gasleft(); + Float c = a.power(b, tables); + uint256 afterGas = gasleft(); + console2.log("%d %d Gas used: %d", uint256(signedCoefficientA), uint256(exponentA), beforeGas - afterGas); + (int256 actualSignedCoefficient, int256 actualExponent) = c.unpack(); assertEq(actualSignedCoefficient, expectedSignedCoefficient, "signedCoefficient"); assertEq(actualExponent, expectedExponent, "exponent"); } @@ -61,18 +36,17 @@ contract LibDecimalFloatPowerTest is LogTest { checkPower(5e37, -38, 6e37, -36, 8.7108013937282229965156794425087108013e37, -56); } - function checkRoundTrip(int256 x, int256 exponentX, int256 y, int256 exponentY) internal { + function checkRoundTrip(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + internal + { + Float a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA); + Float b = LibDecimalFloat.packLossless(signedCoefficientB, exponentB); address tables = logTables(); - (int256 result, int256 exponent) = LibDecimalFloat.power(tables, x, exponentX, y, exponentY); - (y, exponentY) = LibDecimalFloat.inv(y, exponentY); - (int256 roundTrip, int256 roundTripExponent) = LibDecimalFloat.power(tables, result, exponent, y, exponentY); + Float c = a.power(b, tables); + Float roundTrip = c.power(b.inv(), tables); + Float diff = a.divide(roundTrip).sub(LibDecimalFloat.packLossless(1, 0)).abs(); - (int256 diff, int256 diffExponent) = LibDecimalFloat.divide(x, exponentX, roundTrip, roundTripExponent); - (diff, diffExponent) = LibDecimalFloat.sub(diff, diffExponent, 1, 0); - (diff, diffExponent) = LibDecimalFloat.abs(diff, diffExponent); - console2.log(diff); - console2.log(diffExponent); - assertTrue(LibDecimalFloat.lt(diff, diffExponent, 0.0025e4, -4), "diff"); + assertTrue(diff.lt(LibDecimalFloat.packLossless(0.0025e4, -4)), "diff"); } /// X^Y^(1/Y) = X diff --git a/test/src/lib/LibDecimalFloat.power10.t.sol b/test/src/lib/LibDecimalFloat.power10.t.sol index 730dce5a..c6dc99d2 100644 --- a/test/src/lib/LibDecimalFloat.power10.t.sol +++ b/test/src/lib/LibDecimalFloat.power10.t.sol @@ -3,13 +3,14 @@ pragma solidity =0.8.25; import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; import {LogTest} from "../../abstract/LogTest.sol"; +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; contract LibDecimalFloatPower10Test is LogTest { using LibDecimalFloat for Float; function power10External(int256 signedCoefficient, int256 exponent) external returns (int256, int256) { address tables = logTables(); - return LibDecimalFloat.power10(tables, signedCoefficient, exponent); + return LibDecimalFloatImplementation.power10(tables, signedCoefficient, exponent); } function power10External(Float float) external returns (Float) { @@ -32,75 +33,4 @@ contract LibDecimalFloatPower10Test is LogTest { this.power10External(float); } } - - function checkPower10( - int256 signedCoefficient, - int256 exponent, - int256 expectedSignedCoefficient, - int256 expectedExponent - ) internal { - address tables = logTables(); - (int256 actualSignedCoefficient, int256 actualExponent) = - LibDecimalFloat.power10(tables, signedCoefficient, exponent); - assertEq(actualSignedCoefficient, expectedSignedCoefficient, "signedCoefficient"); - assertEq(actualExponent, expectedExponent, "exponent"); - } - - function testExactPowers() external { - // 10^1 = 10 - checkPower10(1e37, -37, 1000, -2); - // 10^10 = 10000000000 - checkPower10(10e37, -37, 1000, 7); - checkPower10(1, 2, 1000, 97); - checkPower10(1, 3, 1000, 997); - checkPower10(1, 4, 1000, 9997); - } - - function testExactLookups() external { - // 10^2 = 100 - checkPower10(2, 0, 1000, -1); - // 10^3 = 1000 - checkPower10(3, 0, 1000, 0); - // 10^4 = 10000 - checkPower10(4, 0, 1000, 1); - // 10^5 = 100000 - checkPower10(5, 0, 1000, 2); - // 10^6 = 1000000 - checkPower10(6, 0, 1000, 3); - // 10^7 = 10000000 - checkPower10(7, 0, 1000, 4); - // 10^8 = 100000000 - checkPower10(8, 0, 1000, 5); - // 10^9 = 1000000000 - checkPower10(9, 0, 1000, 6); - - // 10^1.5 = 31.622776601683793319988935444327074859 - checkPower10(1.5e37, -37, 3162, -2); - - checkPower10(0.5e37, -37, 3162, -3); - - checkPower10(0.3e37, -37, 1995, -3); - checkPower10(-0.3e37, -37, 5.012531328320802005012531328320802005e37, -38); - } - - function testInterpolatedLookupsPower() external { - // 10^1.55555 = 35.9376769153 - checkPower10(1.55555e37, -37, 35935e37, -40); - // 10^1234.56789 - checkPower10(123456789, -5, 36979e37, 1193); - } - - function boundFloat(int256 x, int256 exponent) internal pure returns (int256, int256) { - exponent = bound(exponent, -76, 76); - vm.assume(LibDecimalFloat.gt(x, exponent, -1e38, 0)); - vm.assume(LibDecimalFloat.lt(x, exponent, type(int256).max, 0)); - return (x, exponent); - } - - /// Test the current range that we can handle power10 over does not revert. - function testNoRevert(int256 x, int256 exponent) external { - address tables = logTables(); - (x, exponent) = boundFloat(x, exponent); - LibDecimalFloat.power10(tables, x, exponent); - } } diff --git a/test/src/lib/LibDecimalFloat.sub.t.sol b/test/src/lib/LibDecimalFloat.sub.t.sol index 5cd68528..90adf34e 100644 --- a/test/src/lib/LibDecimalFloat.sub.t.sol +++ b/test/src/lib/LibDecimalFloat.sub.t.sol @@ -14,15 +14,14 @@ contract LibDecimalFloatSubTest is Test { pure returns (int256, int256) { - return LibDecimalFloat.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + return LibDecimalFloatImplementation.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } function subExternal(Float floatA, Float floatB) external pure returns (Float) { return LibDecimalFloat.sub(floatA, floatB); } - /// Test to verify that stack-based and memory-based implementations produce the same results. - function testSubMem(Float a, Float b) external { + function testSubPacked(Float a, Float b) external { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); try this.subExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( @@ -37,42 +36,4 @@ contract LibDecimalFloatSubTest is Test { this.subExternal(a, b); } } - - /// 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); - - // Minus will just shift the max min value one exponent internally. - (int256 expectedSignedCoefficient, int256 expectedExponent) = - LibDecimalFloat.add(signedCoefficientA, exponentA, -(signedCoefficientB / 10), exponentB + 1); - - assertEq(signedCoefficient, expectedSignedCoefficient); - assertEq(exponent, expectedExponent); - } } diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol new file mode 100644 index 00000000..3a1fab8d --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import { + LibDecimalFloatImplementation, + EXPONENT_MIN, + EXPONENT_MAX, + ADD_MAX_EXPONENT_DIFF +} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {Test} from "forge-std/Test.sol"; + +contract LibDecimalFloatImplementationAddTest is Test { + /// Simple 0 add 0 + /// 0 + 0 = 0 + function testAddZero() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(0, 0, 0, 0); + assertEq(signedCoefficient, 0); + assertEq(exponent, 0); + } + + /// 0 add 0 any exponent + /// 0 + 0 = 0 + function testAddZeroAnyExponent(int128 inputExponent) external pure { + inputExponent = int128(bound(inputExponent, EXPONENT_MIN, EXPONENT_MAX)); + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(0, inputExponent, 0, 0); + assertEq(signedCoefficient, 0); + assertEq(exponent, 0); + } + + /// 0 add 1 + /// 0 + 1 = 1 + function testAddZeroOne() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(0, 0, 1, 0); + assertEq(signedCoefficient, 1); + assertEq(exponent, 0); + } + + /// 1 add 0 + /// 1 + 0 = 1 + function testAddOneZero() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(1, 0, 0, 0); + assertEq(signedCoefficient, 1); + assertEq(exponent, 0); + } + + /// 1 add 1 + /// 1 + 1 = 2 + function testAddOneOneNotNormalized() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(1, 0, 1, 0); + assertEq(signedCoefficient, 2e37); + assertEq(exponent, -37); + } + + function testAddOneOnePreNormalized() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(1e37, -37, 1e37, -37); + assertEq(signedCoefficient, 2e37); + assertEq(exponent, -37); + } + + /// 123456789 add 987654321 + /// 123456789 + 987654321 = 1111111110 + function testAdd123456789987654321() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(123456789, 0, 987654321, 0); + assertEq(signedCoefficient, 1.11111111e38); + assertEq(exponent, -38 + 9); + } + + /// 123456789e9 add 987654321 + /// 123456789e9 + 987654321 = 123456789987654321 + function testAdd123456789e9987654321() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(123456789, 9, 987654321, 0); + assertEq(signedCoefficient, 1.23456789987654321e46); + assertEq(exponent, -46 + 17); + } + + function testGasAddZero() external pure { + LibDecimalFloatImplementation.add(0, 0, 0, 0); + } + + function testGasAddOne() external pure { + LibDecimalFloatImplementation.add(1e37, -37, 1e37, -37); + } + + /// Provided our exponents are in range we should never revert. + function testAddNeverRevert( + 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); + + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (signedCoefficient, exponent); + } + + function testAddingSmallToLargeReturnsLargeFuzz( + int256 signedCoefficientA, + int256 exponentA, + int256 signedCoefficientB, + int256 exponentB + ) public pure { + exponentA = bound(exponentA, EXPONENT_MIN / 10, EXPONENT_MAX / 10); + exponentB = bound(exponentB, EXPONENT_MIN / 10, EXPONENT_MAX / 10); + vm.assume(signedCoefficientA != 0); + vm.assume(signedCoefficientB != 0); + + (int256 normalizedSignedCoefficientA, int256 normalizedExponentA) = + LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); + (int256 expectedSignedCoefficient, int256 expectedExponent) = + LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + + vm.assume(normalizedSignedCoefficientA != 0); + vm.assume(expectedSignedCoefficient != 0); + + vm.assume((expectedExponent - normalizedExponentA) > int256(ADD_MAX_EXPONENT_DIFF)); + + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + assertEq(signedCoefficient, expectedSignedCoefficient); + assertEq(exponent, expectedExponent); + } + + function testAddingSmallToLargeReturnsLargeExamples() external pure { + // Establish a baseline. + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.add(1e37, 0, 1e37, -37); + assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); + assertEq(exponent, -37); + // Show baseline with reversed order. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(1e37, -37, 1e37, 0); + assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); + assertEq(exponent, -37); + + // Show full precision loss. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(1e37, 0, 1e37, -38); + assertEq(signedCoefficient, 1e37); + assertEq(exponent, 0); + // Show same thing again with reversed order. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(1e37, -38, 1e37, 0); + assertEq(signedCoefficient, 1e37); + assertEq(exponent, 0); + + // Same precision loss happens for negative numbers. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(-1e37, 0, -1e37, -38); + assertEq(signedCoefficient, -1e37); + assertEq(exponent, 0); + // Reverse order. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(-1e37, -38, -1e37, 0); + assertEq(signedCoefficient, -1e37); + assertEq(exponent, 0); + + // Only the difference in exponents matters. Show the baseline. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(1e37, -20, 1e37, -57); + assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); + assertEq(exponent, -57); + // Reverse order. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(1e37, -57, 1e37, -20); + assertEq(signedCoefficient, 10000000000000000000000000000000000001e37); + assertEq(exponent, -57); + + // Only the difference in exponents matters. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(1e37, -20, 1e37, -58); + assertEq(signedCoefficient, 1e37); + assertEq(exponent, -20); + // Reverse order. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(1e37, -58, 1e37, -20); + assertEq(signedCoefficient, 1e37); + + // Only the difference in exponents matters. Show negative numbers. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(-1e37, -20, -1e37, -58); + assertEq(signedCoefficient, -1e37); + assertEq(exponent, -20); + // Reverse order. + (signedCoefficient, exponent) = LibDecimalFloatImplementation.add(-1e37, -58, -1e37, -20); + assertEq(signedCoefficient, -1e37); + } + + /// If the exponents are the same and the coefficients are the same, then + /// addition is simply adding the coefficients. + function testAddSameExponentSameCoefficient(int256 signedCoefficientA, int256 signedCoefficientB) external pure { + int256 exponentA; + int256 exponentB; + (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, 0); + (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, 0); + + if (signedCoefficientA == 0 || signedCoefficientB == 0) { + exponentA = 0; + } + exponentB = exponentA; + + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + int256 expectedSignedCoefficient = signedCoefficientA + signedCoefficientB; + int256 expectedExponent = exponentA; + + assertEq(signedCoefficient, expectedSignedCoefficient); + assertEq(exponent, expectedExponent); + } + + /// Adding any zero to any value returns the non-zero value. + function testAddZeroToAnyNonZero(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); + + vm.assume(signedCoefficient != 0); + + (int256 expectedSignedCoefficient, int256 expectedExponent) = (signedCoefficient, exponent); + (int256 signedCoefficientAddZero, int256 exponentAddZero) = + LibDecimalFloatImplementation.add(0, exponentZero, signedCoefficient, exponent); + assertEq(signedCoefficientAddZero, expectedSignedCoefficient); + assertEq(exponentAddZero, expectedExponent); + + // Reverse order. + (signedCoefficientAddZero, exponentAddZero) = + LibDecimalFloatImplementation.add(signedCoefficient, exponent, 0, exponentZero); + assertEq(signedCoefficientAddZero, expectedSignedCoefficient); + assertEq(exponentAddZero, expectedExponent); + } +} diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.divide.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.divide.t.sol new file mode 100644 index 00000000..622ad857 --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.divide.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {Test} from "forge-std/Test.sol"; +import { + LibDecimalFloatImplementation, + EXPONENT_MIN, + EXPONENT_MAX +} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {THREES, ONES} from "../../../lib/LibCommonResults.sol"; + +contract LibDecimalFloatImplementationDivideTest is Test { + function checkDivision( + int256 signedCoefficientA, + int256 exponentA, + int256 signedCoefficientB, + int256 exponentB, + int256 signedCoefficientC, + int256 exponentC + ) internal pure { + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloatImplementation.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + assertEq(signedCoefficient, signedCoefficientC, "coefficient"); + assertEq(exponent, exponentC, "exponent"); + } + + /// 1 / 3 gas by parts 10 + function testDivide1Over3Gas10() external pure { + (int256 c, int256 e) = LibDecimalFloatImplementation.divide(1, 0, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + (c, e) = LibDecimalFloatImplementation.divide(c, e, 3e37, -37); + } + + /// 1 / 3 + function testDivide1Over3() external pure { + checkDivision(1, 0, 3, 0, THREES, -38); + } + + /// - 1 / 3 + function testDivideNegative1Over3() external pure { + checkDivision(-1, 0, 3, 0, -THREES, -38); + } + + /// 1 / 3 gas + function testDivide1Over3Gas0() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.divide(1e37, -37, 3e37, -37); + (signedCoefficient, exponent); + } + + /// 1e18 / 3 + function testDivide1e18Over3() external pure { + checkDivision(1e18, 0, 3, 0, THREES, -20); + } + + /// 10,0 / 1e38,-37 == 1 + function testDivideTenOverOOMs() external pure { + checkDivision(10, 0, 1e38, -37, 1e38, -38); + } + + /// 1e38,-37 / 2,0 == 5 + function testDivideOOMsOverTen() external pure { + checkDivision(1e38, -37, 2, 0, 5e37, -37); + } + + /// 5e37,-37 / 2e37,-37 == 2.5 + function testDivideOOMs5and2() external pure { + checkDivision(5e37, -37, 2e37, -37, 25e37, -38); + } + + /// (1 / 9) / (1 / 3) == 0.333.. + function testDivide1Over9Over1Over3() external pure { + // 1 / 9 + (int256 signedCoefficientA, int256 exponentA) = LibDecimalFloatImplementation.divide(1, 0, 9, 0); + assertEq(signedCoefficientA, ONES); + assertEq(exponentA, -38); + + // 1 / 3 + (int256 signedCoefficientB, int256 exponentB) = LibDecimalFloatImplementation.divide(1, 0, 3, 0); + assertEq(signedCoefficientB, THREES); + assertEq(exponentB, -38); + + // (1 / 9) / (1 / 3) + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloatImplementation.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + assertEq(signedCoefficient, THREES); + assertEq(exponent, -38); + } + + /// forge-config: default.fuzz.runs = 100 + function testUnnormalizedThreesDivision0(int256 exponentA, int256 exponentB) external pure { + exponentA = bound(exponentA, EXPONENT_MIN / 2, EXPONENT_MAX / 2); + exponentB = bound(exponentB, EXPONENT_MIN / 2, EXPONENT_MAX / 2); + + int256 d = 3; + int256 di = 0; + while (true) { + int256 i = 1; + int256 j = -38 - di; + while (true) { + // want to see full precision on the THREES regardless of the + // scale of the numerator and denominator. + checkDivision(i, exponentA, d, exponentB, THREES, exponentA - exponentB + j); + + if (i == 1e76) { + break; + } + + i *= 10; + ++j; + } + + if (d == 3e76) { + break; + } + d *= 10; + ++di; + } + } +} diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.eq.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.eq.t.sol new file mode 100644 index 00000000..3842cdd7 --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.eq.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {Test} from "forge-std/Test.sol"; +import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; + +contract LibDecimalFloatImplementationEqTest is Test { + function testEqGasDifferentSigns() external pure { + LibDecimalFloatImplementation.eq(1, 0, -1, 0); + } + + function testEqGasAZero() external pure { + LibDecimalFloatImplementation.eq(0, 0, 1, 0); + } + + function testEqGasBZero() external pure { + LibDecimalFloatImplementation.eq(1, 0, 0, 0); + } + + function testEqGasBothZero() external pure { + LibDecimalFloatImplementation.eq(0, 0, 0, 0); + } + + function testEqGasExponentDiffOverflow() external pure { + LibDecimalFloatImplementation.eq(1, type(int256).max, 1, type(int256).min); + } + + /// if xeX == yeY, then x / y == 10^(X - Y) || y / x == 10^(Y - X) + function testEqXEqY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + bool eq = LibDecimalFloatImplementation.eq(x, exponentX, y, exponentY); + + if (eq) { + if (x == y) { + assertTrue(exponentX == exponentY || x == 0); + } else if (y > x) { + assertTrue(exponentY < exponentX, "y > x but exponentY >= exponentX"); + assertTrue(exponentX - exponentY < 77, "y > x but exponentX - exponentY >= 77"); + assertEq(x / y, int256(10 ** uint256(exponentX - exponentY)), "y > x but x / y != 10^(X - Y)"); + assertEq(x % y, 0, "y > x but x % y != 0"); + } else { + assertTrue(exponentX < exponentY, "x < y but exponentX >= exponentY"); + assertTrue(exponentY - exponentX < 77, "x < y but exponentY - exponentX >= 77"); + assertEq(y / x, int256(10 ** uint256(exponentY - exponentX)), "x < y but y / x != 10^(Y - X)"); + assertEq(y % x, 0, "x < y but y % x != 0"); + } + } else { + if (x == y) { + assertTrue(exponentX != exponentY); + } + } + } + + /// xeX != yeY if x != y + function testEqXNotY(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + vm.assume(x != y); + bool eq = LibDecimalFloatImplementation.eq(x, exponentX, y, exponentY); + assertTrue(!eq); + } + + /// xeX != xeY if X != Y && x != 0 + function testEqXEAnyVsXEAny(int256 x, int256 exponentX, int256 exponentY) external pure { + vm.assume(x != 0); + bool eq = LibDecimalFloatImplementation.eq(x, exponentX, x, exponentY); + + assertEq(eq, exponentX == exponentY); + + // Reverse the order. + eq = LibDecimalFloatImplementation.eq(x, exponentY, x, exponentX); + assertEq(eq, exponentX == exponentY); + } + + /// xeX == xeY if x == 0 + function testEqZero(int256 exponentX, int256 exponentY) external pure { + bool eq = LibDecimalFloatImplementation.eq(0, exponentX, 0, exponentY); + assertTrue(eq); + } + + function testEqNotReverts(int256 x, int256 exponentX, int256 y, int256 exponentY) external pure { + LibDecimalFloatImplementation.eq(x, exponentX, y, exponentY); + } + + /// x == x + function testEqX(int256 x) external pure { + bool eq = LibDecimalFloatImplementation.eq(x, 0, x, 0); + assertTrue(eq); + } + + /// xeX == xeX + function testEqOneEAny(int256 x, int256 exponent) external pure { + bool eq = LibDecimalFloatImplementation.eq(x, exponent, x, exponent); + assertTrue(eq); + } + + function testEqReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + { + bool actual = LibDecimalFloatImplementation.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + bool expected = LibDecimalFloatSlow.eqSlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + assertEq(actual, expected); + } +} diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol new file mode 100644 index 00000000..2a46baf5 --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; +import { + LibDecimalFloatImplementation, + EXPONENT_MIN, + EXPONENT_MAX +} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; + +contract LibDecimalFloatImplementationInvTest is Test { + /// Compare reference. + function testInvReference(int256 signedCoefficient, int256 exponent) external pure { + vm.assume(signedCoefficient != 0); + exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); + + (int256 outputSignedCoefficient, int256 outputExponent) = + LibDecimalFloatImplementation.inv(signedCoefficient, exponent); + (int256 referenceSignedCoefficient, int256 referenceExponent) = + LibDecimalFloatSlow.invSlow(signedCoefficient, exponent); + + assertEq(outputSignedCoefficient, referenceSignedCoefficient, "coefficient"); + assertEq(outputExponent, referenceExponent, "exponent"); + } + + function testInvGas0() external pure { + (int256 outputSignedCoefficient, int256 outputExponent) = LibDecimalFloatImplementation.inv(3e37, -37); + (outputSignedCoefficient, outputExponent); + } + + function testInvSlowGas0() external pure { + (int256 outputSignedCoefficient, int256 outputExponent) = LibDecimalFloatSlow.invSlow(3e37, -37); + (outputSignedCoefficient, outputExponent); + } +} diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol new file mode 100644 index 00000000..e09ee0e0 --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.log10.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {LogTest, console2} from "../../../abstract/LogTest.sol"; + +contract LibDecimalFloatImplementationLog10Test is LogTest { + function checkLog10( + int256 signedCoefficient, + int256 exponent, + int256 expectedSignedCoefficient, + int256 expectedExponent + ) internal { + address tables = logTables(); + uint256 aGas = gasleft(); + (int256 actualSignedCoefficient, int256 actualExponent) = + LibDecimalFloatImplementation.log10(tables, signedCoefficient, exponent); + uint256 bGas = gasleft(); + console2.log("%d %d Gas used: %d", uint256(signedCoefficient), uint256(exponent), aGas - bGas); + assertEq(actualSignedCoefficient, expectedSignedCoefficient); + assertEq(actualExponent, expectedExponent); + } + + function testExactLogs() external { + checkLog10(1, 0, 0, 0); + checkLog10(10, 0, 1, 0); + checkLog10(100, 0, 2, 0); + checkLog10(1000, 0, 3, 0); + checkLog10(10000, 0, 4, 0); + checkLog10(1e37, -37, 0, 0); + } + + function testExactLookups() external { + checkLog10(1001, 0, 3.0004e41, -41); + checkLog10(100.1e1, -1, 2.0004e41, -41); + checkLog10(10.01e2, -2, 1.0004e41, -41); + checkLog10(1.001e3, -3, 0.0004e38, -38); + + checkLog10(10.02e2, -2, 1.0009e41, -41); + checkLog10(10.99e2, -2, 1.0411e39, -39); + + checkLog10(6566, 0, 3.8173e38, -38); + } + + function testInterpolatedLookups() external { + checkLog10(10.015e3, -3, 1.00065e41, -41); + } + + function testSub1() external { + checkLog10(0.1001e4, -4, -0.9996e38, -38); + } +} diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.minus.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.minus.t.sol new file mode 100644 index 00000000..605da7b7 --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.minus.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import { + LibDecimalFloatImplementation, + EXPONENT_MIN, + EXPONENT_MAX +} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {Test} from "forge-std/Test.sol"; + +contract LibDecimalFloatImplementationMinusTest 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) = + LibDecimalFloatImplementation.minus(signedCoefficient, exponent); + (int256 expectedSignedCoefficient, int256 expectedExponent) = + LibDecimalFloatImplementation.sub(0, exponentZero, signedCoefficient, exponent); + + assertEq(signedCoefficientMinus, expectedSignedCoefficient); + assertEq(exponentMinus, expectedExponent); + } +} diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.multiply.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.multiply.t.sol new file mode 100644 index 00000000..393c9ffd --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.multiply.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import { + LibDecimalFloatImplementation, + EXPONENT_MIN, + EXPONENT_MAX, + NORMALIZED_ZERO_SIGNED_COEFFICIENT, + NORMALIZED_ZERO_EXPONENT +} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {Test} from "forge-std/Test.sol"; +import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; + +contract LibDecimalFloatImplementationMultiplyTest is Test { + /// Simple 0 multiply 0 + /// 0 * 0 = 0 + function testMultiplyZero0Exponent() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(0, 0, 0, 0); + assertEq(signedCoefficient, NORMALIZED_ZERO_SIGNED_COEFFICIENT); + assertEq(exponent, NORMALIZED_ZERO_EXPONENT); + } + + /// 0 multiply 0 any exponent + /// 0 * 0 = 0 + function testMultiplyZeroAnyExponent(int64 exponentA, int64 exponentB) external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(0, exponentA, 0, exponentB); + assertEq(signedCoefficient, NORMALIZED_ZERO_SIGNED_COEFFICIENT); + assertEq(exponent, NORMALIZED_ZERO_EXPONENT); + } + + /// 0 multiply 1 + /// 0 * 1 = 0 + function testMultiplyZeroOne() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(0, 0, 1, 0); + assertEq(signedCoefficient, NORMALIZED_ZERO_SIGNED_COEFFICIENT); + assertEq(exponent, NORMALIZED_ZERO_EXPONENT); + } + + /// 1 multiply 0 + /// 1 * 0 = 0 + function testMultiplyOneZero() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(1, 0, 0, 0); + assertEq(signedCoefficient, NORMALIZED_ZERO_SIGNED_COEFFICIENT); + assertEq(exponent, NORMALIZED_ZERO_EXPONENT); + } + + /// 1 multiply 1 + /// 1 * 1 = 1 + function testMultiplyOneOne() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(1, 0, 1, 0); + assertEq(signedCoefficient, 1); + assertEq(exponent, 0); + } + + /// 123456789 multiply 987654321 + /// 123456789 * 987654321 = 121932631112635269 + function testMultiply123456789987654321() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(123456789, 0, 987654321, 0); + assertEq(signedCoefficient, 121932631112635269); + assertEq(exponent, 0); + } + + /// 123456789 multiply 987654321 with exponents + /// 123456789 * 987654321 = 121932631112635269 + function testMultiply123456789987654321WithExponents(int128 exponentA, int128 exponentB) external pure { + exponentA = int128(bound(exponentA, -127, 127)); + exponentB = int128(bound(exponentB, -127, 127)); + + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloatImplementation.multiply(123456789, exponentA, 987654321, exponentB); + assertEq(signedCoefficient, 121932631112635269); + assertEq(exponent, exponentA + exponentB); + } + + /// 1e18 * 1e-19 = 1e-1 + function testMultiply1e181e19() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(1, 18, 1, -19); + assertEq(signedCoefficient, 1); + assertEq(exponent, -1); + } + + function testMultiplyGasZero() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(0, 0, 0, 0); + (signedCoefficient, exponent); + } + + function testMultiplyGasOne() external pure { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.multiply(1e37, -37, 1e37, -37); + (signedCoefficient, exponent); + } + + function testMultiplyNotRevertAnyExpectation( + int256 signedCoefficientA, + int256 exponentA, + int256 signedCoefficientB, + int256 exponentB + ) external pure { + exponentA = bound(exponentA, EXPONENT_MIN, EXPONENT_MAX); + exponentB = bound(exponentB, EXPONENT_MIN, EXPONENT_MAX); + (int256 signedCoefficient, int256 exponent) = + LibDecimalFloatImplementation.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (int256 expectedSignedCoefficient, int256 expectedExponent) = + LibDecimalFloatSlow.multiplySlow(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + assertEq(signedCoefficient, expectedSignedCoefficient); + assertEq(exponent, expectedExponent); + } +} diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol new file mode 100644 index 00000000..35a8600e --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {LogTest} from "../../../abstract/LogTest.sol"; + +import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; + +contract LibDecimalFloatImplementationPower10Test is LogTest { + function checkPower10( + int256 signedCoefficient, + int256 exponent, + int256 expectedSignedCoefficient, + int256 expectedExponent + ) internal { + address tables = logTables(); + (int256 actualSignedCoefficient, int256 actualExponent) = + LibDecimalFloatImplementation.power10(tables, signedCoefficient, exponent); + assertEq(actualSignedCoefficient, expectedSignedCoefficient, "signedCoefficient"); + assertEq(actualExponent, expectedExponent, "exponent"); + } + + function testExactPowers() external { + // 10^1 = 10 + checkPower10(1e37, -37, 1000, -2); + // 10^10 = 10000000000 + checkPower10(10e37, -37, 1000, 7); + checkPower10(1, 2, 1000, 97); + checkPower10(1, 3, 1000, 997); + checkPower10(1, 4, 1000, 9997); + } + + function testExactLookups() external { + // 10^2 = 100 + checkPower10(2, 0, 1000, -1); + // 10^3 = 1000 + checkPower10(3, 0, 1000, 0); + // 10^4 = 10000 + checkPower10(4, 0, 1000, 1); + // 10^5 = 100000 + checkPower10(5, 0, 1000, 2); + // 10^6 = 1000000 + checkPower10(6, 0, 1000, 3); + // 10^7 = 10000000 + checkPower10(7, 0, 1000, 4); + // 10^8 = 100000000 + checkPower10(8, 0, 1000, 5); + // 10^9 = 1000000000 + checkPower10(9, 0, 1000, 6); + + // 10^1.5 = 31.622776601683793319988935444327074859 + checkPower10(1.5e37, -37, 3162, -2); + + checkPower10(0.5e37, -37, 3162, -3); + + checkPower10(0.3e37, -37, 1995, -3); + checkPower10(-0.3e37, -37, 5.012531328320802005012531328320802005e37, -38); + } + + function testInterpolatedLookupsPower() external { + // 10^1.55555 = 35.9376769153 + checkPower10(1.55555e37, -37, 35935e37, -40); + // 10^1234.56789 + checkPower10(123456789, -5, 36979e37, 1193); + } + + // function boundFloat(int256 x, int256 exponent) internal pure returns (int256, int256) { + // exponent = bound(exponent, -76, 76); + // vm.assume(LibDecimalFloat.gt(x, exponent, -1e38, 0)); + // vm.assume(LibDecimalFloat.lt(x, exponent, type(int256).max, 0)); + // return (x, exponent); + // } + + /// Test the current range that we can handle power10 over does not revert. + function testNoRevert(int256 x, int256 exponent) external { + address tables = logTables(); + // (x, exponent) = boundFloat(x, exponent); + LibDecimalFloatImplementation.power10(tables, x, exponent); + } +} diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.sub.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.sub.t.sol new file mode 100644 index 00000000..aa72fd4e --- /dev/null +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.sub.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.25; + +import {Test} from "forge-std/Test.sol"; +import { + LibDecimalFloatImplementation, + EXPONENT_MAX, + EXPONENT_MIN +} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; + +contract LibDecimalFloatImplementationSubTest 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) = + LibDecimalFloatImplementation.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (int256 expectedSignedCoefficient, int256 expectedExponent) = + LibDecimalFloatImplementation.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) = + LibDecimalFloatImplementation.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + + // Minus will just shift the max min value one exponent internally. + (int256 expectedSignedCoefficient, int256 expectedExponent) = + LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, -(signedCoefficientB / 10), exponentB + 1); + + assertEq(signedCoefficient, expectedSignedCoefficient); + assertEq(exponent, expectedExponent); + } +} From 0094e7d4ef8ee6e277c6fc75eb73042528da3483 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sat, 19 Apr 2025 14:52:22 +0400 Subject: [PATCH 04/13] fix test --- .../LibDecimalFloatImplementation.sol | 31 +++++++++---------- ...ibDecimalFloatImplementation.power10.t.sol | 23 ++++++++------ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index a1fd3090..6a64b565 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -136,8 +136,8 @@ library LibDecimalFloatImplementation { // cannot overflow, so this will always succeed, provided the // exponents are not out of bounds. if (didOverflow) { - (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + (signedCoefficientA, exponentA) = normalize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = normalize(signedCoefficientB, exponentB); return multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); } return (signedCoefficient, exponent); @@ -200,8 +200,8 @@ library LibDecimalFloatImplementation { returns (int256, int256) { unchecked { - (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + (signedCoefficientA, exponentA) = normalize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = normalize(signedCoefficientB, exponentB); int256 signedCoefficient = (signedCoefficientA * 1e38) / signedCoefficientB; int256 exponent = exponentA - exponentB - 38; @@ -289,8 +289,8 @@ library LibDecimalFloatImplementation { // Normalizing A and B gives us similar coefficients, which simplifies // detecting when their exponents are too far apart to add without // simply ignoring one of them. - (signedCoefficientA, exponentA) = LibDecimalFloatImplementation.normalize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB) = LibDecimalFloatImplementation.normalize(signedCoefficientB, exponentB); + (signedCoefficientA, exponentA) = normalize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = normalize(signedCoefficientB, exponentB); // We want A to represent the larger exponent. If this is not the case // then swap them. @@ -366,14 +366,14 @@ library LibDecimalFloatImplementation { returns (bool) { (signedCoefficientA, signedCoefficientB) = - LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB); return signedCoefficientA == signedCoefficientB; } /// Inverts a float. Equivalent to `1 / x` with modest gas optimizations. function inv(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); + (signedCoefficient, exponent) = normalize(signedCoefficient, exponent); signedCoefficient = 1e75 / signedCoefficient; exponent = -exponent - 75; @@ -399,7 +399,7 @@ library LibDecimalFloatImplementation { { unchecked { { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.normalize(signedCoefficient, exponent); + (signedCoefficient, exponent) = normalize(signedCoefficient, exponent); if (signedCoefficient <= 0) { if (signedCoefficient == 0) { @@ -471,7 +471,7 @@ library LibDecimalFloatImplementation { } if (interpolate) { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.unitLinearInterpolation( + (signedCoefficient, exponent) = unitLinearInterpolation( signedCoefficient, exponent, x1Coefficient, exponent, -39, y1Coefficient, y2Coefficient, -38 ); } else { @@ -516,15 +516,15 @@ library LibDecimalFloatImplementation { // Table lookup. (int256 characteristicCoefficient, int256 mantissaCoefficient) = - LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent); + characteristicMantissa(signedCoefficient, exponent); int256 characteristicExponent = exponent; { - (int256 idx, bool interpolate) = LibDecimalFloatImplementation.mantissa4(mantissaCoefficient, exponent); + (int256 idx, bool interpolate) = mantissa4(mantissaCoefficient, exponent); (int256 y1Coefficient, int256 y2Coefficient) = - LibDecimalFloatImplementation.lookupAntilogTableY1Y2(tablesDataContract, uint256(idx), interpolate); + lookupAntilogTableY1Y2(tablesDataContract, uint256(idx), interpolate); if (interpolate) { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.unitLinearInterpolation( + (signedCoefficient, exponent) = unitLinearInterpolation( mantissaCoefficient, exponent, idx, -4, -41, y1Coefficient, y2Coefficient, -4 ); } else { @@ -535,8 +535,7 @@ library LibDecimalFloatImplementation { return ( signedCoefficient, - 1 + exponent - + LibDecimalFloatImplementation.withTargetExponent(characteristicCoefficient, characteristicExponent, 0) + 1 + exponent + withTargetExponent(characteristicCoefficient, characteristicExponent, 0) ); } } diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol index 35a8600e..ed64f1e8 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol @@ -4,8 +4,11 @@ pragma solidity =0.8.25; import {LogTest} from "../../../abstract/LogTest.sol"; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; +import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; contract LibDecimalFloatImplementationPower10Test is LogTest { + using LibDecimalFloat for Float; + function checkPower10( int256 signedCoefficient, int256 exponent, @@ -63,17 +66,17 @@ contract LibDecimalFloatImplementationPower10Test is LogTest { checkPower10(123456789, -5, 36979e37, 1193); } - // function boundFloat(int256 x, int256 exponent) internal pure returns (int256, int256) { - // exponent = bound(exponent, -76, 76); - // vm.assume(LibDecimalFloat.gt(x, exponent, -1e38, 0)); - // vm.assume(LibDecimalFloat.lt(x, exponent, type(int256).max, 0)); - // return (x, exponent); - // } + function boundFloat(int224 x, int32 exponent) internal pure returns (int224, int32) { + exponent = int32(bound(exponent, -76, 76)); + Float a = LibDecimalFloat.packLossless(x, exponent); + vm.assume(a.gt(LibDecimalFloat.packLossless(-1e38, 0))); + vm.assume(a.lt(LibDecimalFloat.packLossless(type(int224).max, 0))); + return (x, exponent); + } /// Test the current range that we can handle power10 over does not revert. - function testNoRevert(int256 x, int256 exponent) external { - address tables = logTables(); - // (x, exponent) = boundFloat(x, exponent); - LibDecimalFloatImplementation.power10(tables, x, exponent); + function testNoRevert(int224 x, int32 exponent) external { + (x, exponent) = boundFloat(x, exponent); + LibDecimalFloatImplementation.power10(logTables(), x, exponent); } } From 2c12f2555303303aee5dda5fd3d4e81830a5002d Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 20 Apr 2025 18:46:56 +0400 Subject: [PATCH 05/13] wip on fixing round trip power --- src/generated/LogTables.pointers.sol | 20 +++---- src/lib/LibDecimalFloat.sol | 31 +++++++++++ .../LibDecimalFloatImplementation.sol | 24 ++++++++- src/lib/table/LibLogTable.sol | 30 ++++++----- test/src/lib/LibDecimalFloat.power.t.sol | 54 ++++++++++++++++++- test/src/lib/LibDecimalFloat.power10.t.sol | 24 +++++---- test/src/lib/LibDecimalFloat.sub.t.sol | 6 +-- .../lib/format/LibFormatDecimalFloat.t.sol | 4 +- ...ibDecimalFloatImplementation.power10.t.sol | 2 + 9 files changed, 154 insertions(+), 41 deletions(-) diff --git a/src/generated/LogTables.pointers.sol b/src/generated/LogTables.pointers.sol index 706826fe..fe21c629 100644 --- a/src/generated/LogTables.pointers.sol +++ b/src/generated/LogTables.pointers.sol @@ -12,21 +12,21 @@ pragma solidity =0.8.25; bytes32 constant BYTECODE_HASH = bytes32(0x0000000000000000000000000000000000000000000000000000000000000000); /// @dev Log tables. -bytes constant LOG_TABLES = - hex"0000002b0056008000aa80d480fd8126814e8176019e01c501ec02130239825f828582aa82cf82f30318033c0360038303a603c983ec840e843084520473049504b604d704f78517853785578577859605b505d405f306118630864e866c868986a786c406e106fe071a07370753076f878b87a787c387de07f90814082f084a0864887f889988b388cd88e70900091a0933094c0965097e899789b089c889e109f90a110a290a410a588a708a878a9e8ab68acd0ae40afa0b110b280b3e8b548b6b8b818b978bad0bc20bd80bee0c030c180c2e0c430c580c6d0c810c960cab0cbf0cd40ce80cfc0d110d250d390d4c0d600d740d880d9b0dae0dc20dd50de80dfb0e0e0e210e340e470e5a0e6c0e7f0e910ea30eb60ec80eda0eec0efe0f100f220f340f450f570f690f7a0f8b0f9d0fae0fbf0fd00fe10ff2100310141025103610461057106810781088109910a910b910ca10da10ea10fa110a111a1129113911491158116811781187119611a611b511c411d411e311f212011210121f122e123d124b125a126912781286129512a312b212c012ce12dd12eb12f913071316132413321340134e135b136913771385139313a013ae13bb13c913d713e413f113ff140c1419142714341441144e145b146814751482148f149c14a914b614c314d014dc14e914f61502150f151b152815341541154d155915661572157e158a159715a315af15bb15c715d315df15eb15f71603160f161a16261632163e164916551661166c16781683168f169a16a616b116bd16c816d316df16ea16f51700170b17171722172d17381743174e17591764176f177a1785178f179a17a517b017bb17c517d017db17e517f017fa18051810181a1824182f18391844184e18581863186d18771882188c189618a018aa18b518bf18c918d318dd18e718f118fb1905190f19191923192c19361940194a1954195d19671971197a1984198e199719a119ab19b419be19c719d119da19e419ed19f61a001a091a131a1c1a251a2e1a381a411a4a1a531a5d1a661a6f1a781a811a8a1a931a9c1aa51aae1ab71ac01ac91ad21adb1ae41aed1af61aff1b081b101b191b221b2b1b341b3c1b451b4e1b561b5f1b681b701b791b821b8a1b931b9b1ba41bac1bb51bbd1bc61bce1bd61bdf1be71bf01bf81c001c091c111c191c221c2a1c321c3a1c431c4b1c531c5b1c631c6b1c741c7c1c841c8c1c941c9c1ca41cac1cb41cbc1cc41ccc1cd41cdc1ce41cec1cf41cfb1d031d0b1d131d1b1d231d2a1d321d3a1d421d491d511d591d601d681d701d771d7f1d871d8e1d961d9e1da51dad1db41dbc1dc31dcb1dd21dda1de11de91df01df81dff1e061e0e1e151e1d1e241e2b1e331e3a1e411e481e501e571e5e1e661e6d1e741e7b1e821e8a1e911e981e9f1ea61ead1eb41ebc1ec31eca1ed11ed81edf1ee61eed1ef41efb1f021f091f101f171f1e1f251f2c1f331f391f401f471f4e1f551f5c1f631f691f701f771f7e1f851f8b1f921f991fa01fa61fad1fb41fba1fc11fc81fce1fd51fdc1fe21fe91ff01ff61ffd2003200a20112017201e2024202b20312038203e2045204b20522058205f2065206b20722078207f2085208b20922098209f20a520ab20b220b820be20c420cb20d120d720de20e420ea20f020f720fd21032109210f2116211c21222128212e2134213a21412147214d21532159215f2165216b21712177217d21832189218f2195219b21a121a721ad21b321b921bf21c521cb21d121d721dd21e321e921ee21f421fa22002206220c22122217221d22232229222f2234223a22402246224b22512257225d22622268226e22742279227f2285228a22902296229b22a122a722ac22b222b722bd22c322c822ce22d322d922df22e422ea22ef22f522fa23002305230b23102316231b23212326232c23312337233c23412347234c23522357235d23622367236d23722377237d23822388238d23922398239d23a223a823ad23b223b723bd23c223c723cd23d223d723dc23e223e723ec23f123f623fc24012406240b24102416241b24202425242a242f2435243a243f24442449244e24532458245d24632468246d24722477247c24812486248b24902495249a249f24a424a924ae24b324b824bd24c224c724cc24d124d624db24e024e524ea24ef24f424f924fd25022507250c25112516251b252025252529252e25332538253d25422546254b25502555255a255e25632568256d25722576257b258025852589258e25932598259c25a125a625ab25af25b425b925bd25c225c725cb25d025d525d925de25e325e725ec25f125f525fa25ff26032608260d26112616261a261f26232628262d26312636263a263f26432648264d26512656265a265f26632668266c26712675267a267e26832687268c269026952699269e26a226a626ab26af26b426b826bd26c126c626ca26ce26d326d726dc26e026e426e926ed26f126f626fa26ff27032707270c"; +bytes constant LOG_TABLES + = hex"0000002b0056008000aa80d480fd8126814e8176019e01c501ec02130239825f828582aa82cf82f30318033c0360038303a603c983ec840e843084520473049504b604d704f78517853785578577859605b505d405f306118630864e866c868986a786c406e106fe071a07370753076f878b87a787c387de07f90814082f084a0864887f889988b388cd88e70900091a0933094c0965097e899789b089c889e109f90a110a290a410a588a708a878a9e8ab68acd0ae40afa0b110b280b3e8b548b6b8b818b978bad0bc20bd80bee0c030c180c2e0c430c580c6d0c810c960cab0cbf0cd40ce80cfc0d110d250d390d4c0d600d740d880d9b0dae0dc20dd50de80dfb0e0e0e210e340e470e5a0e6c0e7f0e910ea30eb60ec80eda0eec0efe0f100f220f340f450f570f690f7a0f8b0f9d0fae0fbf0fd00fe10ff2100310141025103610461057106810781088109910a910b910ca10da10ea10fa110a111a1129113911491158116811781187119611a611b511c411d411e311f212011210121f122e123d124b125a126912781286129512a312b212c012ce12dd12eb12f913071316132413321340134e135b136913771385139313a013ae13bb13c913d713e413f113ff140c1419142714341441144e145b146814751482148f149c14a914b614c314d014dc14e914f61502150f151b152815341541154d155915661572157e158a159715a315af15bb15c715d315df15eb15f71603160f161a16261632163e164916551661166c16781683168f169a16a616b116bd16c816d316df16ea16f51700170b17171722172d17381743174e17591764176f177a1785178f179a17a517b017bb17c517d017db17e517f017fa18051810181a1824182f18391844184e18581863186d18771882188c189618a018aa18b518bf18c918d318dd18e718f118fb1905190f19191923192c19361940194a1954195d19671971197a1984198e199719a119ab19b419be19c719d119da19e419ed19f61a001a091a131a1c1a251a2e1a381a411a4a1a531a5d1a661a6f1a781a811a8a1a931a9c1aa51aae1ab71ac01ac91ad21adb1ae41aed1af61aff1b081b101b191b221b2b1b341b3c1b451b4e1b561b5f1b681b701b791b821b8a1b931b9b1ba41bac1bb51bbd1bc61bce1bd61bdf1be71bf01bf81c001c091c111c191c221c2a1c321c3a1c431c4b1c531c5b1c631c6b1c741c7c1c841c8c1c941c9c1ca41cac1cb41cbc1cc41ccc1cd41cdc1ce41cec1cf41cfb1d031d0b1d131d1b1d231d2a1d321d3a1d421d491d511d591d601d681d701d771d7f1d871d8e1d961d9e1da51dad1db41dbc1dc31dcb1dd21dda1de11de91df01df81dff1e061e0e1e151e1d1e241e2b1e331e3a1e411e481e501e571e5e1e661e6d1e741e7b1e821e8a1e911e981e9f1ea61ead1eb41ebc1ec31eca1ed11ed81edf1ee61eed1ef41efb1f021f091f101f171f1e1f251f2c1f331f391f401f471f4e1f551f5c1f631f691f701f771f7e1f851f8b1f921f991fa01fa61fad1fb41fba1fc11fc81fce1fd51fdc1fe21fe91ff01ff61ffd2003200a20112017201e2024202b20312038203e2045204b20522058205f2065206b20722078207f2085208b20922098209f20a520ab20b220b820be20c420cb20d120d720de20e420ea20f020f720fd21032109210f2116211c21222128212e2134213a21412147214d21532159215f2165216b21712177217d21832189218f2195219b21a121a721ad21b321b921bf21c521cb21d121d721dd21e321e921ee21f421fa22002206220c22122217221d22232229222f2234223a22402246224b22512257225d22622268226e22742279227f2285228a22902296229b22a122a722ac22b222b722bd22c322c822ce22d322d922df22e422ea22ef22f522fa23002305230b23102316231b23212326232c23312337233c23412347234c23522357235d23622367236d23722377237d23822388238d23922398239d23a223a823ad23b223b723bd23c223c723cd23d223d723dc23e223e723ec23f123f623fc24012406240b24102416241b24202425242a242f2435243a243f24442449244e24532458245d24632468246d24722477247c24812486248b24902495249a249f24a424a924ae24b324b824bd24c224c724cc24d124d624db24e024e524ea24ef24f424f924fd25022507250c25112516251b252025252529252e25332538253d25422546254b25502555255a255e25632568256d25722576257b258025852589258e25932598259c25a125a625ab25af25b425b925bd25c225c725cb25d025d525d925de25e325e725ec25f125f525fa25ff26032608260d26112616261a261f26232628262d26312636263a263f26432648264d26512656265a265f26632668266c26712675267a267e26832687268c269026952699269e26a226a626ab26af26b426b826bd26c126c626ca26ce26d326d726dc26e026e426e926ed26f126f626fa26ff27032707270c"; /// @dev Log tables small. -bytes constant LOG_TABLES_SMALL = - hex"0004090d11151a1e22260004080c0f13171b1f230003070b0e1215191c200003070a0d1014171a1e000306090c0f1215181c000306090b0e1114171a000305080b0e10131618000305080a0d0f12141700020507090c0e10131500020407090b0d10121400020406080b0d0f111300020406080a0c0e101200020406080a0c0e0f110002040607090b0d0f110002040507090b0c0e100002030507090a0c0e0f0002030507080a0b0d0f000203050608090b0d0e000203050608090b0c0e000103040607090a0c0d000103040607090a0b0d000103040607080a0b0c00010304050708090b0c00010304050608090a0c00010304050608090a0b00010204050607090a0b00010204050607080a0b0001020305060708090a0001020305060708090a0001020304050708090a0001020304050608090a0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060707080001020304050506070800010203040405060708000102030404050607080001020303040506070800010203030405060708000102020304050607070001020203040506060700010202030405060607000102020304050506070001020203040505060700010202030405050607000101020304040506070001010203040405060700010102030404050606000101020304040506060001010203030405060600010102030304050506000101020303040505060001010203030405050600010102030304050506000101020303040505060001010203030404050600010102020304040506000101020203040405060001010202030404050500010102020304040505000101020203040405050001010202030404050500010102020303040505000101020203030405050001010202030304040500010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304040500000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030304"; +bytes constant LOG_TABLES_SMALL + = hex"0004090d11151a1e22260004080c0f13171b1f230003070b0e1215191c200003070a0d1014171a1e000306090c0f1215181c000306090b0e1114171a000305080b0e10131618000305080a0d0f12141700020507090c0e10131500020407090b0d10121400020406080b0d0f111300020406080a0c0e101200020406080a0c0e0f110002040607090b0d0f110002040507090b0c0e100002030507090a0c0e0f0002030507080a0b0d0f000203050608090b0d0e000203050608090b0c0e000103040607090a0c0d000103040607090a0b0d000103040607080a0b0c00010304050708090b0c00010304050608090a0c00010304050608090a0b00010204050607090a0b00010204050607080a0b0001020305060708090a0001020305060708090a0001020304050708090a0001020304050608090a0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060707080001020304050506070800010203040405060708000102030404050607080001020303040506070800010203030405060708000102020304050607070001020203040506060700010202030405060607000102020304050506070001020203040505060700010202030405050607000101020304040506070001010203040405060700010102030404050606000101020304040506060001010203030405060600010102030304050506000101020303040505060001010203030405050600010102030304050506000101020303040505060001010203030404050600010102020304040506000101020203040405060001010202030404050500010102020304040505000101020203040405050001010202030404050500010102020303040505000101020203030405050001010202030304040500010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304040500000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030304"; /// @dev Log tables small alt. -bytes constant LOG_TABLES_SMALL_ALT = - hex"0004080c1014181c20250004070b0f13161a1e210003070a0e1114181b1f0003070a0c101316191d000306090c0f1114171a000305080b0e10131619000305080a0d0f121517000205070a0c0f11131600020507090b0e10121500020406080b0d0f1113"; +bytes constant LOG_TABLES_SMALL_ALT + = hex"0004080c1014181c20250004070b0f13161a1e210003070a0e1114181b1f0003070a0c101316191d000306090c0f1114171a000305080b0e10131619000305080a0d0f121517000205070a0c0f11131600020507090b0e10121500020406080b0d0f1113"; /// @dev Anti log tables. -bytes constant ANTI_LOG_TABLES = - hex"03e803ea03ed03ef03f103f403f603f803fb03fd03ff0402040404060409040b040e0410041204150417041a041c041e0421042304260428042b042d04300432043404370439043c043e0441044304460448044b044e0450045304550458045a045d045f046204650467046a046c046f047204740477047a047c047f0481048404870489048c048f049104940497049a049c049f04a204a504a704aa04ad04af04b204b504b804bb04bd04c004c304c604c904cb04ce04d104d404d704da04dd04df04e204e504e804eb04ee04f104f404f704fa04fc04ff050205050508050b050e051105140517051a051d0520052305260529052c052f053205360539053c053f054205450548054b054e055105550558055b055e056105640568056b056e057105740578057b057e058105850588058b058e059205950598059b059f05a205a505a905ac05af05b305b605ba05bd05c005c405c705cb05ce05d105d505d805dc05df05e305e605ea05ed05f105f405f805fb05ff060206060609060d061006140618061b061f06220626062a062d063106350638063c064006430647064b064e06520656065a065d066106650669066c067006740678067c067f06830687068b068f06930697069a069e06a206a606aa06ae06b206b606ba06be06c206c606ca06ce06d206d606da06de06e206e606ea06ee06f206f606fa06ff07030707070b070f07130718071c072007240728072d073107350739073e07420746074a074f07530757075c076007640769076d07710776077a077f07830788078c079007950799079e07a207a707ab07b007b407b907be07c207c707cb07d007d407d907de07e207e707ec07f007f507fa07fe08030808080d08110816081b082008240829082e08330838083d08410846084b08500855085a085f08640869086e08730878087d08820887088c08910896089b08a008a508aa08af08b408ba08bf08c408c908ce08d308d908de08e308e808ee08f308f808fd09030908090d09130918091d09230928092e09330938093e09430949094e09540959095f0964096a096f0975097b09800986098b09910997099c09a209a809ad09b309b909bf09c409ca09d009d609db09e109e709ed09f309f909ff0a040a0a0a100a160a1c0a220a280a2e0a340a3a0a400a460a4c0a520a590a5f0a650a6b0a710a770a7d0a840a8a0a900a960a9c0aa30aa90aaf0ab60abc0ac20ac90acf0ad50adc0ae20ae90aef0af50afc0b020b090b0f0b160b1c0b230b2a0b300b370b3d0b440b4b0b510b580b5f0b650b6c0b730b7a0b800b870b8e0b950b9c0ba30ba90bb00bb70bbe0bc50bcc0bd30bda0be10be80bef0bf60bfd0c040c0b0c120c190c210c280c2f0c360c3d0c450c4c0c530c5a0c620c690c700c780c7f0c860c8e0c950c9c0ca40cab0cb30cba0cc20cc90cd10cd90ce00ce80cef0cf70cff0d060d0e0d160d1d0d250d2d0d350d3c0d440d4c0d540d5c0d640d6c0d730d7b0d830d8b0d930d9b0da30dab0db40dbc0dc40dcc0dd40ddc0de40ded0df50dfd0e050e0d0e160e1e0e260e2f0e370e400e480e500e590e610e6a0e720e7b0e830e8c0e950e9d0ea60eae0eb70ec00ec80ed10eda0ee30eeb0ef40efd0f060f0f0f180f210f2a0f320f3b0f440f4d0f560f600f690f720f7b0f840f8d0f960f9f0fa90fb20fbb0fc40fce0fd70fe00fea0ff30ffd1006100f10191022102c1036103f10491052105c1066106f10791083108c109610a010aa10b410bd10c710d110db10e510ef10f91103110d11171121112b11361140114a1154115e11691173117d11871192119c11a711b111bb11c611d011db11e511f011fb12051210121a12251230123b12451250125b12661271127c12861291129c12a712b212bd12c812d312df12ea12f51300130b13171322132d13381344134f135b13661371137d13881394139f13ab13b713c213ce13da13e513f113fd140914141420142c143814441450145c146814741480148c149814a414b114bd14c914d514e214ee14fa150715131520152c153915451552155e156b157715841591159e15aa15b715c415d115de15ea15f716041611161e162b1639164616531660166d167a1688169516a216b016bd16ca16d816e516f31700170e171c17291737174517521760176e177c178a179717a517b317c117cf17dd17ec17fa18081816182418321841184f185d186c187a1889189718a618b418c318d118e018ef18fd190c191b192a193919471956196519741983199219a119b119c019cf19de19ed19fd1a0c1a1b1a2b1a3a1a4a1a591a691a781a881a981aa71ab71ac71ad71ae71af61b061b161b261b361b461b561b671b771b871b971ba71bb81bc81bd91be91bf91c0a1c1a1c2b1c3c1c4c1c5d1c6e1c7f1c8f1ca01cb11cc21cd31ce41cf51d061d171d281d3a1d4b1d5c1d6e1d7f1d901da21db31dc51dd61de81dfa1e0b1e1d1e2f1e411e521e641e761e881e9a1eac1ebe1ed11ee31ef51f071f1a1f2c1f3e1f511f631f761f881f9b1fae1fc01fd31fe61ff9200c201e203120442057206b207e209120a420b720cb20de20f121052118212c213f21532167217a218e21a221b621ca21de21f22206221a222e22422256226a227f229322a822bc22d122e522fa230e23232338234c23612376238b23a023b523ca23df23f4240a241f2434244a245f2475248a24a024b524cb24e124f6250c25222538254e2564257a259025a625bd25d325e925ff2616262c2643265926702687269e26b426cb26e226f9"; +bytes constant ANTI_LOG_TABLES + = hex"03e803ea03ed03ef03f103f403f603f803fb03fd03ff0402040404060409040b040e0410041204150417041a041c041e0421042304260428042b042d04300432043404370439043c043e0441044304460448044b044e0450045304550458045a045d045f046204650467046a046c046f047204740477047a047c047f0481048404870489048c048f049104940497049a049c049f04a204a504a704aa04ad04af04b204b504b804bb04bd04c004c304c604c904cb04ce04d104d404d704da04dd04df04e204e504e804eb04ee04f104f404f704fa04fc04ff050205050508050b050e051105140517051a051d0520052305260529052c052f053205360539053c053f054205450548054b054e055105550558055b055e056105640568056b056e057105740578057b057e058105850588058b058e059205950598059b059f05a205a505a905ac05af05b305b605ba05bd05c005c405c705cb05ce05d105d505d805dc05df05e305e605ea05ed05f105f405f805fb05ff060206060609060d061006140618061b061f06220626062a062d063106350638063c064006430647064b064e06520656065a065d066106650669066c067006740678067c067f06830687068b068f06930697069a069e06a206a606aa06ae06b206b606ba06be06c206c606ca06ce06d206d606da06de06e206e606ea06ee06f206f606fa06ff07030707070b070f07130718071c072007240728072d073107350739073e07420746074a074f07530757075c076007640769076d07710776077a077f07830788078c079007950799079e07a207a707ab07b007b407b907be07c207c707cb07d007d407d907de07e207e707ec07f007f507fa07fe08030808080d08110816081b082008240829082e08330838083d08410846084b08500855085a085f08640869086e08730878087d08820887088c08910896089b08a008a508aa08af08b408ba08bf08c408c908ce08d308d908de08e308e808ee08f308f808fd09030908090d09130918091d09230928092e09330938093e09430949094e09540959095f0964096a096f0975097b09800986098b09910997099c09a209a809ad09b309b909bf09c409ca09d009d609db09e109e709ed09f309f909ff0a040a0a0a100a160a1c0a220a280a2e0a340a3a0a400a460a4c0a520a590a5f0a650a6b0a710a770a7d0a840a8a0a900a960a9c0aa30aa90aaf0ab60abc0ac20ac90acf0ad50adc0ae20ae90aef0af50afc0b020b090b0f0b160b1c0b230b2a0b300b370b3d0b440b4b0b510b580b5f0b650b6c0b730b7a0b800b870b8e0b950b9c0ba30ba90bb00bb70bbe0bc50bcc0bd30bda0be10be80bef0bf60bfd0c040c0b0c120c190c210c280c2f0c360c3d0c450c4c0c530c5a0c620c690c700c780c7f0c860c8e0c950c9c0ca40cab0cb30cba0cc20cc90cd10cd90ce00ce80cef0cf70cff0d060d0e0d160d1d0d250d2d0d350d3c0d440d4c0d540d5c0d640d6c0d730d7b0d830d8b0d930d9b0da30dab0db40dbc0dc40dcc0dd40ddc0de40ded0df50dfd0e050e0d0e160e1e0e260e2f0e370e400e480e500e590e610e6a0e720e7b0e830e8c0e950e9d0ea60eae0eb70ec00ec80ed10eda0ee30eeb0ef40efd0f060f0f0f180f210f2a0f320f3b0f440f4d0f560f600f690f720f7b0f840f8d0f960f9f0fa90fb20fbb0fc40fce0fd70fe00fea0ff30ffd1006100f10191022102c1036103f10491052105c1066106f10791083108c109610a010aa10b410bd10c710d110db10e510ef10f91103110d11171121112b11361140114a1154115e11691173117d11871192119c11a711b111bb11c611d011db11e511f011fb12051210121a12251230123b12451250125b12661271127c12861291129c12a712b212bd12c812d312df12ea12f51300130b13171322132d13381344134f135b13661371137d13881394139f13ab13b713c213ce13da13e513f113fd140914141420142c143814441450145c146814741480148c149814a414b114bd14c914d514e214ee14fa150715131520152c153915451552155e156b157715841591159e15aa15b715c415d115de15ea15f716041611161e162b1639164616531660166d167a1688169516a216b016bd16ca16d816e516f31700170e171c17291737174517521760176e177c178a179717a517b317c117cf17dd17ec17fa18081816182418321841184f185d186c187a1889189718a618b418c318d118e018ef18fd190c191b192a193919471956196519741983199219a119b119c019cf19de19ed19fd1a0c1a1b1a2b1a3a1a4a1a591a691a781a881a981aa71ab71ac71ad71ae71af61b061b161b261b361b461b561b671b771b871b971ba71bb81bc81bd91be91bf91c0a1c1a1c2b1c3c1c4c1c5d1c6e1c7f1c8f1ca01cb11cc21cd31ce41cf51d061d171d281d3a1d4b1d5c1d6e1d7f1d901da21db31dc51dd61de81dfa1e0b1e1d1e2f1e411e521e641e761e881e9a1eac1ebe1ed11ee31ef51f071f1a1f2c1f3e1f511f631f761f881f9b1fae1fc01fd31fe61ff9200c201e203120442057206b207e209120a420b720cb20de20f121052118212c213f21532167217a218e21a221b621ca21de21f22206221a222e22422256226a227f229322a822bc22d122e522fa230e23232338234c23612376238b23a023b523ca23df23f4240a241f2434244a245f2475248a24a024b524cb24e124f6250c25222538254e2564257a259025a625bd25d325e925ff2616262c2643265926702687269e26b426cb26e226f92710271027102710271027102710271027102710"; /// @dev Anti log tables small. -bytes constant ANTI_LOG_TABLES_SMALL = - hex"0000000101010102020200000001010101020202000000010101010202020000000101010102020200000101010102020202000001010101020202020000010101010202020200000101010102020202000001010101020202030000010101010202020300000101010102020203000001010102020202030000010101020202020300000101010202020303000001010102020203030000010101020202030300000101010202020303000001010102020203030000010101020202030300000101010202030303000001010102020303030000010102020203030300000101020202030303000001010202020303040000010102020203030400000101020202030304000001010202030303040000010102020303030400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304050500010102020304040505000101020203040405050001010202030404050600010102030304040506000101020303040405060001010203030405050600010102030304050506000101020303040505060001010203040405060600010102030404050606000101020304040506070001020203040505060700010202030405050607000102020304050606070001020203040506060700010202030405060707000102030304050607080001020303040506070800010203040405060708000102030405050607080001020304050606070800010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607090a0001020304050708090a0001020304060708090a0001020305060708090a0001020405060708090b00010204050607080a0b00010204050607090a0b00010304050608090a0b00010304050608090a0c00010304050708090a0c00010304050708090b0c000103040507080a0b0c000103040607080a0b0d000103040607090a0b0d000103040607090a0c0d000203050608090b0c0e000203050608090b0c0e000203050608090b0d0e0002030506080a0b0d0f0002030507080a0c0d0f0002030507080a0c0d0f0002030507090a0c0e100002040507090b0c0e100002040507090b0d0e100002040607090b0d0f110002040608090b0d0f1100020406080a0c0e0f1100020406080a0c0e101200020406080a0c0e101200020406080a0c0f111300020406080b0d0f111300020407090b0d0f111400020407090b0d10121400020507090b0e101214"; +bytes constant ANTI_LOG_TABLES_SMALL + = hex"0000000101010102020200000001010101020202000000010101010202020000000101010102020200000101010102020202000001010101020202020000010101010202020200000101010102020202000001010101020202030000010101010202020300000101010102020203000001010102020202030000010101020202020300000101010202020303000001010102020203030000010101020202030300000101010202020303000001010102020203030000010101020202030300000101010202030303000001010102020303030000010102020203030300000101020202030303000001010202020303040000010102020203030400000101020202030304000001010202030303040000010102020303030400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304050500010102020304040505000101020203040405050001010202030404050600010102030304040506000101020303040405060001010203030405050600010102030304050506000101020303040505060001010203040405060600010102030404050606000101020304040506070001020203040505060700010202030405050607000102020304050606070001020203040506060700010202030405060707000102030304050607080001020303040506070800010203040405060708000102030405050607080001020304050606070800010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607090a0001020304050708090a0001020304060708090a0001020305060708090a0001020405060708090b00010204050607080a0b00010204050607090a0b00010304050608090a0b00010304050608090a0c00010304050708090a0c00010304050708090b0c000103040507080a0b0c000103040607080a0b0d000103040607090a0b0d000103040607090a0c0d000203050608090b0c0e000203050608090b0c0e000203050608090b0d0e0002030506080a0b0d0f0002030507080a0c0d0f0002030507080a0c0d0f0002030507090a0c0e100002040507090b0c0e100002040507090b0d0e100002040607090b0d0f110002040608090b0d0f1100020406080a0c0e0f1100020406080a0c0e101200020406080a0c0e101200020406080a0c0f111300020406080b0d0f111300020407090b0d0f111400020407090b0d10121400020507090b0e10121400000000000000000000"; diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index f196d0f8..0352f31c 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -28,6 +28,8 @@ import { EXPONENT_MIN } from "./implementation/LibDecimalFloatImplementation.sol"; +import {console2} from "forge-std/Test.sol"; + type Float is bytes32; /// @dev When normalizing a number, how far we "leap" when very far from @@ -585,13 +587,33 @@ library LibDecimalFloat { /// logarithm tables. function power(Float a, Float b, address tablesDataContract) internal view returns (Float) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + console2.log("power a"); + console2.logInt(signedCoefficientA); + console2.logInt(exponentA); + (int256 signedCoefficientC, int256 exponentC) = LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficientA, exponentA); + console2.log("power log 10"); + console2.logInt(signedCoefficientC); + console2.logInt(exponentC); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + console2.log("power b"); + console2.logInt(signedCoefficientB); + console2.logInt(exponentB); + (signedCoefficientC, exponentC) = LibDecimalFloatImplementation.multiply(signedCoefficientC, exponentC, signedCoefficientB, exponentB); + console2.log("power multiply"); + console2.logInt(signedCoefficientC); + console2.logInt(exponentC); + (signedCoefficientC, exponentC) = LibDecimalFloatImplementation.power10(tablesDataContract, signedCoefficientC, exponentC); + console2.log("power power 10"); + console2.logInt(signedCoefficientC); + console2.logInt(exponentC); + (Float c, bool lossless) = packLossy(signedCoefficientC, exponentC); // We don't care if power is lossy because it's an approximation anyway. (lossless); @@ -614,4 +636,13 @@ library LibDecimalFloat { function max(Float a, Float b) internal pure returns (Float) { return gt(a, b) ? a : b; } + + function isZero(Float a) internal pure returns (bool result) { + uint256 mask = type(uint224).max; + assembly ("memory-safe") { + // Don't need to signextend here because we only care if the value + // is zero or not. + result := iszero(and(a, mask)) + } + } } diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 6a64b565..a8e7a1ca 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -11,6 +11,8 @@ import { } from "../../generated/LogTables.pointers.sol"; import {LibDecimalFloat} from "../LibDecimalFloat.sol"; +import {console2} from "forge-std/Test.sol"; + error WithTargetExponentOverflow(int256 signedCoefficient, int256 exponent, int256 targetExponent); uint256 constant ADD_MAX_EXPONENT_DIFF = 37; @@ -378,6 +380,10 @@ library LibDecimalFloatImplementation { signedCoefficient = 1e75 / signedCoefficient; exponent = -exponent - 75; + console2.log("inv"); + console2.logInt(signedCoefficient); + console2.logInt(exponent); + return (signedCoefficient, exponent); } @@ -509,6 +515,7 @@ library LibDecimalFloatImplementation { { unchecked { if (signedCoefficient < 0) { + console2.log("power10 negative"); (signedCoefficient, exponent) = minus(signedCoefficient, exponent); (signedCoefficient, exponent) = power10(tablesDataContract, signedCoefficient, exponent); return inv(signedCoefficient, exponent); @@ -517,11 +524,20 @@ library LibDecimalFloatImplementation { // Table lookup. (int256 characteristicCoefficient, int256 mantissaCoefficient) = characteristicMantissa(signedCoefficient, exponent); + console2.log("table lookup"); + console2.logInt(characteristicCoefficient); + console2.logInt(mantissaCoefficient); int256 characteristicExponent = exponent; { (int256 idx, bool interpolate) = mantissa4(mantissaCoefficient, exponent); + console2.log("index"); + console2.logInt(idx); + console2.logBool(interpolate); (int256 y1Coefficient, int256 y2Coefficient) = lookupAntilogTableY1Y2(tablesDataContract, uint256(idx), interpolate); + console2.log("y1 y2"); + console2.logInt(y1Coefficient); + console2.logInt(y2Coefficient); if (interpolate) { (signedCoefficient, exponent) = unitLinearInterpolation( @@ -533,6 +549,12 @@ library LibDecimalFloatImplementation { } } + console2.log("after"); + console2.logInt(signedCoefficient); + console2.logInt(exponent); + console2.logInt(characteristicCoefficient); + console2.logInt(characteristicExponent); + return ( signedCoefficient, 1 + exponent + withTargetExponent(characteristicCoefficient, characteristicExponent, 0) @@ -816,7 +838,7 @@ library LibDecimalFloatImplementation { extcodecopy(tables, 30, add(offset, mul(div(index, 10), 2)), 2) let mainTableVal := mload(0) - offset := add(offset, 2000) + offset := add(offset, 2020) mstore(0, 0) extcodecopy(tables, 31, add(offset, add(mul(div(index, 100), 10), mod(index, 10))), 1) result := add(mainTableVal, mload(0)) diff --git a/src/lib/table/LibLogTable.sol b/src/lib/table/LibLogTable.sol index 525d51d5..7b2503e3 100644 --- a/src/lib/table/LibLogTable.sol +++ b/src/lib/table/LibLogTable.sol @@ -63,16 +63,16 @@ library LibLogTable { 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) @@ -86,7 +86,7 @@ library LibLogTable { } } - mstore(cursor, 1000) + mstore(cursor, 1010) } return encoded; } @@ -119,16 +119,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) @@ -142,7 +142,7 @@ library LibLogTable { } } - mstore(cursor, 2000) + mstore(cursor, 2020) } return encoded; } @@ -462,7 +462,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], @@ -563,11 +563,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] ]; } - 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], @@ -668,7 +671,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] ]; } } diff --git a/test/src/lib/LibDecimalFloat.power.t.sol b/test/src/lib/LibDecimalFloat.power.t.sol index 77581c30..cfbca449 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -57,7 +57,59 @@ contract LibDecimalFloatPowerTest is LogTest { checkRoundTrip(50, 0, 40, 0); checkRoundTrip(5, -1, 3, -1); checkRoundTrip(5, -1, 2, -1); - checkRoundTrip(5, 100, 3, 20); + checkRoundTrip(5, 10, 3, 5); checkRoundTrip(5, -1, 100, 0); } + + function powerExternal(Float a, Float b) external returns (Float) { + return a.power(b, logTables()); + } + + function testRoundTripFuzz(Float a, Float b) external { + try this.powerExternal(a, b) returns (Float c) { + // If b is zero we'll divide by zero on the inv. + // If c is 1 then it's not round trippable because 1^x = 1 for all x. + // C will be 1 when a is 1 or b is 0 (or very close to either). + if (b.isZero() || c.eq(LibDecimalFloat.packLossless(1, 0))) { + } + else { + Float inv = b.inv(); + try this.powerExternal(c, inv) returns (Float roundTrip) { + if (roundTrip.isZero()) { + } + else { + Float diff = a.divide(roundTrip).sub(LibDecimalFloat.packLossless(1, 0)).abs(); + console2.log("a"); + (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + console2.logInt(signedCoefficientA); + console2.logInt(exponentA); + console2.log("b"); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); + console2.logInt(signedCoefficientB); + console2.logInt(exponentB); + console2.log("c"); + (int256 signedCoefficientC, int256 exponentC) = c.unpack(); + console2.logInt(signedCoefficientC); + console2.logInt(exponentC); + console2.log("inv"); + (int256 signedCoefficientInv, int256 exponentInv) = inv.unpack(); + console2.logInt(signedCoefficientInv); + console2.logInt(exponentInv); + console2.log("roundTrip"); + (int256 signedCoefficientRoundTrip, int256 exponentRoundTrip) = roundTrip.unpack(); + console2.logInt(signedCoefficientRoundTrip); + console2.logInt(exponentRoundTrip); + console2.log("diff"); + (int256 signedCoefficientDiff, int256 exponentDiff) = diff.unpack(); + console2.logInt(signedCoefficientDiff); + console2.logInt(exponentDiff); + + assertTrue(diff.lt(LibDecimalFloat.packLossless(10, 0)), "diff"); + } + } catch (bytes memory err) { + } + } + } catch (bytes memory err) { + } + } } diff --git a/test/src/lib/LibDecimalFloat.power10.t.sol b/test/src/lib/LibDecimalFloat.power10.t.sol index c6dc99d2..342f3965 100644 --- a/test/src/lib/LibDecimalFloat.power10.t.sol +++ b/test/src/lib/LibDecimalFloat.power10.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, Float, ExponentOverflow} from "src/lib/LibDecimalFloat.sol"; import {LogTest} from "../../abstract/LogTest.sol"; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; @@ -9,25 +9,27 @@ contract LibDecimalFloatPower10Test is LogTest { using LibDecimalFloat for Float; function power10External(int256 signedCoefficient, int256 exponent) external returns (int256, int256) { - address tables = logTables(); - return LibDecimalFloatImplementation.power10(tables, signedCoefficient, exponent); + return LibDecimalFloatImplementation.power10(logTables(), signedCoefficient, exponent); } function power10External(Float float) external returns (Float) { - address tables = logTables(); - return LibDecimalFloat.power10(tables, float); + return LibDecimalFloat.power10(logTables(), float); } - /// Stack and mem are the same. - function testPower10Mem(Float float) external { + function testPower10Packed(Float float) external { (int256 signedCoefficientFloat, int256 exponentFloat) = float.unpack(); try this.power10External(signedCoefficientFloat, exponentFloat) returns ( int256 signedCoefficient, int256 exponent ) { - Float floatPower10 = this.power10External(float); - (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatPower10.unpack(); - assertEq(signedCoefficient, signedCoefficientUnpacked); - assertEq(exponent, exponentUnpacked); + if (exponent > type(int32).max) { + vm.expectRevert(abi.encodeWithSelector(ExponentOverflow.selector, signedCoefficient, exponent)); + Float floatPower10 = this.power10External(float); + } else { + Float floatPower10 = this.power10External(float); + (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = floatPower10.unpack(); + assertEq(signedCoefficient, signedCoefficientUnpacked); + assertEq(exponent, exponentUnpacked); + } } catch (bytes memory err) { vm.expectRevert(err); this.power10External(float); diff --git a/test/src/lib/LibDecimalFloat.sub.t.sol b/test/src/lib/LibDecimalFloat.sub.t.sol index 90adf34e..2e9b5f19 100644 --- a/test/src/lib/LibDecimalFloat.sub.t.sol +++ b/test/src/lib/LibDecimalFloat.sub.t.sol @@ -28,9 +28,9 @@ contract LibDecimalFloatSubTest is Test { int256 signedCoefficient, int256 exponent ) { Float float = this.subExternal(a, b); - (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = float.unpack(); - assertEq(signedCoefficient, signedCoefficientUnpacked); - assertEq(exponent, exponentUnpacked); + (Float floatImplementation, bool lossless) = LibDecimalFloat.packLossy(signedCoefficient, exponent); + (lossless); + assertTrue(float.eq(floatImplementation)); } catch (bytes memory err) { vm.expectRevert(err); this.subExternal(a, b); diff --git a/test/src/lib/format/LibFormatDecimalFloat.t.sol b/test/src/lib/format/LibFormatDecimalFloat.t.sol index d254843e..16e51a64 100644 --- a/test/src/lib/format/LibFormatDecimalFloat.t.sol +++ b/test/src/lib/format/LibFormatDecimalFloat.t.sol @@ -35,9 +35,7 @@ contract LibFormatDecimalFloatTest is Test { /// Test round tripping a value through parse and format. function testFormatDecimalRoundTrip(uint256 value) external pure { - // Dividing by 10 here keeps us clearly within the range of lossless - // conversions. - value = bound(value, 0, type(uint256).max / 10); + 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); diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol index ed64f1e8..0c511436 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.power10.t.sol @@ -64,6 +64,8 @@ contract LibDecimalFloatImplementationPower10Test is LogTest { checkPower10(1.55555e37, -37, 35935e37, -40); // 10^1234.56789 checkPower10(123456789, -5, 36979e37, 1193); + // ~= 10 (fuzzing found this edge case). + checkPower10(99999999999999999999999999999999999997448, -41, 99999999999999999999999999999999999991000, -40); } function boundFloat(int224 x, int32 exponent) internal pure returns (int224, int32) { From 6fd3a7469d289efdcb33c9ad4955aa5eb8bd5c42 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 20 Apr 2025 20:06:09 +0400 Subject: [PATCH 06/13] wip on fixing tests --- src/generated/LogTables.pointers.sol | 20 +++++------ src/lib/LibDecimalFloat.sol | 17 --------- .../LibDecimalFloatImplementation.sol | 35 ++++--------------- src/lib/table/LibLogTable.sol | 30 +++++++++------- test/src/lib/LibDecimalFloat.power.t.sol | 21 +++++------ 5 files changed, 42 insertions(+), 81 deletions(-) diff --git a/src/generated/LogTables.pointers.sol b/src/generated/LogTables.pointers.sol index fe21c629..bf44e6c4 100644 --- a/src/generated/LogTables.pointers.sol +++ b/src/generated/LogTables.pointers.sol @@ -12,21 +12,21 @@ pragma solidity =0.8.25; bytes32 constant BYTECODE_HASH = bytes32(0x0000000000000000000000000000000000000000000000000000000000000000); /// @dev Log tables. -bytes constant LOG_TABLES - = hex"0000002b0056008000aa80d480fd8126814e8176019e01c501ec02130239825f828582aa82cf82f30318033c0360038303a603c983ec840e843084520473049504b604d704f78517853785578577859605b505d405f306118630864e866c868986a786c406e106fe071a07370753076f878b87a787c387de07f90814082f084a0864887f889988b388cd88e70900091a0933094c0965097e899789b089c889e109f90a110a290a410a588a708a878a9e8ab68acd0ae40afa0b110b280b3e8b548b6b8b818b978bad0bc20bd80bee0c030c180c2e0c430c580c6d0c810c960cab0cbf0cd40ce80cfc0d110d250d390d4c0d600d740d880d9b0dae0dc20dd50de80dfb0e0e0e210e340e470e5a0e6c0e7f0e910ea30eb60ec80eda0eec0efe0f100f220f340f450f570f690f7a0f8b0f9d0fae0fbf0fd00fe10ff2100310141025103610461057106810781088109910a910b910ca10da10ea10fa110a111a1129113911491158116811781187119611a611b511c411d411e311f212011210121f122e123d124b125a126912781286129512a312b212c012ce12dd12eb12f913071316132413321340134e135b136913771385139313a013ae13bb13c913d713e413f113ff140c1419142714341441144e145b146814751482148f149c14a914b614c314d014dc14e914f61502150f151b152815341541154d155915661572157e158a159715a315af15bb15c715d315df15eb15f71603160f161a16261632163e164916551661166c16781683168f169a16a616b116bd16c816d316df16ea16f51700170b17171722172d17381743174e17591764176f177a1785178f179a17a517b017bb17c517d017db17e517f017fa18051810181a1824182f18391844184e18581863186d18771882188c189618a018aa18b518bf18c918d318dd18e718f118fb1905190f19191923192c19361940194a1954195d19671971197a1984198e199719a119ab19b419be19c719d119da19e419ed19f61a001a091a131a1c1a251a2e1a381a411a4a1a531a5d1a661a6f1a781a811a8a1a931a9c1aa51aae1ab71ac01ac91ad21adb1ae41aed1af61aff1b081b101b191b221b2b1b341b3c1b451b4e1b561b5f1b681b701b791b821b8a1b931b9b1ba41bac1bb51bbd1bc61bce1bd61bdf1be71bf01bf81c001c091c111c191c221c2a1c321c3a1c431c4b1c531c5b1c631c6b1c741c7c1c841c8c1c941c9c1ca41cac1cb41cbc1cc41ccc1cd41cdc1ce41cec1cf41cfb1d031d0b1d131d1b1d231d2a1d321d3a1d421d491d511d591d601d681d701d771d7f1d871d8e1d961d9e1da51dad1db41dbc1dc31dcb1dd21dda1de11de91df01df81dff1e061e0e1e151e1d1e241e2b1e331e3a1e411e481e501e571e5e1e661e6d1e741e7b1e821e8a1e911e981e9f1ea61ead1eb41ebc1ec31eca1ed11ed81edf1ee61eed1ef41efb1f021f091f101f171f1e1f251f2c1f331f391f401f471f4e1f551f5c1f631f691f701f771f7e1f851f8b1f921f991fa01fa61fad1fb41fba1fc11fc81fce1fd51fdc1fe21fe91ff01ff61ffd2003200a20112017201e2024202b20312038203e2045204b20522058205f2065206b20722078207f2085208b20922098209f20a520ab20b220b820be20c420cb20d120d720de20e420ea20f020f720fd21032109210f2116211c21222128212e2134213a21412147214d21532159215f2165216b21712177217d21832189218f2195219b21a121a721ad21b321b921bf21c521cb21d121d721dd21e321e921ee21f421fa22002206220c22122217221d22232229222f2234223a22402246224b22512257225d22622268226e22742279227f2285228a22902296229b22a122a722ac22b222b722bd22c322c822ce22d322d922df22e422ea22ef22f522fa23002305230b23102316231b23212326232c23312337233c23412347234c23522357235d23622367236d23722377237d23822388238d23922398239d23a223a823ad23b223b723bd23c223c723cd23d223d723dc23e223e723ec23f123f623fc24012406240b24102416241b24202425242a242f2435243a243f24442449244e24532458245d24632468246d24722477247c24812486248b24902495249a249f24a424a924ae24b324b824bd24c224c724cc24d124d624db24e024e524ea24ef24f424f924fd25022507250c25112516251b252025252529252e25332538253d25422546254b25502555255a255e25632568256d25722576257b258025852589258e25932598259c25a125a625ab25af25b425b925bd25c225c725cb25d025d525d925de25e325e725ec25f125f525fa25ff26032608260d26112616261a261f26232628262d26312636263a263f26432648264d26512656265a265f26632668266c26712675267a267e26832687268c269026952699269e26a226a626ab26af26b426b826bd26c126c626ca26ce26d326d726dc26e026e426e926ed26f126f626fa26ff27032707270c"; +bytes constant LOG_TABLES = + hex"0000002b0056008000aa80d480fd8126814e8176019e01c501ec02130239825f828582aa82cf82f30318033c0360038303a603c983ec840e843084520473049504b604d704f78517853785578577859605b505d405f306118630864e866c868986a786c406e106fe071a07370753076f878b87a787c387de07f90814082f084a0864887f889988b388cd88e70900091a0933094c0965097e899789b089c889e109f90a110a290a410a588a708a878a9e8ab68acd0ae40afa0b110b280b3e8b548b6b8b818b978bad0bc20bd80bee0c030c180c2e0c430c580c6d0c810c960cab0cbf0cd40ce80cfc0d110d250d390d4c0d600d740d880d9b0dae0dc20dd50de80dfb0e0e0e210e340e470e5a0e6c0e7f0e910ea30eb60ec80eda0eec0efe0f100f220f340f450f570f690f7a0f8b0f9d0fae0fbf0fd00fe10ff2100310141025103610461057106810781088109910a910b910ca10da10ea10fa110a111a1129113911491158116811781187119611a611b511c411d411e311f212011210121f122e123d124b125a126912781286129512a312b212c012ce12dd12eb12f913071316132413321340134e135b136913771385139313a013ae13bb13c913d713e413f113ff140c1419142714341441144e145b146814751482148f149c14a914b614c314d014dc14e914f61502150f151b152815341541154d155915661572157e158a159715a315af15bb15c715d315df15eb15f71603160f161a16261632163e164916551661166c16781683168f169a16a616b116bd16c816d316df16ea16f51700170b17171722172d17381743174e17591764176f177a1785178f179a17a517b017bb17c517d017db17e517f017fa18051810181a1824182f18391844184e18581863186d18771882188c189618a018aa18b518bf18c918d318dd18e718f118fb1905190f19191923192c19361940194a1954195d19671971197a1984198e199719a119ab19b419be19c719d119da19e419ed19f61a001a091a131a1c1a251a2e1a381a411a4a1a531a5d1a661a6f1a781a811a8a1a931a9c1aa51aae1ab71ac01ac91ad21adb1ae41aed1af61aff1b081b101b191b221b2b1b341b3c1b451b4e1b561b5f1b681b701b791b821b8a1b931b9b1ba41bac1bb51bbd1bc61bce1bd61bdf1be71bf01bf81c001c091c111c191c221c2a1c321c3a1c431c4b1c531c5b1c631c6b1c741c7c1c841c8c1c941c9c1ca41cac1cb41cbc1cc41ccc1cd41cdc1ce41cec1cf41cfb1d031d0b1d131d1b1d231d2a1d321d3a1d421d491d511d591d601d681d701d771d7f1d871d8e1d961d9e1da51dad1db41dbc1dc31dcb1dd21dda1de11de91df01df81dff1e061e0e1e151e1d1e241e2b1e331e3a1e411e481e501e571e5e1e661e6d1e741e7b1e821e8a1e911e981e9f1ea61ead1eb41ebc1ec31eca1ed11ed81edf1ee61eed1ef41efb1f021f091f101f171f1e1f251f2c1f331f391f401f471f4e1f551f5c1f631f691f701f771f7e1f851f8b1f921f991fa01fa61fad1fb41fba1fc11fc81fce1fd51fdc1fe21fe91ff01ff61ffd2003200a20112017201e2024202b20312038203e2045204b20522058205f2065206b20722078207f2085208b20922098209f20a520ab20b220b820be20c420cb20d120d720de20e420ea20f020f720fd21032109210f2116211c21222128212e2134213a21412147214d21532159215f2165216b21712177217d21832189218f2195219b21a121a721ad21b321b921bf21c521cb21d121d721dd21e321e921ee21f421fa22002206220c22122217221d22232229222f2234223a22402246224b22512257225d22622268226e22742279227f2285228a22902296229b22a122a722ac22b222b722bd22c322c822ce22d322d922df22e422ea22ef22f522fa23002305230b23102316231b23212326232c23312337233c23412347234c23522357235d23622367236d23722377237d23822388238d23922398239d23a223a823ad23b223b723bd23c223c723cd23d223d723dc23e223e723ec23f123f623fc24012406240b24102416241b24202425242a242f2435243a243f24442449244e24532458245d24632468246d24722477247c24812486248b24902495249a249f24a424a924ae24b324b824bd24c224c724cc24d124d624db24e024e524ea24ef24f424f924fd25022507250c25112516251b252025252529252e25332538253d25422546254b25502555255a255e25632568256d25722576257b258025852589258e25932598259c25a125a625ab25af25b425b925bd25c225c725cb25d025d525d925de25e325e725ec25f125f525fa25ff26032608260d26112616261a261f26232628262d26312636263a263f26432648264d26512656265a265f26632668266c26712675267a267e26832687268c269026952699269e26a226a626ab26af26b426b826bd26c126c626ca26ce26d326d726dc26e026e426e926ed26f126f626fa26ff27032707270c2710271027102710271027102710271027102710"; /// @dev Log tables small. -bytes constant LOG_TABLES_SMALL - = hex"0004090d11151a1e22260004080c0f13171b1f230003070b0e1215191c200003070a0d1014171a1e000306090c0f1215181c000306090b0e1114171a000305080b0e10131618000305080a0d0f12141700020507090c0e10131500020407090b0d10121400020406080b0d0f111300020406080a0c0e101200020406080a0c0e0f110002040607090b0d0f110002040507090b0c0e100002030507090a0c0e0f0002030507080a0b0d0f000203050608090b0d0e000203050608090b0c0e000103040607090a0c0d000103040607090a0b0d000103040607080a0b0c00010304050708090b0c00010304050608090a0c00010304050608090a0b00010204050607090a0b00010204050607080a0b0001020305060708090a0001020305060708090a0001020304050708090a0001020304050608090a0001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060707080001020304050506070800010203040405060708000102030404050607080001020303040506070800010203030405060708000102020304050607070001020203040506060700010202030405060607000102020304050506070001020203040505060700010202030405050607000101020304040506070001010203040405060700010102030404050606000101020304040506060001010203030405060600010102030304050506000101020303040505060001010203030405050600010102030304050506000101020303040505060001010203030404050600010102020304040506000101020203040405060001010202030404050500010102020304040505000101020203040405050001010202030404050500010102020303040505000101020203030405050001010202030304040500010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304040500000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030304"; +bytes constant LOG_TABLES_SMALL = + hex"0004090d11151a1e22260004080c0f13171b1f230003070b0e1215191c200003070a0d1014171a1e000306090c0f1215181c000306090b0e1114171a000305080b0e10131618000305080a0d0f12141700020507090c0e10131500020407090b0d10121400020406080b0d0f111300020406080a0c0e101200020406080a0c0e0f110002040607090b0d0f110002040507090b0c0e100002030507090a0c0e0f0002030507080a0b0d0f000203050608090b0d0e000203050608090b0c0e000103040607090a0c0d000103040607090a0b0d000103040607080a0b0c00010304050708090b0c00010304050608090a0c00010304050608090a0b00010204050607090a0b00010204050607080a0b0001020305060708090a0001020305060708090a0001020304050708090a0001020304050608090a000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607080900010203040506070708000102030405050607080001020304040506070800010203040405060708000102030304050607080001020303040506070800010202030405060707000102020304050606070001020203040506060700010202030405050607000102020304050506070001020203040505060700010102030404050607000101020304040506070001010203040405060600010102030404050606000101020303040506060001010203030405050600010102030304050506000101020303040505060001010203030405050600010102030304050506000101020303040405060001010202030404050600010102020304040506000101020203040405050001010202030404050500010102020304040505000101020203040405050001010202030304050500010102020303040505000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303030400000000000000000000"; /// @dev Log tables small alt. -bytes constant LOG_TABLES_SMALL_ALT - = hex"0004080c1014181c20250004070b0f13161a1e210003070a0e1114181b1f0003070a0c101316191d000306090c0f1114171a000305080b0e10131619000305080a0d0f121517000205070a0c0f11131600020507090b0e10121500020406080b0d0f1113"; +bytes constant LOG_TABLES_SMALL_ALT = + hex"0004080c1014181c20250004070b0f13161a1e210003070a0e1114181b1f0003070a0c101316191d000306090c0f1114171a000305080b0e10131619000305080a0d0f121517000205070a0c0f11131600020507090b0e10121500020406080b0d0f1113"; /// @dev Anti log tables. -bytes constant ANTI_LOG_TABLES - = hex"03e803ea03ed03ef03f103f403f603f803fb03fd03ff0402040404060409040b040e0410041204150417041a041c041e0421042304260428042b042d04300432043404370439043c043e0441044304460448044b044e0450045304550458045a045d045f046204650467046a046c046f047204740477047a047c047f0481048404870489048c048f049104940497049a049c049f04a204a504a704aa04ad04af04b204b504b804bb04bd04c004c304c604c904cb04ce04d104d404d704da04dd04df04e204e504e804eb04ee04f104f404f704fa04fc04ff050205050508050b050e051105140517051a051d0520052305260529052c052f053205360539053c053f054205450548054b054e055105550558055b055e056105640568056b056e057105740578057b057e058105850588058b058e059205950598059b059f05a205a505a905ac05af05b305b605ba05bd05c005c405c705cb05ce05d105d505d805dc05df05e305e605ea05ed05f105f405f805fb05ff060206060609060d061006140618061b061f06220626062a062d063106350638063c064006430647064b064e06520656065a065d066106650669066c067006740678067c067f06830687068b068f06930697069a069e06a206a606aa06ae06b206b606ba06be06c206c606ca06ce06d206d606da06de06e206e606ea06ee06f206f606fa06ff07030707070b070f07130718071c072007240728072d073107350739073e07420746074a074f07530757075c076007640769076d07710776077a077f07830788078c079007950799079e07a207a707ab07b007b407b907be07c207c707cb07d007d407d907de07e207e707ec07f007f507fa07fe08030808080d08110816081b082008240829082e08330838083d08410846084b08500855085a085f08640869086e08730878087d08820887088c08910896089b08a008a508aa08af08b408ba08bf08c408c908ce08d308d908de08e308e808ee08f308f808fd09030908090d09130918091d09230928092e09330938093e09430949094e09540959095f0964096a096f0975097b09800986098b09910997099c09a209a809ad09b309b909bf09c409ca09d009d609db09e109e709ed09f309f909ff0a040a0a0a100a160a1c0a220a280a2e0a340a3a0a400a460a4c0a520a590a5f0a650a6b0a710a770a7d0a840a8a0a900a960a9c0aa30aa90aaf0ab60abc0ac20ac90acf0ad50adc0ae20ae90aef0af50afc0b020b090b0f0b160b1c0b230b2a0b300b370b3d0b440b4b0b510b580b5f0b650b6c0b730b7a0b800b870b8e0b950b9c0ba30ba90bb00bb70bbe0bc50bcc0bd30bda0be10be80bef0bf60bfd0c040c0b0c120c190c210c280c2f0c360c3d0c450c4c0c530c5a0c620c690c700c780c7f0c860c8e0c950c9c0ca40cab0cb30cba0cc20cc90cd10cd90ce00ce80cef0cf70cff0d060d0e0d160d1d0d250d2d0d350d3c0d440d4c0d540d5c0d640d6c0d730d7b0d830d8b0d930d9b0da30dab0db40dbc0dc40dcc0dd40ddc0de40ded0df50dfd0e050e0d0e160e1e0e260e2f0e370e400e480e500e590e610e6a0e720e7b0e830e8c0e950e9d0ea60eae0eb70ec00ec80ed10eda0ee30eeb0ef40efd0f060f0f0f180f210f2a0f320f3b0f440f4d0f560f600f690f720f7b0f840f8d0f960f9f0fa90fb20fbb0fc40fce0fd70fe00fea0ff30ffd1006100f10191022102c1036103f10491052105c1066106f10791083108c109610a010aa10b410bd10c710d110db10e510ef10f91103110d11171121112b11361140114a1154115e11691173117d11871192119c11a711b111bb11c611d011db11e511f011fb12051210121a12251230123b12451250125b12661271127c12861291129c12a712b212bd12c812d312df12ea12f51300130b13171322132d13381344134f135b13661371137d13881394139f13ab13b713c213ce13da13e513f113fd140914141420142c143814441450145c146814741480148c149814a414b114bd14c914d514e214ee14fa150715131520152c153915451552155e156b157715841591159e15aa15b715c415d115de15ea15f716041611161e162b1639164616531660166d167a1688169516a216b016bd16ca16d816e516f31700170e171c17291737174517521760176e177c178a179717a517b317c117cf17dd17ec17fa18081816182418321841184f185d186c187a1889189718a618b418c318d118e018ef18fd190c191b192a193919471956196519741983199219a119b119c019cf19de19ed19fd1a0c1a1b1a2b1a3a1a4a1a591a691a781a881a981aa71ab71ac71ad71ae71af61b061b161b261b361b461b561b671b771b871b971ba71bb81bc81bd91be91bf91c0a1c1a1c2b1c3c1c4c1c5d1c6e1c7f1c8f1ca01cb11cc21cd31ce41cf51d061d171d281d3a1d4b1d5c1d6e1d7f1d901da21db31dc51dd61de81dfa1e0b1e1d1e2f1e411e521e641e761e881e9a1eac1ebe1ed11ee31ef51f071f1a1f2c1f3e1f511f631f761f881f9b1fae1fc01fd31fe61ff9200c201e203120442057206b207e209120a420b720cb20de20f121052118212c213f21532167217a218e21a221b621ca21de21f22206221a222e22422256226a227f229322a822bc22d122e522fa230e23232338234c23612376238b23a023b523ca23df23f4240a241f2434244a245f2475248a24a024b524cb24e124f6250c25222538254e2564257a259025a625bd25d325e925ff2616262c2643265926702687269e26b426cb26e226f92710271027102710271027102710271027102710"; +bytes constant ANTI_LOG_TABLES = + hex"03e803ea03ed03ef03f103f403f603f803fb03fd03ff0402040404060409040b040e0410041204150417041a041c041e0421042304260428042b042d04300432043404370439043c043e0441044304460448044b044e0450045304550458045a045d045f046204650467046a046c046f047204740477047a047c047f0481048404870489048c048f049104940497049a049c049f04a204a504a704aa04ad04af04b204b504b804bb04bd04c004c304c604c904cb04ce04d104d404d704da04dd04df04e204e504e804eb04ee04f104f404f704fa04fc04ff050205050508050b050e051105140517051a051d0520052305260529052c052f053205360539053c053f054205450548054b054e055105550558055b055e056105640568056b056e057105740578057b057e058105850588058b058e059205950598059b059f05a205a505a905ac05af05b305b605ba05bd05c005c405c705cb05ce05d105d505d805dc05df05e305e605ea05ed05f105f405f805fb05ff060206060609060d061006140618061b061f06220626062a062d063106350638063c064006430647064b064e06520656065a065d066106650669066c067006740678067c067f06830687068b068f06930697069a069e06a206a606aa06ae06b206b606ba06be06c206c606ca06ce06d206d606da06de06e206e606ea06ee06f206f606fa06ff07030707070b070f07130718071c072007240728072d073107350739073e07420746074a074f07530757075c076007640769076d07710776077a077f07830788078c079007950799079e07a207a707ab07b007b407b907be07c207c707cb07d007d407d907de07e207e707ec07f007f507fa07fe08030808080d08110816081b082008240829082e08330838083d08410846084b08500855085a085f08640869086e08730878087d08820887088c08910896089b08a008a508aa08af08b408ba08bf08c408c908ce08d308d908de08e308e808ee08f308f808fd09030908090d09130918091d09230928092e09330938093e09430949094e09540959095f0964096a096f0975097b09800986098b09910997099c09a209a809ad09b309b909bf09c409ca09d009d609db09e109e709ed09f309f909ff0a040a0a0a100a160a1c0a220a280a2e0a340a3a0a400a460a4c0a520a590a5f0a650a6b0a710a770a7d0a840a8a0a900a960a9c0aa30aa90aaf0ab60abc0ac20ac90acf0ad50adc0ae20ae90aef0af50afc0b020b090b0f0b160b1c0b230b2a0b300b370b3d0b440b4b0b510b580b5f0b650b6c0b730b7a0b800b870b8e0b950b9c0ba30ba90bb00bb70bbe0bc50bcc0bd30bda0be10be80bef0bf60bfd0c040c0b0c120c190c210c280c2f0c360c3d0c450c4c0c530c5a0c620c690c700c780c7f0c860c8e0c950c9c0ca40cab0cb30cba0cc20cc90cd10cd90ce00ce80cef0cf70cff0d060d0e0d160d1d0d250d2d0d350d3c0d440d4c0d540d5c0d640d6c0d730d7b0d830d8b0d930d9b0da30dab0db40dbc0dc40dcc0dd40ddc0de40ded0df50dfd0e050e0d0e160e1e0e260e2f0e370e400e480e500e590e610e6a0e720e7b0e830e8c0e950e9d0ea60eae0eb70ec00ec80ed10eda0ee30eeb0ef40efd0f060f0f0f180f210f2a0f320f3b0f440f4d0f560f600f690f720f7b0f840f8d0f960f9f0fa90fb20fbb0fc40fce0fd70fe00fea0ff30ffd1006100f10191022102c1036103f10491052105c1066106f10791083108c109610a010aa10b410bd10c710d110db10e510ef10f91103110d11171121112b11361140114a1154115e11691173117d11871192119c11a711b111bb11c611d011db11e511f011fb12051210121a12251230123b12451250125b12661271127c12861291129c12a712b212bd12c812d312df12ea12f51300130b13171322132d13381344134f135b13661371137d13881394139f13ab13b713c213ce13da13e513f113fd140914141420142c143814441450145c146814741480148c149814a414b114bd14c914d514e214ee14fa150715131520152c153915451552155e156b157715841591159e15aa15b715c415d115de15ea15f716041611161e162b1639164616531660166d167a1688169516a216b016bd16ca16d816e516f31700170e171c17291737174517521760176e177c178a179717a517b317c117cf17dd17ec17fa18081816182418321841184f185d186c187a1889189718a618b418c318d118e018ef18fd190c191b192a193919471956196519741983199219a119b119c019cf19de19ed19fd1a0c1a1b1a2b1a3a1a4a1a591a691a781a881a981aa71ab71ac71ad71ae71af61b061b161b261b361b461b561b671b771b871b971ba71bb81bc81bd91be91bf91c0a1c1a1c2b1c3c1c4c1c5d1c6e1c7f1c8f1ca01cb11cc21cd31ce41cf51d061d171d281d3a1d4b1d5c1d6e1d7f1d901da21db31dc51dd61de81dfa1e0b1e1d1e2f1e411e521e641e761e881e9a1eac1ebe1ed11ee31ef51f071f1a1f2c1f3e1f511f631f761f881f9b1fae1fc01fd31fe61ff9200c201e203120442057206b207e209120a420b720cb20de20f121052118212c213f21532167217a218e21a221b621ca21de21f22206221a222e22422256226a227f229322a822bc22d122e522fa230e23232338234c23612376238b23a023b523ca23df23f4240a241f2434244a245f2475248a24a024b524cb24e124f6250c25222538254e2564257a259025a625bd25d325e925ff2616262c2643265926702687269e26b426cb26e226f92710271027102710271027102710271027102710"; /// @dev Anti log tables small. -bytes constant ANTI_LOG_TABLES_SMALL - = hex"0000000101010102020200000001010101020202000000010101010202020000000101010102020200000101010102020202000001010101020202020000010101010202020200000101010102020202000001010101020202030000010101010202020300000101010102020203000001010102020202030000010101020202020300000101010202020303000001010102020203030000010101020202030300000101010202020303000001010102020203030000010101020202030300000101010202030303000001010102020303030000010102020203030300000101020202030303000001010202020303040000010102020203030400000101020202030304000001010202030303040000010102020303030400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304050500010102020304040505000101020203040405050001010202030404050600010102030304040506000101020303040405060001010203030405050600010102030304050506000101020303040505060001010203040405060600010102030404050606000101020304040506070001020203040505060700010202030405050607000102020304050606070001020203040506060700010202030405060707000102030304050607080001020303040506070800010203040405060708000102030405050607080001020304050606070800010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607090a0001020304050708090a0001020304060708090a0001020305060708090a0001020405060708090b00010204050607080a0b00010204050607090a0b00010304050608090a0b00010304050608090a0c00010304050708090a0c00010304050708090b0c000103040507080a0b0c000103040607080a0b0d000103040607090a0b0d000103040607090a0c0d000203050608090b0c0e000203050608090b0c0e000203050608090b0d0e0002030506080a0b0d0f0002030507080a0c0d0f0002030507080a0c0d0f0002030507090a0c0e100002040507090b0c0e100002040507090b0d0e100002040607090b0d0f110002040608090b0d0f1100020406080a0c0e0f1100020406080a0c0e101200020406080a0c0e101200020406080a0c0f111300020406080b0d0f111300020407090b0d0f111400020407090b0d10121400020507090b0e10121400000000000000000000"; +bytes constant ANTI_LOG_TABLES_SMALL = + hex"0000000101010102020200000001010101020202000000010101010202020000000101010102020200000101010102020202000001010101020202020000010101010202020200000101010102020202000001010101020202030000010101010202020300000101010102020203000001010102020202030000010101020202020300000101010202020303000001010102020203030000010101020202030300000101010202020303000001010102020203030000010101020202030300000101010202030303000001010102020303030000010102020203030300000101020202030303000001010202020303040000010102020203030400000101020202030304000001010202030303040000010102020303030400000101020203030404000001010202030304040000010102020303040400000101020203030404000001010202030304040000010102020303040400010102020303040405000101020203030404050001010202030304040500010102020303040405000101020203030404050001010202030304050500010102020304040505000101020203040405050001010202030404050600010102030304040506000101020303040405060001010203030405050600010102030304050506000101020303040505060001010203040405060600010102030404050606000101020304040506070001020203040505060700010202030405050607000102020304050606070001020203040506060700010202030405060707000102030304050607080001020303040506070800010203040405060708000102030405050607080001020304050606070800010203040506070809000102030405060708090001020304050607080900010203040506070809000102030405060708090001020304050607090a0001020304050708090a0001020304060708090a0001020305060708090a0001020405060708090b00010204050607080a0b00010204050607090a0b00010304050608090a0b00010304050608090a0c00010304050708090a0c00010304050708090b0c000103040507080a0b0c000103040607080a0b0d000103040607090a0b0d000103040607090a0c0d000203050608090b0c0e000203050608090b0c0e000203050608090b0d0e0002030506080a0b0d0f0002030507080a0c0d0f0002030507080a0c0d0f0002030507090a0c0e100002040507090b0c0e100002040507090b0d0e100002040607090b0d0f110002040608090b0d0f1100020406080a0c0e0f1100020406080a0c0e101200020406080a0c0e101200020406080a0c0f111300020406080b0d0f111300020407090b0d0f111400020407090b0d10121400020507090b0e10121400000000000000000000"; diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 0352f31c..d1ee3061 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -28,8 +28,6 @@ import { EXPONENT_MIN } from "./implementation/LibDecimalFloatImplementation.sol"; -import {console2} from "forge-std/Test.sol"; - type Float is bytes32; /// @dev When normalizing a number, how far we "leap" when very far from @@ -587,32 +585,17 @@ library LibDecimalFloat { /// logarithm tables. function power(Float a, Float b, address tablesDataContract) internal view returns (Float) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - console2.log("power a"); - console2.logInt(signedCoefficientA); - console2.logInt(exponentA); (int256 signedCoefficientC, int256 exponentC) = LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficientA, exponentA); - console2.log("power log 10"); - console2.logInt(signedCoefficientC); - console2.logInt(exponentC); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - console2.log("power b"); - console2.logInt(signedCoefficientB); - console2.logInt(exponentB); (signedCoefficientC, exponentC) = LibDecimalFloatImplementation.multiply(signedCoefficientC, exponentC, signedCoefficientB, exponentB); - console2.log("power multiply"); - console2.logInt(signedCoefficientC); - console2.logInt(exponentC); (signedCoefficientC, exponentC) = LibDecimalFloatImplementation.power10(tablesDataContract, signedCoefficientC, exponentC); - console2.log("power power 10"); - console2.logInt(signedCoefficientC); - console2.logInt(exponentC); (Float c, bool lossless) = packLossy(signedCoefficientC, exponentC); // We don't care if power is lossy because it's an approximation anyway. diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index a8e7a1ca..9c3ce0f5 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -11,8 +11,6 @@ import { } from "../../generated/LogTables.pointers.sol"; import {LibDecimalFloat} from "../LibDecimalFloat.sol"; -import {console2} from "forge-std/Test.sol"; - error WithTargetExponentOverflow(int256 signedCoefficient, int256 exponent, int256 targetExponent); uint256 constant ADD_MAX_EXPONENT_DIFF = 37; @@ -380,10 +378,6 @@ library LibDecimalFloatImplementation { signedCoefficient = 1e75 / signedCoefficient; exponent = -exponent - 75; - console2.log("inv"); - console2.logInt(signedCoefficient); - console2.logInt(exponent); - return (signedCoefficient, exponent); } @@ -442,13 +436,13 @@ library LibDecimalFloatImplementation { let mainTableVal := mload(0) result := and(mainTableVal, 0x7FFF) - // Skip first byte of data contract then 1800 bytes + // Skip first byte of data contract then 1820 bytes // of the log tables. - let smallTableOffset := 1801 + let smallTableOffset := 1821 if iszero(iszero(and(mainTableVal, 0x8000))) { // Small table is half the size of the main // table. - smallTableOffset := add(smallTableOffset, 900) + smallTableOffset := add(smallTableOffset, 910) } mstore(0, 0) @@ -515,7 +509,6 @@ library LibDecimalFloatImplementation { { unchecked { if (signedCoefficient < 0) { - console2.log("power10 negative"); (signedCoefficient, exponent) = minus(signedCoefficient, exponent); (signedCoefficient, exponent) = power10(tablesDataContract, signedCoefficient, exponent); return inv(signedCoefficient, exponent); @@ -524,21 +517,11 @@ library LibDecimalFloatImplementation { // Table lookup. (int256 characteristicCoefficient, int256 mantissaCoefficient) = characteristicMantissa(signedCoefficient, exponent); - console2.log("table lookup"); - console2.logInt(characteristicCoefficient); - console2.logInt(mantissaCoefficient); int256 characteristicExponent = exponent; { (int256 idx, bool interpolate) = mantissa4(mantissaCoefficient, exponent); - console2.log("index"); - console2.logInt(idx); - console2.logBool(interpolate); (int256 y1Coefficient, int256 y2Coefficient) = lookupAntilogTableY1Y2(tablesDataContract, uint256(idx), interpolate); - console2.log("y1 y2"); - console2.logInt(y1Coefficient); - console2.logInt(y2Coefficient); - if (interpolate) { (signedCoefficient, exponent) = unitLinearInterpolation( mantissaCoefficient, exponent, idx, -4, -41, y1Coefficient, y2Coefficient, -4 @@ -549,12 +532,6 @@ library LibDecimalFloatImplementation { } } - console2.log("after"); - console2.logInt(signedCoefficient); - console2.logInt(exponent); - console2.logInt(characteristicCoefficient); - console2.logInt(characteristicExponent); - return ( signedCoefficient, 1 + exponent + withTargetExponent(characteristicCoefficient, characteristicExponent, 0) @@ -830,10 +807,10 @@ library LibDecimalFloatImplementation { //slither-disable-next-line divide-before-multiply function lookupTableVal(tables, index) -> result { // 1 byte for start of data contract - // + 1800 for log tables - // + 900 for small log tables + // + 1820 for log tables + // + 910 for small log tables // + 100 for alt small log tables - let offset := 2801 + let offset := 2831 mstore(0, 0) extcodecopy(tables, 30, add(offset, mul(div(index, 10), 2)), 2) let mainTableVal := mload(0) diff --git a/src/lib/table/LibLogTable.sol b/src/lib/table/LibLogTable.sol index 7b2503e3..a62bb58c 100644 --- a/src/lib/table/LibLogTable.sol +++ b/src/lib/table/LibLogTable.sol @@ -7,16 +7,16 @@ 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) @@ -30,21 +30,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) @@ -58,7 +58,7 @@ library LibLogTable { } } - mstore(cursor, 900) + mstore(cursor, 910) } return encoded; } @@ -147,7 +147,7 @@ library LibLogTable { return encoded; } - function logTableDec() internal pure returns (uint16[10][90] memory) { + function logTableDec() internal pure returns (uint16[10][91] memory) { return [ [ 0, @@ -348,11 +348,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] ]; } - 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], @@ -443,7 +446,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. The last entry is 10000, so we just use that. + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ]; } diff --git a/test/src/lib/LibDecimalFloat.power.t.sol b/test/src/lib/LibDecimalFloat.power.t.sol index cfbca449..ae79a879 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -34,6 +34,7 @@ contract LibDecimalFloatPowerTest is LogTest { function testPowers() external { checkPower(5e37, -38, 3e37, -36, 9.3283582089552238805970149253731343283e37, -47); checkPower(5e37, -38, 6e37, -36, 8.7108013937282229965156794425087108013e37, -56); + checkPower(99999, 0, 12182, 0, 1000, 60907); } function checkRoundTrip(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) @@ -46,11 +47,11 @@ contract LibDecimalFloatPowerTest is LogTest { Float roundTrip = c.power(b.inv(), tables); Float diff = a.divide(roundTrip).sub(LibDecimalFloat.packLossless(1, 0)).abs(); - assertTrue(diff.lt(LibDecimalFloat.packLossless(0.0025e4, -4)), "diff"); + assertTrue(!diff.gt(LibDecimalFloat.packLossless(0.002e3, -3)), "diff"); } /// X^Y^(1/Y) = X - /// Can generally round trip whatever within 1% of the original value. + /// Can generally round trip whatever within 0.25% of the original value. function testRoundTrip() external { checkRoundTrip(5, 0, 2, 0); checkRoundTrip(5, 0, 3, 0); @@ -70,14 +71,10 @@ contract LibDecimalFloatPowerTest is LogTest { // If b is zero we'll divide by zero on the inv. // If c is 1 then it's not round trippable because 1^x = 1 for all x. // C will be 1 when a is 1 or b is 0 (or very close to either). - if (b.isZero() || c.eq(LibDecimalFloat.packLossless(1, 0))) { - } - else { + if (b.isZero() || c.eq(LibDecimalFloat.packLossless(1, 0))) {} else { Float inv = b.inv(); try this.powerExternal(c, inv) returns (Float roundTrip) { - if (roundTrip.isZero()) { - } - else { + if (roundTrip.isZero()) {} else { Float diff = a.divide(roundTrip).sub(LibDecimalFloat.packLossless(1, 0)).abs(); console2.log("a"); (int256 signedCoefficientA, int256 exponentA) = a.unpack(); @@ -104,12 +101,10 @@ contract LibDecimalFloatPowerTest is LogTest { console2.logInt(signedCoefficientDiff); console2.logInt(exponentDiff); - assertTrue(diff.lt(LibDecimalFloat.packLossless(10, 0)), "diff"); + assertTrue(!diff.gt(LibDecimalFloat.packLossless(0.9e1, -1)), "diff"); } - } catch (bytes memory err) { - } + } catch (bytes memory err) {} } - } catch (bytes memory err) { - } + } catch (bytes memory err) {} } } From d5821b4fc8e7fac201f45ebe2e3bb7731435d3f1 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Wed, 23 Apr 2025 19:43:47 +0400 Subject: [PATCH 07/13] fix tests --- src/lib/LibDecimalFloat.sol | 6 ++ src/lib/LibDecimalFloatDeploy.sol | 3 + .../LibDecimalFloatImplementation.sol | 80 ++++++++++++++----- src/lib/table/LibLogTable.sol | 2 - test/src/lib/LibDecimalFloat.power.t.sol | 16 +++- 5 files changed, 83 insertions(+), 24 deletions(-) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index d1ee3061..ef38d31c 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -28,6 +28,8 @@ import { EXPONENT_MIN } from "./implementation/LibDecimalFloatImplementation.sol"; +import {console2} from "forge-std/Test.sol"; + type Float is bytes32; /// @dev When normalizing a number, how far we "leap" when very far from @@ -589,6 +591,10 @@ library LibDecimalFloat { (int256 signedCoefficientC, int256 exponentC) = LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficientA, exponentA); + console2.log("power log10"); + console2.logInt(signedCoefficientC); + console2.logInt(exponentC); + (int256 signedCoefficientB, int256 exponentB) = b.unpack(); (signedCoefficientC, exponentC) = diff --git a/src/lib/LibDecimalFloatDeploy.sol b/src/lib/LibDecimalFloatDeploy.sol index 7d75712a..72bed115 100644 --- a/src/lib/LibDecimalFloatDeploy.sol +++ b/src/lib/LibDecimalFloatDeploy.sol @@ -12,6 +12,8 @@ import {LibDataContract, DataContractMemoryContainer} from "rain.datacontract/li import {LibBytes} from "rain.solmem/lib/LibBytes.sol"; import {LibMemCpy, Pointer} from "rain.solmem/lib/LibMemCpy.sol"; +import {console2} from "forge-std/Test.sol"; + library LibDecimalFloatDeploy { function combinedTables() internal pure returns (bytes memory) { return @@ -20,6 +22,7 @@ library LibDecimalFloatDeploy { function dataContract() internal pure returns (DataContractMemoryContainer) { bytes memory tables = combinedTables(); + console2.logBytes(tables); (DataContractMemoryContainer container, Pointer pointer) = LibDataContract.newContainer(tables.length); LibMemCpy.unsafeCopyBytesTo(LibBytes.dataPointer(tables), pointer, tables.length); return container; diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 9c3ce0f5..24b6cb6f 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -10,6 +10,7 @@ import { ANTI_LOG_TABLES_SMALL } from "../../generated/LogTables.pointers.sol"; import {LibDecimalFloat} from "../LibDecimalFloat.sol"; +import {console2} from "forge-std/Test.sol"; error WithTargetExponentOverflow(int256 signedCoefficient, int256 exponent, int256 targetExponent); @@ -397,6 +398,9 @@ library LibDecimalFloatImplementation { view returns (int256, int256) { + console2.log("log10 start"); + console2.logInt(signedCoefficient); + console2.logInt(exponent); unchecked { { (signedCoefficient, exponent) = normalize(signedCoefficient, exponent); @@ -409,6 +413,9 @@ library LibDecimalFloatImplementation { } } } + console2.log("normalized"); + console2.logInt(signedCoefficient); + console2.logInt(exponent); // This is a positive log. i.e. log(x) where x >= 1. if (exponent > -38) { @@ -420,12 +427,14 @@ library LibDecimalFloatImplementation { int256 y1Coefficient; int256 y2Coefficient; int256 x1Coefficient; + int256 x2Coefficient; int256 x1Exponent = exponent; bool interpolate; // Table lookup. { uint256 scale = 1e34; + uint256 idx; assembly ("memory-safe") { //slither-disable-next-line divide-before-multiply function lookupTableVal(tables, index) -> result { @@ -458,27 +467,41 @@ library LibDecimalFloatImplementation { // deliberate here. //slither-disable-next-line divide-before-multiply x1Coefficient := div(signedCoefficient, scale) - let index := sub(x1Coefficient, 1000) + idx := sub(x1Coefficient, 1000) x1Coefficient := mul(x1Coefficient, scale) + x2Coefficient := add(x1Coefficient, scale) interpolate := iszero(eq(x1Coefficient, signedCoefficient)) - y1Coefficient := mul(scale, lookupTableVal(tablesDataContract, index)) + y1Coefficient := mul(scale, lookupTableVal(tablesDataContract, idx)) - if interpolate { - y2Coefficient := mul(scale, lookupTableVal(tablesDataContract, add(index, 1))) - } + if interpolate { y2Coefficient := mul(scale, lookupTableVal(tablesDataContract, add(idx, 1))) } } + console2.log("index"); + console2.logUint(idx); } + console2.log("lookup"); + console2.logInt(x1Coefficient); + console2.logInt(x1Exponent); + console2.logInt(y1Coefficient); + console2.logInt(y2Coefficient); + if (interpolate) { + console2.log("yes interpolate"); + console2.logInt(signedCoefficient); + console2.logInt(exponent); (signedCoefficient, exponent) = unitLinearInterpolation( - signedCoefficient, exponent, x1Coefficient, exponent, -39, y1Coefficient, y2Coefficient, -38 + x1Coefficient, signedCoefficient, x2Coefficient, exponent, y1Coefficient, y2Coefficient, -38 ); } else { signedCoefficient = y1Coefficient; exponent = -38; } + console2.log("interpolated"); + console2.logInt(signedCoefficient); + console2.logInt(exponent); + return add(signedCoefficient, exponent, x1Exponent + 37, 0); } // This is a negative log. i.e. log(x) where 0 < x < 1. @@ -519,14 +542,22 @@ library LibDecimalFloatImplementation { characteristicMantissa(signedCoefficient, exponent); int256 characteristicExponent = exponent; { - (int256 idx, bool interpolate) = mantissa4(mantissaCoefficient, exponent); + (int256 idx, bool interpolate, int256 scale) = mantissa4(mantissaCoefficient, exponent); (int256 y1Coefficient, int256 y2Coefficient) = lookupAntilogTableY1Y2(tablesDataContract, uint256(idx), interpolate); + console2.log("power10 interpolation"); + console2.logInt(mantissaCoefficient); + console2.logInt(exponent); + console2.logInt(idx); + console2.logInt(idx * scale); + console2.logInt(y1Coefficient); + console2.logInt(y2Coefficient); if (interpolate) { (signedCoefficient, exponent) = unitLinearInterpolation( - mantissaCoefficient, exponent, idx, -4, -41, y1Coefficient, y2Coefficient, -4 + idx * scale, mantissaCoefficient, (idx + 1) * scale, exponent, y1Coefficient, y2Coefficient, -4 ); } else { + console2.log("not interpolate"); signedCoefficient = y1Coefficient; exponent = -4; } @@ -777,23 +808,23 @@ library LibDecimalFloatImplementation { } } - function mantissa4(int256 signedCoefficient, int256 exponent) internal pure returns (int256, bool) { + function mantissa4(int256 signedCoefficient, int256 exponent) internal pure returns (int256, bool, int256) { unchecked { if (exponent == -4) { - return (signedCoefficient, false); + return (signedCoefficient, false, 1); } else if (exponent < -4) { if (exponent < -80) { - return (0, signedCoefficient != 0); + return (0, signedCoefficient != 0, 1); } int256 scale = int256(10 ** uint256(-(exponent + 4))); //slither-disable-next-line divide-before-multiply int256 rescaled = signedCoefficient / scale; - return (rescaled, rescaled * scale != signedCoefficient); + return (rescaled, rescaled * scale != signedCoefficient, scale); } else if (exponent >= 0) { - return (0, false); + return (0, false, 1); } else { // exponent is [-3, -1] - return (signedCoefficient * int256(10 ** uint256(4 + exponent)), false); + return (signedCoefficient * int256(10 ** uint256(4 + exponent)), false, 1); } } } @@ -829,11 +860,10 @@ library LibDecimalFloatImplementation { // Linear interpolation. // y = y1 + ((x - x1) * (y2 - y1)) / (x2 - x1) function unitLinearInterpolation( + int256 x1Coefficient, int256 xCoefficient, + int256 x2Coefficient, int256 xExponent, - int256 x1Coefficient, - int256 x1Exponent, - int256 xUnitExponent, int256 y1Coefficient, int256 y2Coefficient, int256 yExponent @@ -843,7 +873,14 @@ library LibDecimalFloatImplementation { { // x - x1 - (int256 xDiffCoefficient, int256 xDiffExponent) = sub(xCoefficient, xExponent, x1Coefficient, x1Exponent); + (int256 xDiffCoefficient, int256 xDiffExponent) = sub(xCoefficient, xExponent, x1Coefficient, xExponent); + // console2.log("x - x1"); + // console2.logInt(xCoefficient); + // console2.logInt(xExponent); + // console2.logInt(x1Coefficient); + // console2.logInt(x1Exponent); + // console2.logInt(xDiffCoefficient); + // console2.logInt(xDiffExponent); // y2 - y1 (int256 yDiffCoefficient, int256 yDiffExponent) = sub(y2Coefficient, yExponent, y1Coefficient, yExponent); @@ -853,9 +890,12 @@ library LibDecimalFloatImplementation { multiply(xDiffCoefficient, xDiffExponent, yDiffCoefficient, yDiffExponent); } - // Diff between x2 and x1 is always 1 unit. + // x2 - x1 + (int256 xDiffCoefficient, int256 xDiffExponent) = sub(x2Coefficient, xExponent, x1Coefficient, xExponent); + + // ((x - x1) * (y2 - y1)) / (x2 - x1) (int256 yMarginalSignedCoefficient, int256 yMarginalExponent) = - divide(numeratorSignedCoefficient, numeratorExponent, 1e37, xUnitExponent); + divide(numeratorSignedCoefficient, numeratorExponent, xDiffCoefficient, xDiffExponent); // y1 + ((x - x1) * (y2 - y1)) / (x2 - x1) (int256 signedCoefficient, int256 exponent) = diff --git a/src/lib/table/LibLogTable.sol b/src/lib/table/LibLogTable.sol index a62bb58c..f647713e 100644 --- a/src/lib/table/LibLogTable.sol +++ b/src/lib/table/LibLogTable.sol @@ -1,8 +1,6 @@ // 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 diff --git a/test/src/lib/LibDecimalFloat.power.t.sol b/test/src/lib/LibDecimalFloat.power.t.sol index ae79a879..65f997dd 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -34,7 +34,9 @@ contract LibDecimalFloatPowerTest is LogTest { function testPowers() external { checkPower(5e37, -38, 3e37, -36, 9.3283582089552238805970149253731343283e37, -47); checkPower(5e37, -38, 6e37, -36, 8.7108013937282229965156794425087108013e37, -56); + // // Issues found in fuzzing from here. checkPower(99999, 0, 12182, 0, 1000, 60907); + checkPower(1785215562, 0, 18, 0, 3388, 163); } function checkRoundTrip(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) @@ -44,7 +46,17 @@ contract LibDecimalFloatPowerTest is LogTest { Float b = LibDecimalFloat.packLossless(signedCoefficientB, exponentB); address tables = logTables(); Float c = a.power(b, tables); + (int256 signedCoefficientC, int256 exponentC) = c.unpack(); + console2.log("c"); + console2.logInt(signedCoefficientC); + console2.logInt(exponentC); Float roundTrip = c.power(b.inv(), tables); + + (int256 signedCoefficientRoundTrip, int256 exponentRoundTrip) = roundTrip.unpack(); + console2.log("round trip"); + console2.logInt(signedCoefficientRoundTrip); + console2.logInt(exponentRoundTrip); + Float diff = a.divide(roundTrip).sub(LibDecimalFloat.packLossless(1, 0)).abs(); assertTrue(!diff.gt(LibDecimalFloat.packLossless(0.002e3, -3)), "diff"); @@ -52,7 +64,7 @@ contract LibDecimalFloatPowerTest is LogTest { /// X^Y^(1/Y) = X /// Can generally round trip whatever within 0.25% of the original value. - function testRoundTrip() external { + function testRoundTripSimple() external { checkRoundTrip(5, 0, 2, 0); checkRoundTrip(5, 0, 3, 0); checkRoundTrip(50, 0, 40, 0); @@ -101,7 +113,7 @@ contract LibDecimalFloatPowerTest is LogTest { console2.logInt(signedCoefficientDiff); console2.logInt(exponentDiff); - assertTrue(!diff.gt(LibDecimalFloat.packLossless(0.9e1, -1)), "diff"); + assertTrue(!diff.gt(LibDecimalFloat.packLossless(285, -4)), "diff"); } } catch (bytes memory err) {} } From 6037499f6ead91f6cc6cf187cc8bb8f68de0c59d Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Wed, 23 Apr 2025 22:56:47 +0400 Subject: [PATCH 08/13] fix tests --- src/lib/LibDecimalFloat.sol | 6 --- src/lib/LibDecimalFloatDeploy.sol | 3 -- .../LibDecimalFloatImplementation.sol | 48 ++----------------- test/src/lib/LibDecimalFloat.add.t.sol | 13 +++-- test/src/lib/LibDecimalFloat.decimal.t.sol | 3 +- .../lib/LibDecimalFloat.decimalLossless.t.sol | 9 ++-- test/src/lib/LibDecimalFloat.divide.t.sol | 11 +++-- test/src/lib/LibDecimalFloat.floor.t.sol | 38 ++++++--------- test/src/lib/LibDecimalFloat.frac.t.sol | 34 +++++++------ test/src/lib/LibDecimalFloat.gt.t.sol | 2 +- test/src/lib/LibDecimalFloat.inv.t.sol | 13 ++--- test/src/lib/LibDecimalFloat.log10.t.sol | 12 +++-- test/src/lib/LibDecimalFloat.lt.t.sol | 2 +- test/src/lib/LibDecimalFloat.multiply.t.sol | 11 +++-- test/src/lib/LibDecimalFloat.pack.t.sol | 7 +-- test/src/lib/LibDecimalFloat.power.t.sol | 45 ++++------------- 16 files changed, 92 insertions(+), 165 deletions(-) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index ef38d31c..d1ee3061 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -28,8 +28,6 @@ import { EXPONENT_MIN } from "./implementation/LibDecimalFloatImplementation.sol"; -import {console2} from "forge-std/Test.sol"; - type Float is bytes32; /// @dev When normalizing a number, how far we "leap" when very far from @@ -591,10 +589,6 @@ library LibDecimalFloat { (int256 signedCoefficientC, int256 exponentC) = LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficientA, exponentA); - console2.log("power log10"); - console2.logInt(signedCoefficientC); - console2.logInt(exponentC); - (int256 signedCoefficientB, int256 exponentB) = b.unpack(); (signedCoefficientC, exponentC) = diff --git a/src/lib/LibDecimalFloatDeploy.sol b/src/lib/LibDecimalFloatDeploy.sol index 72bed115..7d75712a 100644 --- a/src/lib/LibDecimalFloatDeploy.sol +++ b/src/lib/LibDecimalFloatDeploy.sol @@ -12,8 +12,6 @@ import {LibDataContract, DataContractMemoryContainer} from "rain.datacontract/li import {LibBytes} from "rain.solmem/lib/LibBytes.sol"; import {LibMemCpy, Pointer} from "rain.solmem/lib/LibMemCpy.sol"; -import {console2} from "forge-std/Test.sol"; - library LibDecimalFloatDeploy { function combinedTables() internal pure returns (bytes memory) { return @@ -22,7 +20,6 @@ library LibDecimalFloatDeploy { function dataContract() internal pure returns (DataContractMemoryContainer) { bytes memory tables = combinedTables(); - console2.logBytes(tables); (DataContractMemoryContainer container, Pointer pointer) = LibDataContract.newContainer(tables.length); LibMemCpy.unsafeCopyBytesTo(LibBytes.dataPointer(tables), pointer, tables.length); return container; diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 24b6cb6f..920f3b67 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -10,7 +10,6 @@ import { ANTI_LOG_TABLES_SMALL } from "../../generated/LogTables.pointers.sol"; import {LibDecimalFloat} from "../LibDecimalFloat.sol"; -import {console2} from "forge-std/Test.sol"; error WithTargetExponentOverflow(int256 signedCoefficient, int256 exponent, int256 targetExponent); @@ -398,9 +397,6 @@ library LibDecimalFloatImplementation { view returns (int256, int256) { - console2.log("log10 start"); - console2.logInt(signedCoefficient); - console2.logInt(exponent); unchecked { { (signedCoefficient, exponent) = normalize(signedCoefficient, exponent); @@ -413,9 +409,6 @@ library LibDecimalFloatImplementation { } } } - console2.log("normalized"); - console2.logInt(signedCoefficient); - console2.logInt(exponent); // This is a positive log. i.e. log(x) where x >= 1. if (exponent > -38) { @@ -434,7 +427,6 @@ library LibDecimalFloatImplementation { // Table lookup. { uint256 scale = 1e34; - uint256 idx; assembly ("memory-safe") { //slither-disable-next-line divide-before-multiply function lookupTableVal(tables, index) -> result { @@ -467,7 +459,7 @@ library LibDecimalFloatImplementation { // deliberate here. //slither-disable-next-line divide-before-multiply x1Coefficient := div(signedCoefficient, scale) - idx := sub(x1Coefficient, 1000) + let idx := sub(x1Coefficient, 1000) x1Coefficient := mul(x1Coefficient, scale) x2Coefficient := add(x1Coefficient, scale) interpolate := iszero(eq(x1Coefficient, signedCoefficient)) @@ -476,20 +468,9 @@ library LibDecimalFloatImplementation { if interpolate { y2Coefficient := mul(scale, lookupTableVal(tablesDataContract, add(idx, 1))) } } - console2.log("index"); - console2.logUint(idx); } - console2.log("lookup"); - console2.logInt(x1Coefficient); - console2.logInt(x1Exponent); - console2.logInt(y1Coefficient); - console2.logInt(y2Coefficient); - if (interpolate) { - console2.log("yes interpolate"); - console2.logInt(signedCoefficient); - console2.logInt(exponent); (signedCoefficient, exponent) = unitLinearInterpolation( x1Coefficient, signedCoefficient, x2Coefficient, exponent, y1Coefficient, y2Coefficient, -38 ); @@ -498,10 +479,6 @@ library LibDecimalFloatImplementation { exponent = -38; } - console2.log("interpolated"); - console2.logInt(signedCoefficient); - console2.logInt(exponent); - return add(signedCoefficient, exponent, x1Exponent + 37, 0); } // This is a negative log. i.e. log(x) where 0 < x < 1. @@ -545,19 +522,11 @@ library LibDecimalFloatImplementation { (int256 idx, bool interpolate, int256 scale) = mantissa4(mantissaCoefficient, exponent); (int256 y1Coefficient, int256 y2Coefficient) = lookupAntilogTableY1Y2(tablesDataContract, uint256(idx), interpolate); - console2.log("power10 interpolation"); - console2.logInt(mantissaCoefficient); - console2.logInt(exponent); - console2.logInt(idx); - console2.logInt(idx * scale); - console2.logInt(y1Coefficient); - console2.logInt(y2Coefficient); if (interpolate) { (signedCoefficient, exponent) = unitLinearInterpolation( idx * scale, mantissaCoefficient, (idx + 1) * scale, exponent, y1Coefficient, y2Coefficient, -4 ); } else { - console2.log("not interpolate"); signedCoefficient = y1Coefficient; exponent = -4; } @@ -873,29 +842,22 @@ library LibDecimalFloatImplementation { { // x - x1 - (int256 xDiffCoefficient, int256 xDiffExponent) = sub(xCoefficient, xExponent, x1Coefficient, xExponent); - // console2.log("x - x1"); - // console2.logInt(xCoefficient); - // console2.logInt(xExponent); - // console2.logInt(x1Coefficient); - // console2.logInt(x1Exponent); - // console2.logInt(xDiffCoefficient); - // console2.logInt(xDiffExponent); + (int256 xDiffCoefficient0, int256 xDiffExponent0) = sub(xCoefficient, xExponent, x1Coefficient, xExponent); // y2 - y1 (int256 yDiffCoefficient, int256 yDiffExponent) = sub(y2Coefficient, yExponent, y1Coefficient, yExponent); // (x - x1) * (y2 - y1) (numeratorSignedCoefficient, numeratorExponent) = - multiply(xDiffCoefficient, xDiffExponent, yDiffCoefficient, yDiffExponent); + multiply(xDiffCoefficient0, xDiffExponent0, yDiffCoefficient, yDiffExponent); } // x2 - x1 - (int256 xDiffCoefficient, int256 xDiffExponent) = sub(x2Coefficient, xExponent, x1Coefficient, xExponent); + (int256 xDiffCoefficient1, int256 xDiffExponent1) = sub(x2Coefficient, xExponent, x1Coefficient, xExponent); // ((x - x1) * (y2 - y1)) / (x2 - x1) (int256 yMarginalSignedCoefficient, int256 yMarginalExponent) = - divide(numeratorSignedCoefficient, numeratorExponent, xDiffCoefficient, xDiffExponent); + divide(numeratorSignedCoefficient, numeratorExponent, xDiffCoefficient1, xDiffExponent1); // y1 + ((x - x1) * (y2 - y1)) / (x2 - x1) (int256 signedCoefficient, int256 exponent) = diff --git a/test/src/lib/LibDecimalFloat.add.t.sol b/test/src/lib/LibDecimalFloat.add.t.sol index f1ff7754..d784996d 100644 --- a/test/src/lib/LibDecimalFloat.add.t.sol +++ b/test/src/lib/LibDecimalFloat.add.t.sol @@ -15,9 +15,13 @@ contract LibDecimalFloatDecimalAddTest is Test { function addExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure - returns (int256, int256) + returns (Float) { - return LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (int256 signedCoefficientC, int256 exponentC) = + LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (Float c, bool lossless) = LibDecimalFloat.packLossy(signedCoefficientC, exponentC); + (lossless); + return c; } function addExternal(Float a, Float b) external pure returns (Float) { @@ -27,9 +31,8 @@ contract LibDecimalFloatDecimalAddTest is Test { function testAddPacked(Float a, Float b) external { (int256 signedCoefficientA, int256 exponentA) = LibDecimalFloat.unpack(a); (int256 signedCoefficientB, int256 exponentB) = LibDecimalFloat.unpack(b); - try this.addExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( - int256 signedCoefficient, int256 exponent - ) { + try this.addExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns (Float resultParts) { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.unpack(resultParts); Float result = this.addExternal(a, b); (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = LibDecimalFloat.unpack(result); assertEq(signedCoefficient, signedCoefficientUnpacked); diff --git a/test/src/lib/LibDecimalFloat.decimal.t.sol b/test/src/lib/LibDecimalFloat.decimal.t.sol index a7dc2c81..70bd49d1 100644 --- a/test/src/lib/LibDecimalFloat.decimal.t.sol +++ b/test/src/lib/LibDecimalFloat.decimal.t.sol @@ -28,8 +28,9 @@ contract LibDecimalFloatDecimalTest is Test { return float.toFixedDecimalLossy(decimals); } - /// Memory version of from behaves same as stack version. function testFromFixedDecimalLossyPacked(uint256 value, uint8 decimals) external pure { + // Above this bound the conversion will be lossy. + value = bound(value, 0, type(uint224).max / 10); (int256 signedCoefficient, int256 exponent, bool lossless) = LibDecimalFloat.fromFixedDecimalLossy(value, decimals); diff --git a/test/src/lib/LibDecimalFloat.decimalLossless.t.sol b/test/src/lib/LibDecimalFloat.decimalLossless.t.sol index 03e4b916..1a77f0aa 100644 --- a/test/src/lib/LibDecimalFloat.decimalLossless.t.sol +++ b/test/src/lib/LibDecimalFloat.decimalLossless.t.sol @@ -30,12 +30,12 @@ contract LibDecimalFloatDecimalLosslessTest is Test { return LibDecimalFloat.toFixedDecimalLossless(signedCoefficient, exponent, decimals); } - function toFixedDecimalLosslessMemExternal(Float float, uint8 decimals) external pure returns (uint256) { + function toFixedDecimalLosslessExternal(Float float, uint8 decimals) external pure returns (uint256) { return float.toFixedDecimalLossless(decimals); } - /// Memory version of from behaves the same as stack version. function testFromFixedDecimalLosslessMem(uint256 value, uint8 decimals) external { + value = bound(value, 0, uint256(int256(type(int224).max))); (,, bool losslessPreflight) = LibDecimalFloat.fromFixedDecimalLossy(value, decimals); if (!losslessPreflight) { vm.expectRevert( @@ -49,15 +49,14 @@ contract LibDecimalFloatDecimalLosslessTest is Test { assertEq(exponent, exponentPacked); } - /// Memory version of to behaves the same as stack version. - function testToFixedDecimalLosslessMem(Float float, uint8 decimals) external { + function testToFixedDecimalLosslessPacked(Float float, uint8 decimals) external { (int256 signedCoefficient, int256 exponent) = float.unpack(); try this.toFixedDecimalLosslessExternal(signedCoefficient, exponent, decimals) returns (uint256 value) { uint256 valueFloat = float.toFixedDecimalLossless(decimals); assertEq(valueFloat, value); } catch (bytes memory err) { vm.expectRevert(err); - uint256 valueFloat = this.toFixedDecimalLosslessMemExternal(float, decimals); + uint256 valueFloat = this.toFixedDecimalLosslessExternal(float, decimals); (valueFloat); } } diff --git a/test/src/lib/LibDecimalFloat.divide.t.sol b/test/src/lib/LibDecimalFloat.divide.t.sol index 5f5b4c25..5dbcb16d 100644 --- a/test/src/lib/LibDecimalFloat.divide.t.sol +++ b/test/src/lib/LibDecimalFloat.divide.t.sol @@ -12,9 +12,13 @@ contract LibDecimalFloatDivideTest is Test { function divideExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure - returns (int256, int256) + returns (Float) { - return LibDecimalFloatImplementation.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (int256 signedCoefficientC, int256 exponentC) = + LibDecimalFloatImplementation.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (Float c, bool lossless) = LibDecimalFloat.packLossy(signedCoefficientC, exponentC); + (lossless); + return c; } function divideExternal(Float floatA, Float floatB) external pure returns (Float) { @@ -25,8 +29,9 @@ contract LibDecimalFloatDivideTest is Test { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); try this.divideExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( - int256 signedCoefficient, int256 exponent + Float resultParts ) { + (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.unpack(resultParts); Float float = this.divideExternal(a, b); (int256 signedCoefficientFloat, int256 exponentFloat) = float.unpack(); assertEq(signedCoefficient, signedCoefficientFloat); diff --git a/test/src/lib/LibDecimalFloat.floor.t.sol b/test/src/lib/LibDecimalFloat.floor.t.sol index 89a57d2e..57f64fc3 100644 --- a/test/src/lib/LibDecimalFloat.floor.t.sol +++ b/test/src/lib/LibDecimalFloat.floor.t.sol @@ -20,19 +20,19 @@ contract LibDecimalFloatFloorTest is Test { } /// Every non negative exponent is identity for floor. - function testFloorNonNegative(int256 x, int256 exponent) external pure { - exponent = bound(exponent, 0, type(int256).max); + function testFloorNonNegative(int224 x, int256 exponent) external pure { + exponent = bound(exponent, 0, type(int32).max); checkFloor(x, exponent, x, exponent); } /// If the exponent is less than -76 then the floor is 0. - function testFloorLessThanMin(int256 x, int256 exponent) external pure { - exponent = bound(exponent, type(int256).min, -77); + function testFloorLessThanMin(int224 x, int256 exponent) external pure { + exponent = bound(exponent, type(int32).min, -77); checkFloor(x, exponent, 0, exponent); } /// For exponents [-76,-1] the floor is the / 1. - function testFloorInRange(int256 x, int256 exponent) external pure { + function testFloorInRange(int224 x, int256 exponent) external pure { exponent = bound(exponent, -76, -1); int256 scale = int256(10 ** uint256(-exponent)); checkFloor(x, exponent, (x / scale) * scale, exponent); @@ -52,27 +52,19 @@ contract LibDecimalFloatFloorTest is Test { checkFloor(123456789, -9, 0, -9); checkFloor(123456789, -10, 0, -10); checkFloor(123456789, -11, 0, -11); - checkFloor(type(int256).max, 0, type(int256).max, 0); - checkFloor(type(int256).min, 0, type(int256).min, 0); + checkFloor(type(int224).max, 0, type(int224).max, 0); + checkFloor(type(int224).min, 0, type(int224).min, 0); checkFloor(2.5e37, -37, 2e37, -37); - checkFloor(type(int256).max, 0, type(int256).max, 0); - checkFloor( - type(int256).max, -1, 57896044618658097711785492504343953926634992332820282019728792003956564819960, -1 - ); - checkFloor( - type(int256).max, -2, 57896044618658097711785492504343953926634992332820282019728792003956564819900, -2 - ); - checkFloor( - type(int256).max, -3, 57896044618658097711785492504343953926634992332820282019728792003956564819000, -3 - ); - checkFloor( - type(int256).max, -4, 57896044618658097711785492504343953926634992332820282019728792003956564810000, -4 - ); - checkFloor(type(int256).max, -77, 0, -77); - checkFloor(type(int256).max, -78, 0, -78); - checkFloor(type(int256).max, -76, 5e76, -76); + checkFloor(type(int224).max, 0, type(int224).max, 0); + checkFloor(type(int224).max, -1, 13479973333575319897333507543509815336818572211270286240551805124600, -1); + checkFloor(type(int224).max, -2, 13479973333575319897333507543509815336818572211270286240551805124600, -2); + checkFloor(type(int224).max, -3, 13479973333575319897333507543509815336818572211270286240551805124000, -3); + checkFloor(type(int224).max, -4, 13479973333575319897333507543509815336818572211270286240551805120000, -4); + checkFloor(type(int224).max, -77, 0, -77); + checkFloor(type(int224).max, -78, 0, -78); + checkFloor(type(int224).max, -76, 0, -76); } function testFloorGasZero() external pure { diff --git a/test/src/lib/LibDecimalFloat.frac.t.sol b/test/src/lib/LibDecimalFloat.frac.t.sol index 69e95698..eedac9fa 100644 --- a/test/src/lib/LibDecimalFloat.frac.t.sol +++ b/test/src/lib/LibDecimalFloat.frac.t.sol @@ -20,20 +20,20 @@ contract LibDecimalFloatFracTest is Test { } /// Every non negative exponent has no fractional component. - function testFracNonNegative(int256 x, int256 exponent) external pure { - exponent = bound(exponent, 0, type(int256).max); + function testFracNonNegative(int224 x, int256 exponent) external pure { + exponent = bound(exponent, 0, type(int32).max); checkFrac(x, exponent, 0, exponent); } /// If the exponent is less than -76 then the fractional component is the /// same as the input. - function testFracLessThanMin(int256 x, int256 exponent) external pure { - exponent = bound(exponent, type(int256).min, -77); + function testFracLessThanMin(int224 x, int256 exponent) external pure { + exponent = bound(exponent, type(int32).min, -77); checkFrac(x, exponent, x, exponent); } /// For exponents [-76,-1] the fractional component is the modulo of 1. - function testFracInRange(int256 x, int256 exponent) external pure { + function testFracInRange(int224 x, int256 exponent) external pure { exponent = bound(exponent, -76, -1); checkFrac(x, exponent, x % int256(10 ** uint256(-exponent)), exponent); } @@ -52,21 +52,19 @@ contract LibDecimalFloatFracTest is Test { checkFrac(123456789, -9, 123456789, -9); checkFrac(123456789, -10, 123456789, -10); checkFrac(123456789, -11, 123456789, -11); - checkFrac(type(int256).max, 0, 0, 0); - checkFrac(type(int256).min, 0, 0, 0); - checkFrac(2.5e37, -37, 0.5e37, -37); - checkFrac(type(int256).max, 0, 0, 0); - checkFrac(type(int256).max, -1, 7, -1); - checkFrac(type(int256).max, -2, 67, -2); - checkFrac(type(int256).max, -3, 967, -3); - checkFrac(type(int256).max, -4, 9967, -4); - checkFrac(type(int256).max, -77, type(int256).max, -77); - checkFrac(type(int256).max, -78, type(int256).max, -78); - checkFrac( - type(int256).max, -76, 7896044618658097711785492504343953926634992332820282019728792003956564819967, -76 - ); + // type(int224.max) is 13479973333575319897333507543509815336818572211270286240551805124607 + checkFrac(type(int224).max, 0, 0, 0); + checkFrac(type(int224).min, 0, 0, 0); + checkFrac(type(int224).max, 0, 0, 0); + checkFrac(type(int224).max, -1, 7, -1); + checkFrac(type(int224).max, -2, 7, -2); + checkFrac(type(int224).max, -3, 607, -3); + checkFrac(type(int224).max, -4, 4607, -4); + checkFrac(type(int224).max, -77, type(int224).max, -77); + checkFrac(type(int224).max, -78, type(int224).max, -78); + checkFrac(type(int224).max, -76, 13479973333575319897333507543509815336818572211270286240551805124607, -76); } function testFracGasZero() external pure { diff --git a/test/src/lib/LibDecimalFloat.gt.t.sol b/test/src/lib/LibDecimalFloat.gt.t.sol index 0adf29e4..d6ff44d5 100644 --- a/test/src/lib/LibDecimalFloat.gt.t.sol +++ b/test/src/lib/LibDecimalFloat.gt.t.sol @@ -10,7 +10,7 @@ import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatGtTest is Test { using LibDecimalFloat for Float; - function testGtReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + function testGtReference(int224 signedCoefficientA, int32 exponentA, int224 signedCoefficientB, int32 exponentB) external pure { diff --git a/test/src/lib/LibDecimalFloat.inv.t.sol b/test/src/lib/LibDecimalFloat.inv.t.sol index 4b191709..837d3cde 100644 --- a/test/src/lib/LibDecimalFloat.inv.t.sol +++ b/test/src/lib/LibDecimalFloat.inv.t.sol @@ -8,20 +8,21 @@ import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFl contract LibDecimalFloatInvTest is Test { using LibDecimalFloat for Float; - function invExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) { - return LibDecimalFloatImplementation.inv(signedCoefficient, exponent); + function invExternal(int256 signedCoefficient, int256 exponent) external pure returns (Float) { + (signedCoefficient, exponent) = LibDecimalFloatImplementation.inv(signedCoefficient, exponent); + (Float float, bool lossless) = LibDecimalFloat.packLossy(signedCoefficient, exponent); + (lossless); + return float; } function invExternal(Float float) external pure returns (Float) { return LibDecimalFloat.inv(float); } - /// Stack and mem are the same. function testInvMem(Float float) external { (int256 signedCoefficient, int256 exponent) = float.unpack(); - try this.invExternal(signedCoefficient, exponent) returns ( - int256 signedCoefficientResult, int256 exponentResult - ) { + try this.invExternal(signedCoefficient, exponent) returns (Float floatParts) { + (int256 signedCoefficientResult, int256 exponentResult) = floatParts.unpack(); Float floatInv = this.invExternal(float); (int256 signedCoefficientResultUnpacked, int256 exponentResultUnpacked) = floatInv.unpack(); assertEq(signedCoefficientResultUnpacked, signedCoefficientResult); diff --git a/test/src/lib/LibDecimalFloat.log10.t.sol b/test/src/lib/LibDecimalFloat.log10.t.sol index 3317e40a..a99747ca 100644 --- a/test/src/lib/LibDecimalFloat.log10.t.sol +++ b/test/src/lib/LibDecimalFloat.log10.t.sol @@ -8,9 +8,12 @@ import {LogTest} from "../../abstract/LogTest.sol"; contract LibDecimalFloatLog10Test is LogTest { using LibDecimalFloat for Float; - function log10External(int256 signedCoefficient, int256 exponent) external returns (int256, int256) { + function log10External(int256 signedCoefficient, int256 exponent) external returns (Float) { address tables = logTables(); - return LibDecimalFloatImplementation.log10(tables, signedCoefficient, exponent); + (signedCoefficient, exponent) = LibDecimalFloatImplementation.log10(tables, signedCoefficient, exponent); + (Float float, bool lossless) = LibDecimalFloat.packLossy(signedCoefficient, exponent); + (lossless); + return float; } function log10External(Float float) external returns (Float) { @@ -19,9 +22,8 @@ contract LibDecimalFloatLog10Test is LogTest { function testLog10Packed(Float float) external { (int256 signedCoefficient, int256 exponent) = float.unpack(); - try this.log10External(signedCoefficient, exponent) returns ( - int256 signedCoefficientResult, int256 exponentResult - ) { + try this.log10External(signedCoefficient, exponent) returns (Float floatParts) { + (int256 signedCoefficientResult, int256 exponentResult) = floatParts.unpack(); Float floatLog10 = this.log10External(float); (int256 signedCoefficientResultUnpacked, int256 exponentResultUnpacked) = floatLog10.unpack(); assertEq(signedCoefficientResultUnpacked, signedCoefficientResult); diff --git a/test/src/lib/LibDecimalFloat.lt.t.sol b/test/src/lib/LibDecimalFloat.lt.t.sol index cde2ed21..bbe97dd0 100644 --- a/test/src/lib/LibDecimalFloat.lt.t.sol +++ b/test/src/lib/LibDecimalFloat.lt.t.sol @@ -34,7 +34,7 @@ contract LibDecimalFloatLtTest is Test { } /// xeX < xeY if X < Y && x > 0 - function testLtXEAnyVsXEAny(int256 x, int256 exponentA, int256 exponentB) external pure { + function testLtXEAnyVsXEAny(int256 x, int32 exponentA, int32 exponentB) external pure { x = bound(x, 1, type(int224).max); Float a = LibDecimalFloat.packLossless(x, exponentA); diff --git a/test/src/lib/LibDecimalFloat.multiply.t.sol b/test/src/lib/LibDecimalFloat.multiply.t.sol index f4a43355..538621e9 100644 --- a/test/src/lib/LibDecimalFloat.multiply.t.sol +++ b/test/src/lib/LibDecimalFloat.multiply.t.sol @@ -12,9 +12,13 @@ contract LibDecimalFloatMultiplyTest is Test { function multiplyExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure - returns (int256, int256) + returns (Float) { - return LibDecimalFloatImplementation.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (int256 signedCoefficientC, int256 exponentC) = + LibDecimalFloatImplementation.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + (Float c, bool lossless) = LibDecimalFloat.packLossy(signedCoefficientC, exponentC); + (lossless); + return c; } function multiplyExternal(Float floatA, Float floatB) external pure returns (Float) { @@ -25,8 +29,9 @@ contract LibDecimalFloatMultiplyTest is Test { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); (int256 signedCoefficientB, int256 exponentB) = b.unpack(); try this.multiplyExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( - int256 signedCoefficient, int256 exponent + Float floatExternal ) { + (int256 signedCoefficient, int256 exponent) = floatExternal.unpack(); Float float = this.multiplyExternal(a, b); (int256 signedCoefficientUnpacked, int256 exponentUnpacked) = float.unpack(); assertEq(signedCoefficient, signedCoefficientUnpacked); diff --git a/test/src/lib/LibDecimalFloat.pack.t.sol b/test/src/lib/LibDecimalFloat.pack.t.sol index 03e2991f..40cbdf4a 100644 --- a/test/src/lib/LibDecimalFloat.pack.t.sol +++ b/test/src/lib/LibDecimalFloat.pack.t.sol @@ -21,11 +21,6 @@ contract LibDecimalFloatPackTest is Test { assertTrue(lossless, "lossless"); assertEq(signedCoefficient, signedCoefficientOut, "coefficient"); - if (signedCoefficient != 0) { - assertEq(exponent, exponentOut, "exponent"); - } else { - // 0 exponent is always 0. - assertEq(exponentOut, 0, "exponent"); - } + assertEq(exponent, exponentOut, "exponent"); } } diff --git a/test/src/lib/LibDecimalFloat.power.t.sol b/test/src/lib/LibDecimalFloat.power.t.sol index 65f997dd..566ce9fb 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -11,6 +11,10 @@ import {console2} from "forge-std/Test.sol"; contract LibDecimalFloatPowerTest is LogTest { using LibDecimalFloat for Float; + function diffLimit() internal pure returns (Float) { + return LibDecimalFloat.packLossless(94, -3); + } + function checkPower( int256 signedCoefficientA, int256 exponentA, @@ -46,20 +50,12 @@ contract LibDecimalFloatPowerTest is LogTest { Float b = LibDecimalFloat.packLossless(signedCoefficientB, exponentB); address tables = logTables(); Float c = a.power(b, tables); - (int256 signedCoefficientC, int256 exponentC) = c.unpack(); - console2.log("c"); - console2.logInt(signedCoefficientC); - console2.logInt(exponentC); - Float roundTrip = c.power(b.inv(), tables); - (int256 signedCoefficientRoundTrip, int256 exponentRoundTrip) = roundTrip.unpack(); - console2.log("round trip"); - console2.logInt(signedCoefficientRoundTrip); - console2.logInt(exponentRoundTrip); + Float roundTrip = c.power(b.inv(), tables); Float diff = a.divide(roundTrip).sub(LibDecimalFloat.packLossless(1, 0)).abs(); - assertTrue(!diff.gt(LibDecimalFloat.packLossless(0.002e3, -3)), "diff"); + assertTrue(!diff.gt(diffLimit()), "diff"); } /// X^Y^(1/Y) = X @@ -72,6 +68,8 @@ contract LibDecimalFloatPowerTest is LogTest { checkRoundTrip(5, -1, 2, -1); checkRoundTrip(5, 10, 3, 5); checkRoundTrip(5, -1, 100, 0); + checkRoundTrip(7721, 0, -1, -2); + checkRoundTrip(4157, 0, -1, -2); } function powerExternal(Float a, Float b) external returns (Float) { @@ -88,32 +86,7 @@ contract LibDecimalFloatPowerTest is LogTest { try this.powerExternal(c, inv) returns (Float roundTrip) { if (roundTrip.isZero()) {} else { Float diff = a.divide(roundTrip).sub(LibDecimalFloat.packLossless(1, 0)).abs(); - console2.log("a"); - (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - console2.logInt(signedCoefficientA); - console2.logInt(exponentA); - console2.log("b"); - (int256 signedCoefficientB, int256 exponentB) = b.unpack(); - console2.logInt(signedCoefficientB); - console2.logInt(exponentB); - console2.log("c"); - (int256 signedCoefficientC, int256 exponentC) = c.unpack(); - console2.logInt(signedCoefficientC); - console2.logInt(exponentC); - console2.log("inv"); - (int256 signedCoefficientInv, int256 exponentInv) = inv.unpack(); - console2.logInt(signedCoefficientInv); - console2.logInt(exponentInv); - console2.log("roundTrip"); - (int256 signedCoefficientRoundTrip, int256 exponentRoundTrip) = roundTrip.unpack(); - console2.logInt(signedCoefficientRoundTrip); - console2.logInt(exponentRoundTrip); - console2.log("diff"); - (int256 signedCoefficientDiff, int256 exponentDiff) = diff.unpack(); - console2.logInt(signedCoefficientDiff); - console2.logInt(exponentDiff); - - assertTrue(!diff.gt(LibDecimalFloat.packLossless(285, -4)), "diff"); + assertTrue(!diff.gt(diffLimit()), "diff"); } } catch (bytes memory err) {} } From c740efc9bddc3dca02340f9327fa5a3ddc2bdaff Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Wed, 23 Apr 2025 22:57:47 +0400 Subject: [PATCH 09/13] snapshot --- .gas-snapshot | 391 ++++++++++++++++++++++++-------------------------- 1 file changed, 190 insertions(+), 201 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 518bed2e..2ca445f3 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,214 +1,203 @@ -LibDecimalFloatAbsTest:testAbsMem((int256,int256)) (runs: 5101, μ: 5951, ~: 5890) -LibDecimalFloatAbsTest:testAbsMinValue(int256) (runs: 5101, μ: 4405, ~: 4405) -LibDecimalFloatAbsTest:testAbsNegative(int256,int256) (runs: 5101, μ: 9370, ~: 9569) -LibDecimalFloatAbsTest:testAbsNonNegative(int256,int256) (runs: 5101, μ: 8570, ~: 8347) -LibDecimalFloatDecimalAddTest:testAdd123456789987654321() (gas: 4757) -LibDecimalFloatDecimalAddTest:testAdd123456789e9987654321() (gas: 4788) -LibDecimalFloatDecimalAddTest:testAddMem((int256,int256),(int256,int256)) (runs: 5101, μ: 7765, ~: 8009) -LibDecimalFloatDecimalAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 5101, μ: 12353, ~: 12322) -LibDecimalFloatDecimalAddTest:testAddOneOneNotNormalized() (gas: 5349) -LibDecimalFloatDecimalAddTest:testAddOneOnePreNormalized() (gas: 4228) -LibDecimalFloatDecimalAddTest:testAddOneZero() (gas: 3634) -LibDecimalFloatDecimalAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 5101, μ: 5702, ~: 5746) -LibDecimalFloatDecimalAddTest:testAddZero() (gas: 3632) -LibDecimalFloatDecimalAddTest:testAddZeroAnyExponent(int128) (runs: 5101, μ: 8879, ~: 8914) -LibDecimalFloatDecimalAddTest:testAddZeroOne() (gas: 3633) -LibDecimalFloatDecimalAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 5101, μ: 13178, ~: 13156) -LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 19216) -LibDecimalFloatDecimalAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 5098, μ: 16280, ~: 16250) -LibDecimalFloatDecimalAddTest:testGasAddOne() (gas: 947) -LibDecimalFloatDecimalAddTest:testGasAddZero() (gas: 399) -LibDecimalFloatDecimalLosslessTest:testFromFixedDecimalLosslessFail(uint256,uint8) (runs: 5100, μ: 9232, ~: 9281) -LibDecimalFloatDecimalLosslessTest:testFromFixedDecimalLosslessMem(uint256,uint8) (runs: 5101, μ: 4416, ~: 4423) -LibDecimalFloatDecimalLosslessTest:testFromFixedDecimalLosslessPass(uint256,uint8) (runs: 5101, μ: 7295, ~: 7266) -LibDecimalFloatDecimalLosslessTest:testToFixedDecimalLosslessFail() (gas: 3859) -LibDecimalFloatDecimalLosslessTest:testToFixedDecimalLosslessMem((int256,int256),uint8) (runs: 5101, μ: 6854, ~: 7838) -LibDecimalFloatDecimalLosslessTest:testToFixedDecimalLosslessPass(int256,int256,uint8) (runs: 5101, μ: 14674, ~: 14633) -LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 5101, μ: 8843, ~: 8620) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyComplicated() (gas: 600575) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyMem(uint256,uint8) (runs: 5101, μ: 5321, ~: 5318) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyNormalizedMax() (gas: 588036) +LibDecimalFloatAbsTest:testAbsMinValue(int32) (runs: 5128, μ: 5081, ~: 5081) +LibDecimalFloatAbsTest:testAbsNegative(int256,int32) (runs: 5128, μ: 9926, ~: 10093) +LibDecimalFloatAbsTest:testAbsNonNegative(int256,int32) (runs: 5128, μ: 9179, ~: 8900) +LibDecimalFloatDecimalAddTest:testAddPacked(bytes32,bytes32) (runs: 5128, μ: 8315, ~: 8001) +LibDecimalFloatDecimalLosslessTest:testFromFixedDecimalLosslessFail(uint256,uint8) (runs: 5123, μ: 9242, ~: 9292) +LibDecimalFloatDecimalLosslessTest:testFromFixedDecimalLosslessMem(uint256,uint8) (runs: 5128, μ: 8005, ~: 7914) +LibDecimalFloatDecimalLosslessTest:testFromFixedDecimalLosslessPass(uint256,uint8) (runs: 5128, μ: 7330, ~: 7300) +LibDecimalFloatDecimalLosslessTest:testToFixedDecimalLosslessFail() (gas: 3882) +LibDecimalFloatDecimalLosslessTest:testToFixedDecimalLosslessPacked(bytes32,uint8) (runs: 5128, μ: 6476, ~: 5847) +LibDecimalFloatDecimalLosslessTest:testToFixedDecimalLosslessPass(int256,int256,uint8) (runs: 5128, μ: 14784, ~: 14736) +LibDecimalFloatDecimalTest:testFixedDecimalRoundTripLossless(uint256,uint8) (runs: 5128, μ: 8815, ~: 8598) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyComplicated() (gas: 600553) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyNormalizedMax() (gas: 588014) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyNormalizedMaxPlusOne() (gas: 618935) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOne() (gas: 600509) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOneMillion() (gas: 600532) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOneMillion() (gas: 600510) LibDecimalFloatDecimalTest:testFromFixedDecimalLossyOverflow() (gas: 629579) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 5101, μ: 5589, ~: 5547) -LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 5101, μ: 7033, ~: 5559) -LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 5098, μ: 14946, ~: 14885) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 5101, μ: 13526, ~: 13300) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 5101, μ: 9466, ~: 9236) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyMem((int256,int256),uint8) (runs: 5101, μ: 6917, ~: 7893) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 5101, μ: 9077, ~: 9278) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 5099, μ: 12891, ~: 13099) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 5101, μ: 13472, ~: 13252) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 13162) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 5101, μ: 12420, ~: 12355) -LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 5101, μ: 4368, ~: 4368) -LibDecimalFloatDivideTest:testDivide1Over3() (gas: 5642) -LibDecimalFloatDivideTest:testDivide1Over3Gas0() (gas: 673) -LibDecimalFloatDivideTest:testDivide1Over3Gas10() (gas: 6031) -LibDecimalFloatDivideTest:testDivide1Over9Over1Over3() (gas: 8648) -LibDecimalFloatDivideTest:testDivide1e18Over3() (gas: 5676) -LibDecimalFloatDivideTest:testDivideMem((int256,int256),(int256,int256)) (runs: 5101, μ: 9157, ~: 9320) -LibDecimalFloatDivideTest:testDivideNegative1Over3() (gas: 5722) -LibDecimalFloatDivideTest:testDivideOOMs5and2() (gas: 4519) -LibDecimalFloatDivideTest:testDivideOOMsOverTen() (gas: 5323) -LibDecimalFloatDivideTest:testDivideTenOverOOMs() (gas: 5316) -LibDecimalFloatDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 105, μ: 19158378, ~: 19158310) -LibDecimalFloatEqTest:testEqGasAZero() (gas: 405) -LibDecimalFloatEqTest:testEqGasBZero() (gas: 470) -LibDecimalFloatEqTest:testEqGasBothZero() (gas: 403) -LibDecimalFloatEqTest:testEqGasDifferentSigns() (gas: 457) -LibDecimalFloatEqTest:testEqGasExponentDiffOverflow() (gas: 552) -LibDecimalFloatEqTest:testEqMem((int256,int256),(int256,int256)) (runs: 5101, μ: 6486, ~: 6535) -LibDecimalFloatEqTest:testEqNotReverts(int256,int256,int256,int256) (runs: 5101, μ: 626, ~: 651) -LibDecimalFloatEqTest:testEqOneEAny(int256,int256) (runs: 5101, μ: 3435, ~: 3435) -LibDecimalFloatEqTest:testEqReference(int256,int256,int256,int256) (runs: 5101, μ: 9815, ~: 11305) -LibDecimalFloatEqTest:testEqX(int256) (runs: 5101, μ: 3430, ~: 3430) -LibDecimalFloatEqTest:testEqXEAnyVsXEAny(int256,int256,int256) (runs: 5100, μ: 4659, ~: 4657) -LibDecimalFloatEqTest:testEqXEqY(int256,int256,int256,int256) (runs: 5101, μ: 727, ~: 746) -LibDecimalFloatEqTest:testEqXNotY(int256,int256,int256,int256) (runs: 5100, μ: 3897, ~: 3922) -LibDecimalFloatEqTest:testEqXNotYExponents(int256,int256,int256,int256) (runs: 5101, μ: 4201, ~: 4279) -LibDecimalFloatEqTest:testEqZero(int256,int256) (runs: 5101, μ: 3412, ~: 3412) -LibDecimalFloatFloorTest:testFloorExamples() (gas: 27400) -LibDecimalFloatFloorTest:testFloorGas0() (gas: 463) -LibDecimalFloatFloorTest:testFloorGasTiny() (gas: 394) -LibDecimalFloatFloorTest:testFloorGasZero() (gas: 366) -LibDecimalFloatFloorTest:testFloorInRange(int256,int256) (runs: 5101, μ: 10080, ~: 10093) -LibDecimalFloatFloorTest:testFloorLessThanMin(int256,int256) (runs: 5101, μ: 9263, ~: 9440) -LibDecimalFloatFloorTest:testFloorMem((int256,int256)) (runs: 5101, μ: 6123, ~: 6101) -LibDecimalFloatFloorTest:testFloorNonNegative(int256,int256) (runs: 5101, μ: 8751, ~: 8528) -LibDecimalFloatFloorTest:testFloorNotReverts(int256,int256) (runs: 5101, μ: 483, ~: 472) -LibDecimalFloatFracTest:testFracExamples() (gas: 27404) -LibDecimalFloatFracTest:testFracGas0() (gas: 486) -LibDecimalFloatFracTest:testFracGasTiny() (gas: 372) -LibDecimalFloatFracTest:testFracGasZero() (gas: 377) -LibDecimalFloatFracTest:testFracInRange(int256,int256) (runs: 5101, μ: 9923, ~: 9935) -LibDecimalFloatFracTest:testFracLessThanMin(int256,int256) (runs: 5101, μ: 9243, ~: 9419) -LibDecimalFloatFracTest:testFracMem((int256,int256)) (runs: 5101, μ: 6069, ~: 6047) -LibDecimalFloatFracTest:testFracNonNegative(int256,int256) (runs: 5101, μ: 8728, ~: 8507) -LibDecimalFloatFracTest:testFracNotReverts(int256,int256) (runs: 5101, μ: 462, ~: 451) -LibDecimalFloatGtTest:testGtGasAZero() (gas: 404) -LibDecimalFloatGtTest:testGtGasBZero() (gas: 425) -LibDecimalFloatGtTest:testGtGasBothZero() (gas: 427) -LibDecimalFloatGtTest:testGtGasDifferentSigns() (gas: 405) -LibDecimalFloatGtTest:testGtGasExponentDiffOverflow() (gas: 507) -LibDecimalFloatGtTest:testGtMem((int256,int256),(int256,int256)) (runs: 5101, μ: 6430, ~: 6479) -LibDecimalFloatGtTest:testGtOneEAny(int256,int256) (runs: 5101, μ: 3439, ~: 3439) -LibDecimalFloatGtTest:testGtReference(int256,int256,int256,int256) (runs: 5101, μ: 9938, ~: 11563) -LibDecimalFloatGtTest:testGtX(int256) (runs: 5101, μ: 3411, ~: 3411) -LibDecimalFloatGtTest:testGtXEAnyVsXEAny(int256,int256,int256) (runs: 5101, μ: 9243, ~: 9014) -LibDecimalFloatGtTest:testGtXEAnyVsXEAnyNegative(int256,int256,int256) (runs: 5101, μ: 9706, ~: 9899) -LibDecimalFloatGtTest:testGtXNotY(int256,int256,int256,int256) (runs: 5101, μ: 4212, ~: 4289) -LibDecimalFloatGtTest:testGtXPositiveYNegative(int256,int256,int256,int256) (runs: 5101, μ: 11846, ~: 11803) -LibDecimalFloatGtTest:testGtXPositiveYZero(int256,int256,int256) (runs: 5101, μ: 8970, ~: 8743) -LibDecimalFloatGtTest:testGtZero(int256,int256) (runs: 5101, μ: 3437, ~: 3437) -LibDecimalFloatImplementationNormalizeTest:testExamples() (gas: 157225) -LibDecimalFloatImplementationNormalizeTest:testIdempotent(int256,int256) (runs: 5101, μ: 9458, ~: 9419) -LibDecimalFloatImplementationNormalizeTest:testIsNormalizedReference(int256,int256) (runs: 5101, μ: 3549, ~: 3555) -LibDecimalFloatImplementationNormalizeTest:testNormalized(int256,int256) (runs: 5101, μ: 8934, ~: 8895) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyPacked(uint256,uint8) (runs: 5128, μ: 9025, ~: 8940) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateOne(uint256,uint8) (runs: 5128, μ: 5562, ~: 5525) +LibDecimalFloatDecimalTest:testFromFixedDecimalLossyTruncateZero(uint256,uint8) (runs: 5128, μ: 6952, ~: 5537) +LibDecimalFloatDecimalTest:testToFixedDecimalLosslessScaleUp(int256,int256,uint8) (runs: 5122, μ: 14962, ~: 14781) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyExponentOverflow(int256,int256,uint8) (runs: 5128, μ: 13523, ~: 13300) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyIdentity(int256,uint8) (runs: 5128, μ: 9475, ~: 9247) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyNegative(int256,int256,uint8) (runs: 5128, μ: 9157, ~: 9356) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyPacked(bytes32,uint8) (runs: 5128, μ: 6584, ~: 6659) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyScaleUpOverflow(int256,int256,uint8) (runs: 5102, μ: 12909, ~: 13121) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncate(int256,int256,uint8) (runs: 5128, μ: 13468, ~: 13251) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyTruncateLossless() (gas: 13134) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyUnderflow(int256,int256,uint8) (runs: 5128, μ: 12508, ~: 12444) +LibDecimalFloatDecimalTest:testToFixedDecimalLossyZero(int256,uint8) (runs: 5128, μ: 4346, ~: 4346) +LibDecimalFloatDivideTest:testDividePacked(bytes32,bytes32) (runs: 5128, μ: 7947, ~: 7798) +LibDecimalFloatEqTest:testEqPacked(bytes32,bytes32) (runs: 5128, μ: 5498, ~: 5429) +LibDecimalFloatEqTest:testEqXNotYExponents(bytes32,bytes32) (runs: 5128, μ: 4253, ~: 4153) +LibDecimalFloatFloorTest:testFloorExamples() (gas: 38686) +LibDecimalFloatFloorTest:testFloorGas0() (gas: 926) +LibDecimalFloatFloorTest:testFloorGasTiny() (gas: 847) +LibDecimalFloatFloorTest:testFloorGasZero() (gas: 797) +LibDecimalFloatFloorTest:testFloorInRange(int224,int256) (runs: 5128, μ: 10676, ~: 10685) +LibDecimalFloatFloorTest:testFloorLessThanMin(int224,int256) (runs: 5128, μ: 9931, ~: 9940) +LibDecimalFloatFloorTest:testFloorNonNegative(int224,int256) (runs: 5128, μ: 9420, ~: 9687) +LibDecimalFloatFloorTest:testFloorNotReverts(bytes32) (runs: 5128, μ: 622, ~: 613) +LibDecimalFloatFracTest:testFracExamples() (gas: 38830) +LibDecimalFloatFracTest:testFracGas0() (gas: 905) +LibDecimalFloatFracTest:testFracGasTiny() (gas: 847) +LibDecimalFloatFracTest:testFracGasZero() (gas: 808) +LibDecimalFloatFracTest:testFracInRange(int224,int256) (runs: 5128, μ: 10470, ~: 10479) +LibDecimalFloatFracTest:testFracLessThanMin(int224,int256) (runs: 5128, μ: 9941, ~: 9950) +LibDecimalFloatFracTest:testFracNonNegative(int224,int256) (runs: 5128, μ: 9424, ~: 9689) +LibDecimalFloatFracTest:testFracNotReverts(bytes32) (runs: 5128, μ: 622, ~: 613) +LibDecimalFloatGtTest:testGtGasAZero() (gas: 1004) +LibDecimalFloatGtTest:testGtGasBZero() (gas: 960) +LibDecimalFloatGtTest:testGtGasBothZero() (gas: 743) +LibDecimalFloatGtTest:testGtGasDifferentSigns() (gas: 961) +LibDecimalFloatGtTest:testGtGasExponentDiffOverflow() (gas: 1107) +LibDecimalFloatGtTest:testGtOneEAny(bytes32) (runs: 5128, μ: 3488, ~: 3488) +LibDecimalFloatGtTest:testGtReference(int224,int32,int224,int32) (runs: 5128, μ: 7989, ~: 6107) +LibDecimalFloatGtTest:testGtX(int224) (runs: 5128, μ: 3765, ~: 3765) +LibDecimalFloatGtTest:testGtXEAnyVsXEAny(int256,int32,int32) (runs: 5128, μ: 10109, ~: 9826) +LibDecimalFloatGtTest:testGtXEAnyVsXEAnyNegative(int256,int32,int32) (runs: 5128, μ: 10600, ~: 10754) +LibDecimalFloatGtTest:testGtXNotY(bytes32,bytes32) (runs: 5128, μ: 4300, ~: 4197) +LibDecimalFloatGtTest:testGtXPositiveYNegative(int256,int32,int256,int32) (runs: 5128, μ: 12737, ~: 12629) +LibDecimalFloatGtTest:testGtXPositiveYZero(int256,int32,int32) (runs: 5128, μ: 9794, ~: 9525) +LibDecimalFloatGtTest:testGtZero(int32,int32) (runs: 5128, μ: 4774, ~: 4774) +LibDecimalFloatImplementationAddTest:testAdd123456789987654321() (gas: 4792) +LibDecimalFloatImplementationAddTest:testAdd123456789e9987654321() (gas: 4845) +LibDecimalFloatImplementationAddTest:testAddNeverRevert(int256,int256,int256,int256) (runs: 5128, μ: 12398, ~: 12366) +LibDecimalFloatImplementationAddTest:testAddOneOneNotNormalized() (gas: 5339) +LibDecimalFloatImplementationAddTest:testAddOneOnePreNormalized() (gas: 4241) +LibDecimalFloatImplementationAddTest:testAddOneZero() (gas: 3679) +LibDecimalFloatImplementationAddTest:testAddSameExponentSameCoefficient(int256,int256) (runs: 5128, μ: 5736, ~: 5789) +LibDecimalFloatImplementationAddTest:testAddZero() (gas: 3654) +LibDecimalFloatImplementationAddTest:testAddZeroAnyExponent(int128) (runs: 5128, μ: 8879, ~: 8914) +LibDecimalFloatImplementationAddTest:testAddZeroOne() (gas: 3677) +LibDecimalFloatImplementationAddTest:testAddZeroToAnyNonZero(int256,int256,int256) (runs: 5128, μ: 13197, ~: 13178) +LibDecimalFloatImplementationAddTest:testAddingSmallToLargeReturnsLargeExamples() (gas: 19203) +LibDecimalFloatImplementationAddTest:testAddingSmallToLargeReturnsLargeFuzz(int256,int256,int256,int256) (runs: 5107, μ: 16258, ~: 16239) +LibDecimalFloatImplementationAddTest:testGasAddOne() (gas: 938) +LibDecimalFloatImplementationAddTest:testGasAddZero() (gas: 377) +LibDecimalFloatImplementationDivideTest:testDivide1Over3() (gas: 5724) +LibDecimalFloatImplementationDivideTest:testDivide1Over3Gas0() (gas: 755) +LibDecimalFloatImplementationDivideTest:testDivide1Over3Gas10() (gas: 6851) +LibDecimalFloatImplementationDivideTest:testDivide1Over9Over1Over3() (gas: 8894) +LibDecimalFloatImplementationDivideTest:testDivide1e18Over3() (gas: 5736) +LibDecimalFloatImplementationDivideTest:testDivideNegative1Over3() (gas: 5793) +LibDecimalFloatImplementationDivideTest:testDivideOOMs5and2() (gas: 4601) +LibDecimalFloatImplementationDivideTest:testDivideOOMsOverTen() (gas: 5427) +LibDecimalFloatImplementationDivideTest:testDivideTenOverOOMs() (gas: 5353) +LibDecimalFloatImplementationDivideTest:testUnnormalizedThreesDivision0(int256,int256) (runs: 132, μ: 19644479, ~: 19644351) +LibDecimalFloatImplementationEqTest:testEqGasAZero() (gas: 425) +LibDecimalFloatImplementationEqTest:testEqGasBZero() (gas: 468) +LibDecimalFloatImplementationEqTest:testEqGasBothZero() (gas: 423) +LibDecimalFloatImplementationEqTest:testEqGasDifferentSigns() (gas: 455) +LibDecimalFloatImplementationEqTest:testEqGasExponentDiffOverflow() (gas: 572) +LibDecimalFloatImplementationEqTest:testEqNotReverts(int256,int256,int256,int256) (runs: 5128, μ: 647, ~: 671) +LibDecimalFloatImplementationEqTest:testEqOneEAny(int256,int256) (runs: 5128, μ: 3455, ~: 3455) +LibDecimalFloatImplementationEqTest:testEqReference(int256,int256,int256,int256) (runs: 5128, μ: 9836, ~: 11340) +LibDecimalFloatImplementationEqTest:testEqX(int256) (runs: 5128, μ: 3428, ~: 3428) +LibDecimalFloatImplementationEqTest:testEqXEAnyVsXEAny(int256,int256,int256) (runs: 5127, μ: 4699, ~: 4697) +LibDecimalFloatImplementationEqTest:testEqXEqY(int256,int256,int256,int256) (runs: 5128, μ: 725, ~: 744) +LibDecimalFloatImplementationEqTest:testEqXNotY(int256,int256,int256,int256) (runs: 5127, μ: 3918, ~: 3942) +LibDecimalFloatImplementationEqTest:testEqZero(int256,int256) (runs: 5128, μ: 3432, ~: 3432) +LibDecimalFloatImplementationInvTest:testInvGas0() (gas: 726) +LibDecimalFloatImplementationInvTest:testInvReference(int256,int256) (runs: 5127, μ: 11572, ~: 11551) +LibDecimalFloatImplementationInvTest:testInvSlowGas0() (gas: 748) +LibDecimalFloatImplementationLog10Test:testExactLogs() (gas: 1237336) +LibDecimalFloatImplementationLog10Test:testExactLookups() (gas: 1254146) +LibDecimalFloatImplementationLog10Test:testInterpolatedLookups() (gas: 1232412) +LibDecimalFloatImplementationLog10Test:testSub1() (gas: 1229950) +LibDecimalFloatImplementationMinusTest:testMinusIsSubZero(int256,int256,int256) (runs: 5128, μ: 12106, ~: 12079) +LibDecimalFloatImplementationMultiplyTest:testMultiply123456789987654321() (gas: 3712) +LibDecimalFloatImplementationMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 5128, μ: 12506, ~: 12589) +LibDecimalFloatImplementationMultiplyTest:testMultiply1e181e19() (gas: 3716) +LibDecimalFloatImplementationMultiplyTest:testMultiplyGasOne() (gas: 379) +LibDecimalFloatImplementationMultiplyTest:testMultiplyGasZero() (gas: 342) +LibDecimalFloatImplementationMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 5128, μ: 12561, ~: 11722) +LibDecimalFloatImplementationMultiplyTest:testMultiplyOneOne() (gas: 3689) +LibDecimalFloatImplementationMultiplyTest:testMultiplyOneZero() (gas: 3653) +LibDecimalFloatImplementationMultiplyTest:testMultiplyZero0Exponent() (gas: 3609) +LibDecimalFloatImplementationMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 5128, μ: 3896, ~: 3896) +LibDecimalFloatImplementationMultiplyTest:testMultiplyZeroOne() (gas: 3610) +LibDecimalFloatImplementationNormalizeTest:testExamples() (gas: 159798) +LibDecimalFloatImplementationNormalizeTest:testIdempotent(int256,int256) (runs: 5128, μ: 9457, ~: 9419) +LibDecimalFloatImplementationNormalizeTest:testIsNormalizedReference(int256,int256) (runs: 5128, μ: 3549, ~: 3555) +LibDecimalFloatImplementationNormalizeTest:testNormalized(int256,int256) (runs: 5128, μ: 8933, ~: 8895) +LibDecimalFloatImplementationPower10Test:testExactLookups() (gas: 1253935) +LibDecimalFloatImplementationPower10Test:testExactPowers() (gas: 1233786) +LibDecimalFloatImplementationPower10Test:testInterpolatedLookupsPower() (gas: 1251136) +LibDecimalFloatImplementationPower10Test:testNoRevert(int224,int32) (runs: 5103, μ: 1232828, ~: 1233634) +LibDecimalFloatImplementationSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 5128, μ: 15158, ~: 15171) +LibDecimalFloatImplementationSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 5128, μ: 15380, ~: 15338) LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentExamples() (gas: 12292) -LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerExponentOverflowRescaleRevert(int256,int256,int256) (runs: 5098, μ: 12560, ~: 12463) -LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerExponentVeryLargeDiffRevert(int256,int256,int256) (runs: 5101, μ: 11365, ~: 11301) -LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerTargetExponentNoRevert(int256,int256,int256) (runs: 5101, μ: 10778, ~: 10962) -LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentSameExponentNoop(int256,int256) (runs: 5101, μ: 3562, ~: 3562) -LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentSmallerExponentNoRevert(int256,int256,int256) (runs: 5099, μ: 12976, ~: 12871) -LibDecimalFloatInvTest:testInvGas0() (gas: 685) -LibDecimalFloatInvTest:testInvMem((int256,int256)) (runs: 5101, μ: 7539, ~: 7657) -LibDecimalFloatInvTest:testInvReference(int256,int256) (runs: 5100, μ: 11419, ~: 11394) -LibDecimalFloatInvTest:testInvSlowGas0() (gas: 666) -LibDecimalFloatLog10Test:testExactLogs() (gas: 1225056) -LibDecimalFloatLog10Test:testExactLookups() (gas: 1241554) -LibDecimalFloatLog10Test:testInterpolatedLookups() (gas: 1219044) -LibDecimalFloatLog10Test:testLog10Mem((int256,int256)) (runs: 5101, μ: 1628855, ~: 1223860) -LibDecimalFloatLog10Test:testSub1() (gas: 1216715) -LibDecimalFloatLtTest:testLtGasAZero() (gas: 447) -LibDecimalFloatLtTest:testLtGasBZero() (gas: 469) -LibDecimalFloatLtTest:testLtGasBothZero() (gas: 470) -LibDecimalFloatLtTest:testLtGasDifferentSigns() (gas: 403) -LibDecimalFloatLtTest:testLtGasExponentDiffOverflow() (gas: 509) -LibDecimalFloatLtTest:testLtMem((int256,int256),(int256,int256)) (runs: 5101, μ: 6411, ~: 6459) -LibDecimalFloatLtTest:testLtNegativeVsPositive(int256,int256,int256,int256) (runs: 5101, μ: 11911, ~: 11870) -LibDecimalFloatLtTest:testLtNegativeVsZero(int256,int256,int256) (runs: 5101, μ: 9435, ~: 9631) -LibDecimalFloatLtTest:testLtOneEAny(int256,int256) (runs: 5101, μ: 3439, ~: 3439) -LibDecimalFloatLtTest:testLtReference(int256,int256,int256,int256) (runs: 5101, μ: 9844, ~: 11420) -LibDecimalFloatLtTest:testLtVsEqualVsGt(int256,int256,int256,int256) (runs: 5101, μ: 4174, ~: 4246) -LibDecimalFloatLtTest:testLtX(int256) (runs: 5101, μ: 3410, ~: 3410) -LibDecimalFloatLtTest:testLtXEAnyVsXEAny(int256,int256,int256) (runs: 5101, μ: 9265, ~: 9036) -LibDecimalFloatLtTest:testLtXEAnyVsXEAnyNegative(int256,int256,int256) (runs: 5101, μ: 9728, ~: 9921) -LibDecimalFloatLtTest:testLtZero(int256,int256) (runs: 5101, μ: 3414, ~: 3414) -LibDecimalFloatMaxTest:testMaxMem((int256,int256),(int256,int256)) (runs: 5101, μ: 7358, ~: 7405) -LibDecimalFloatMaxTest:testMaxX((int256,int256)) (runs: 5101, μ: 4395, ~: 4395) -LibDecimalFloatMaxTest:testMaxXY((int256,int256),(int256,int256)) (runs: 5101, μ: 5274, ~: 5344) -LibDecimalFloatMaxTest:testMaxXYEqual((int256,int256)) (runs: 5101, μ: 5420, ~: 5420) -LibDecimalFloatMaxTest:testMaxXYGreater((int256,int256),(int256,int256)) (runs: 5097, μ: 6466, ~: 6552) -LibDecimalFloatMaxTest:testMaxXYLess((int256,int256),(int256,int256)) (runs: 5099, μ: 6461, ~: 6575) -LibDecimalFloatMinTest:testMinMem((int256,int256),(int256,int256)) (runs: 5101, μ: 7377, ~: 7427) -LibDecimalFloatMinTest:testMinX((int256,int256)) (runs: 5101, μ: 4396, ~: 4396) -LibDecimalFloatMinTest:testMinXY((int256,int256),(int256,int256)) (runs: 5101, μ: 5294, ~: 5365) -LibDecimalFloatMinTest:testMinXYEqual((int256,int256)) (runs: 5101, μ: 5398, ~: 5398) -LibDecimalFloatMinTest:testMinXYGreater((int256,int256),(int256,int256)) (runs: 5096, μ: 6452, ~: 6554) -LibDecimalFloatMinTest:testMinXYLess((int256,int256),(int256,int256)) (runs: 5100, μ: 6468, ~: 6596) -LibDecimalFloatMinusTest:testMinusIsSubZero(int256,int256,int256) (runs: 5101, μ: 12110, ~: 12079) -LibDecimalFloatMinusTest:testMinusMem((int256,int256)) (runs: 5101, μ: 5908, ~: 5908) -LibDecimalFloatMixedTest:testDivide1Over3() (gas: 6557) -LibDecimalFloatMultiplyTest:testMultiply123456789987654321() (gas: 3708) -LibDecimalFloatMultiplyTest:testMultiply123456789987654321WithExponents(int128,int128) (runs: 5101, μ: 12544, ~: 12630) -LibDecimalFloatMultiplyTest:testMultiply1e181e19() (gas: 3690) -LibDecimalFloatMultiplyTest:testMultiplyGasOne() (gas: 375) -LibDecimalFloatMultiplyTest:testMultiplyGasZero() (gas: 342) -LibDecimalFloatMultiplyTest:testMultiplyMem((int256,int256),(int256,int256)) (runs: 5101, μ: 7853, ~: 7050) -LibDecimalFloatMultiplyTest:testMultiplyNotRevertAnyExpectation(int256,int256,int256,int256) (runs: 5101, μ: 12524, ~: 11740) -LibDecimalFloatMultiplyTest:testMultiplyOneOne() (gas: 3707) -LibDecimalFloatMultiplyTest:testMultiplyOneZero() (gas: 3653) -LibDecimalFloatMultiplyTest:testMultiplyZero0Exponent() (gas: 3609) -LibDecimalFloatMultiplyTest:testMultiplyZeroAnyExponent(int64,int64) (runs: 5101, μ: 3896, ~: 3896) -LibDecimalFloatMultiplyTest:testMultiplyZeroOne() (gas: 3654) -LibDecimalFloatNormalizeTest:testNormalizeMem((int256,int256)) (runs: 5101, μ: 6893, ~: 6974) -LibDecimalFloatPackTest:testNormalizedRoundTrip(int256,int32) (runs: 5100, μ: 4885, ~: 4921) -LibDecimalFloatPackTest:testPackMem((int256,int256)) (runs: 5101, μ: 4889, ~: 4721) -LibDecimalFloatPackTest:testPartsRoundTrip(int224,int32) (runs: 5101, μ: 4505, ~: 4506) -LibDecimalFloatPackTest:testUnpackMem(bytes32) (runs: 5101, μ: 5941, ~: 5941) -LibDecimalFloatPower10Test:testExactLookups() (gas: 1241679) -LibDecimalFloatPower10Test:testExactPowers() (gas: 1221631) -LibDecimalFloatPower10Test:testInterpolatedLookupsPower() (gas: 1225739) -LibDecimalFloatPower10Test:testNoRevert(int256,int256) (runs: 5100, μ: 1217988, ~: 1218995) -LibDecimalFloatPower10Test:testPower10Mem((int256,int256)) (runs: 5101, μ: 2006090, ~: 2416856) -LibDecimalFloatPowerTest:testPowerMem((int256,int256),(int256,int256)) (runs: 5101, μ: 2294617, ~: 2422994) -LibDecimalFloatPowerTest:testPowers() (gas: 1223498) -LibDecimalFloatPowerTest:testRoundTrip() (gas: 1343164) -LibDecimalFloatSubTest:testSubIsAdd(int256,int256,int256,int256) (runs: 5101, μ: 14997, ~: 15007) -LibDecimalFloatSubTest:testSubMem((int256,int256),(int256,int256)) (runs: 5101, μ: 9557, ~: 9782) -LibDecimalFloatSubTest:testSubMinSignedValue(int256,int256,int256) (runs: 5101, μ: 15219, ~: 15171) -LibFormatDecimalFloatTest:testFormatMem((int256,int256)) (runs: 5101, μ: 6029, ~: 6688) -LibFormatDecimalFloatTest:testRoundTrip(uint256) (runs: 5101, μ: 23976, ~: 18699) -LibLogTableBytesTest:testToBytesAntiLogTableDec() (gas: 153225) -LibLogTableBytesTest:testToBytesAntiLogTableDecSmall() (gas: 158036) -LibLogTableBytesTest:testToBytesLogTableDec() (gas: 137284) -LibLogTableBytesTest:testToBytesLogTableDecSmall() (gas: 141767) +LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerExponentOverflowRescaleRevert(int256,int256,int256) (runs: 5101, μ: 12560, ~: 12463) +LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerExponentVeryLargeDiffRevert(int256,int256,int256) (runs: 5128, μ: 11364, ~: 11301) +LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentLargerTargetExponentNoRevert(int256,int256,int256) (runs: 5128, μ: 10777, ~: 10962) +LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentSameExponentNoop(int256,int256) (runs: 5128, μ: 3562, ~: 3562) +LibDecimalFloatImplementationWithTargetExponentTest:testWithTargetExponentSmallerExponentNoRevert(int256,int256,int256) (runs: 5123, μ: 12976, ~: 12871) +LibDecimalFloatInvTest:testInvMem(bytes32) (runs: 5128, μ: 7282, ~: 7212) +LibDecimalFloatLog10Test:testLog10Packed(bytes32) (runs: 5128, μ: 1628620, ~: 1238645) +LibDecimalFloatLtTest:testLtGasAZero() (gas: 935) +LibDecimalFloatLtTest:testLtGasBZero() (gas: 936) +LibDecimalFloatLtTest:testLtGasBothZero() (gas: 958) +LibDecimalFloatLtTest:testLtGasDifferentSigns() (gas: 958) +LibDecimalFloatLtTest:testLtGasExponentDiffOverflow() (gas: 1041) +LibDecimalFloatLtTest:testLtNegativeVsPositive(int256,int32,int256,int32) (runs: 5128, μ: 12759, ~: 12649) +LibDecimalFloatLtTest:testLtNegativeVsZero(int256,int32,int32) (runs: 5128, μ: 10251, ~: 10418) +LibDecimalFloatLtTest:testLtOneEAny(int224,int32) (runs: 5128, μ: 3895, ~: 3895) +LibDecimalFloatLtTest:testLtReference(bytes32,bytes32) (runs: 5128, μ: 4699, ~: 5010) +LibDecimalFloatLtTest:testLtVsEqualVsGt(bytes32,bytes32) (runs: 5128, μ: 4303, ~: 4197) +LibDecimalFloatLtTest:testLtX(int224) (runs: 5128, μ: 3809, ~: 3809) +LibDecimalFloatLtTest:testLtXEAnyVsXEAny(int256,int32,int32) (runs: 5128, μ: 10118, ~: 9835) +LibDecimalFloatLtTest:testLtXEAnyVsXEAnyNegative(int256,int32,int32) (runs: 5128, μ: 10612, ~: 10764) +LibDecimalFloatLtTest:testLtZero(int32,int32) (runs: 5128, μ: 4155, ~: 4155) +LibDecimalFloatMaxTest:testMaxX(bytes32) (runs: 5128, μ: 4149, ~: 4149) +LibDecimalFloatMaxTest:testMaxXY(bytes32,bytes32) (runs: 5128, μ: 4584, ~: 4512) +LibDecimalFloatMaxTest:testMaxXYEqual(bytes32) (runs: 5128, μ: 5084, ~: 5084) +LibDecimalFloatMaxTest:testMaxXYGreater(bytes32,bytes32) (runs: 5106, μ: 5932, ~: 5825) +LibDecimalFloatMaxTest:testMaxXYLess(bytes32,bytes32) (runs: 5118, μ: 5944, ~: 5836) +LibDecimalFloatMinTest:testMinX(bytes32) (runs: 5128, μ: 4171, ~: 4171) +LibDecimalFloatMinTest:testMinXY(bytes32,bytes32) (runs: 5128, μ: 4584, ~: 4512) +LibDecimalFloatMinTest:testMinXYEqual(bytes32) (runs: 5128, μ: 5104, ~: 5104) +LibDecimalFloatMinTest:testMinXYGreater(bytes32,bytes32) (runs: 5109, μ: 5877, ~: 5770) +LibDecimalFloatMinTest:testMinXYLess(bytes32,bytes32) (runs: 5115, μ: 5892, ~: 5781) +LibDecimalFloatMinusTest:testMinusPacked(bytes32) (runs: 5128, μ: 5529, ~: 5529) +LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7938) +LibDecimalFloatMultiplyTest:testMultiplyPacked(bytes32,bytes32) (runs: 5128, μ: 7848, ~: 8587) +LibDecimalFloatPackTest:testPartsRoundTrip(int224,int32) (runs: 5128, μ: 5084, ~: 5084) +LibDecimalFloatPower10Test:testPower10Packed(bytes32) (runs: 5128, μ: 1600424, ~: 1232587) +LibDecimalFloatPowerTest:testPowers() (gas: 1267312) +LibDecimalFloatPowerTest:testRoundTripFuzz(bytes32,bytes32) (runs: 5128, μ: 1231593, ~: 1228320) +LibDecimalFloatPowerTest:testRoundTripSimple() (gas: 1422336) +LibDecimalFloatSubTest:testSubPacked(bytes32,bytes32) (runs: 5128, μ: 8569, ~: 8256) +LibFormatDecimalFloatTest:testFormatDecimalRoundTrip(uint256) (runs: 5128, μ: 23814, ~: 18978) +LibFormatDecimalFloatTest:testFormatMem(bytes32) (runs: 5128, μ: 6767, ~: 6661) +LibLogTableBytesTest:testToBytesAntiLogTableDec() (gas: 154896) +LibLogTableBytesTest:testToBytesAntiLogTableDecSmall() (gas: 159695) +LibLogTableBytesTest:testToBytesLogTableDec() (gas: 138820) +LibLogTableBytesTest:testToBytesLogTableDecSmall() (gas: 143377) LibLogTableBytesTest:testToBytesLogTableDecSmallAlt() (gas: 18049) LibParseDecimalFloatTest:testParseDecimalFloatEmpty() (gas: 4098) LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert() (gas: 4115) LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert2() (gas: 5291) -LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert3() (gas: 5375) +LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert3() (gas: 5353) LibParseDecimalFloatTest:testParseDecimalFloatExponentRevert4() (gas: 4093) -LibParseDecimalFloatTest:testParseDecimalFloatNonDecimal() (gas: 4158) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDecimals() (gas: 378356) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotE() (gas: 4157) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotE0() (gas: 4135) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert() (gas: 4113) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert2() (gas: 4113) +LibParseDecimalFloatTest:testParseDecimalFloatNonDecimal() (gas: 4136) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDecimals() (gas: 378371) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotE() (gas: 4135) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotE0() (gas: 4113) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert() (gas: 4136) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert2() (gas: 4136) LibParseDecimalFloatTest:testParseLiteralDecimalFloatDotRevert3() (gas: 5120) LibParseDecimalFloatTest:testParseLiteralDecimalFloatEDot() (gas: 4159) LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponentRevert5() (gas: 4145) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponentRevert6() (gas: 4092) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponents() (gas: 393083) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatFuzz(uint256,uint8,bool) (runs: 5101, μ: 45503, ~: 36636) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatLeadingZeros() (gas: 59042) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeE() (gas: 6048) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeFrac() (gas: 5095) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert0() (gas: 27442) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponentRevert6() (gas: 4157) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponents() (gas: 393184) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatFuzz(uint256,uint8,bool) (runs: 5128, μ: 45159, ~: 36807) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatLeadingZeros() (gas: 59049) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeE() (gas: 6026) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeFrac() (gas: 5073) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert0() (gas: 27465) LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert1() (gas: 27353) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatSpecific() (gas: 22682) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatUnrelated() (gas: 31978) -LibParseDecimalFloatTest:testParseMem(string) (runs: 5101, μ: 9205, ~: 9168) \ No newline at end of file +LibParseDecimalFloatTest:testParseLiteralDecimalFloatSpecific() (gas: 22666) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatUnrelated() (gas: 31984) +LibParseDecimalFloatTest:testParseMem(string) (runs: 5128, μ: 9145, ~: 9108) \ No newline at end of file From e33ee75bc172001cbc85a5a3c5376957aafd736f Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 28 Apr 2025 17:35:21 +0400 Subject: [PATCH 10/13] lint console --- test/src/lib/LibDecimalFloat.power.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/src/lib/LibDecimalFloat.power.t.sol b/test/src/lib/LibDecimalFloat.power.t.sol index 566ce9fb..85b3d3f9 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -29,7 +29,9 @@ contract LibDecimalFloatPowerTest is LogTest { uint256 beforeGas = gasleft(); Float c = a.power(b, tables); uint256 afterGas = gasleft(); - console2.log("%d %d Gas used: %d", uint256(signedCoefficientA), uint256(exponentA), beforeGas - afterGas); + console2.log("Gas used:", beforeGas - afterGas); + console2.logInt(signedCoefficientA); + console2.logInt(exponentA); (int256 actualSignedCoefficient, int256 actualExponent) = c.unpack(); assertEq(actualSignedCoefficient, expectedSignedCoefficient, "signedCoefficient"); assertEq(actualExponent, expectedExponent, "exponent"); From ab5aae73076b8957276040c31744cc934fefbdb7 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 28 Apr 2025 19:37:17 +0400 Subject: [PATCH 11/13] tighter test --- src/lib/parse/LibParseDecimalFloat.sol | 1 + test/src/lib/parse/LibParseDecimalFloat.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/parse/LibParseDecimalFloat.sol b/src/lib/parse/LibParseDecimalFloat.sol index 74f02031..06f01946 100644 --- a/src/lib/parse/LibParseDecimalFloat.sol +++ b/src/lib/parse/LibParseDecimalFloat.sol @@ -15,6 +15,7 @@ import {MalformedExponentDigits, ParseDecimalPrecisionLoss, MalformedDecimalPoin import {ParseDecimalOverflow, ParseEmptyDecimalString} from "rain.string/error/ErrParse.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, Float) { diff --git a/test/src/lib/parse/LibParseDecimalFloat.t.sol b/test/src/lib/parse/LibParseDecimalFloat.t.sol index bcd4d2e1..d84988ff 100644 --- a/test/src/lib/parse/LibParseDecimalFloat.t.sol +++ b/test/src/lib/parse/LibParseDecimalFloat.t.sol @@ -372,9 +372,9 @@ contract LibParseDecimalFloatTest is Test { /// impossible to fit the max decimals. function testParseLiteralDecimalFloatPrecisionRevert1() external pure { checkParseDecimalFloatFail( - "1.57896044618658097711785492504343953926634992332820282019728792003956564819967", + "1.5789604461865809771178549250434395392663499233282028201972879200395", ParseDecimalPrecisionLoss.selector, - 79 + 69 ); } } From 1b1693586da549f07c7d1f4edf8c8b510083f8ce Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 28 Apr 2025 19:38:50 +0400 Subject: [PATCH 12/13] fix tests --- src/lib/table/LibLogTable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/table/LibLogTable.sol b/src/lib/table/LibLogTable.sol index f647713e..6d88e4a4 100644 --- a/src/lib/table/LibLogTable.sol +++ b/src/lib/table/LibLogTable.sol @@ -446,7 +446,7 @@ library LibLogTable { [0, 0, 1, 1, 2, 2, 3, 3, 4, 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. The last entry is 10000, so we just use that. + // entry in the table. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ]; } From 9ed774e3a1d39fef22fa465c77d83afb5d2ad34d Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 28 Apr 2025 19:44:47 +0400 Subject: [PATCH 13/13] lint --- .gas-snapshot | 10 +++++----- test/src/lib/LibDecimalFloat.power.t.sol | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 2ca445f3..1e087fc9 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -165,9 +165,9 @@ LibDecimalFloatMixedTest:testDivide1Over3() (gas: 7938) LibDecimalFloatMultiplyTest:testMultiplyPacked(bytes32,bytes32) (runs: 5128, μ: 7848, ~: 8587) LibDecimalFloatPackTest:testPartsRoundTrip(int224,int32) (runs: 5128, μ: 5084, ~: 5084) LibDecimalFloatPower10Test:testPower10Packed(bytes32) (runs: 5128, μ: 1600424, ~: 1232587) -LibDecimalFloatPowerTest:testPowers() (gas: 1267312) -LibDecimalFloatPowerTest:testRoundTripFuzz(bytes32,bytes32) (runs: 5128, μ: 1231593, ~: 1228320) -LibDecimalFloatPowerTest:testRoundTripSimple() (gas: 1422336) +LibDecimalFloatPowerTest:testPowers() (gas: 1269733) +LibDecimalFloatPowerTest:testRoundTripFuzz(bytes32,bytes32) (runs: 5128, μ: 1231690, ~: 1228311) +LibDecimalFloatPowerTest:testRoundTripSimple() (gas: 1422345) LibDecimalFloatSubTest:testSubPacked(bytes32,bytes32) (runs: 5128, μ: 8569, ~: 8256) LibFormatDecimalFloatTest:testFormatDecimalRoundTrip(uint256) (runs: 5128, μ: 23814, ~: 18978) LibFormatDecimalFloatTest:testFormatMem(bytes32) (runs: 5128, μ: 6767, ~: 6661) @@ -192,12 +192,12 @@ LibParseDecimalFloatTest:testParseLiteralDecimalFloatEDot() (gas: 4159) LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponentRevert5() (gas: 4145) LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponentRevert6() (gas: 4157) LibParseDecimalFloatTest:testParseLiteralDecimalFloatExponents() (gas: 393184) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatFuzz(uint256,uint8,bool) (runs: 5128, μ: 45159, ~: 36807) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatFuzz(uint256,uint8,bool) (runs: 5128, μ: 44855, ~: 36576) LibParseDecimalFloatTest:testParseLiteralDecimalFloatLeadingZeros() (gas: 59049) LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeE() (gas: 6026) LibParseDecimalFloatTest:testParseLiteralDecimalFloatNegativeFrac() (gas: 5073) LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert0() (gas: 27465) -LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert1() (gas: 27353) +LibParseDecimalFloatTest:testParseLiteralDecimalFloatPrecisionRevert1() (gas: 24553) LibParseDecimalFloatTest:testParseLiteralDecimalFloatSpecific() (gas: 22666) LibParseDecimalFloatTest:testParseLiteralDecimalFloatUnrelated() (gas: 31984) LibParseDecimalFloatTest:testParseMem(string) (runs: 5128, μ: 9145, ~: 9108) \ No newline at end of file diff --git a/test/src/lib/LibDecimalFloat.power.t.sol b/test/src/lib/LibDecimalFloat.power.t.sol index 85b3d3f9..afec41a1 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -4,7 +4,6 @@ pragma solidity =0.8.25; import {LogTest} from "../../abstract/LogTest.sol"; import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; -import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {console2} from "forge-std/Test.sol";