From cfab79e937f8c6e9f791baa2b17143574eb9f4ae Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Fri, 28 Mar 2025 21:36:38 +0400 Subject: [PATCH 1/4] float struct --- src/lib/LibDecimalFloat.sol | 234 ++++++++++++++++++ test/src/lib/LibDecimalFloat.abs.t.sol | 26 +- test/src/lib/LibDecimalFloat.add.t.sol | 30 ++- test/src/lib/LibDecimalFloat.decimal.t.sol | 35 ++- .../lib/LibDecimalFloat.decimalLossless.t.sol | 49 +++- test/src/lib/LibDecimalFloat.divide.t.sol | 30 ++- test/src/lib/LibDecimalFloat.eq.t.sol | 27 +- test/src/lib/LibDecimalFloat.floor.t.sol | 26 +- test/src/lib/LibDecimalFloat.frac.t.sol | 26 +- test/src/lib/LibDecimalFloat.gt.t.sol | 27 +- test/src/lib/LibDecimalFloat.inv.t.sol | 26 +- test/src/lib/LibDecimalFloat.log10.t.sol | 28 ++- test/src/lib/LibDecimalFloat.lt.t.sol | 27 +- test/src/lib/LibDecimalFloat.minus.t.sol | 28 ++- test/src/lib/LibDecimalFloat.multiply.t.sol | 33 ++- test/src/lib/LibDecimalFloat.pack.t.sol | 48 +++- test/src/lib/LibDecimalFloat.power.t.sol | 29 ++- test/src/lib/LibDecimalFloat.power10.t.sol | 28 ++- test/src/lib/LibDecimalFloat.sub.t.sol | 30 ++- 19 files changed, 768 insertions(+), 19 deletions(-) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 21b0a56c..7a1e1ac5 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -38,6 +38,11 @@ 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))); +struct Float { + int256 signedCoefficient; + int256 exponent; +} + /// @title LibDecimalFloat /// Floating point math library for Rainlang. /// Broadly implements decimal floating point math with 128 signed bits for the @@ -111,6 +116,23 @@ library LibDecimalFloat { } } + /// Same as fromFixedDecimalLossy, but returns a Float struct instead of + /// separate values. + /// Costs more gas but helps mitigate stack depth issues, and is more + /// ergonomic for the caller. + /// @param value The fixed point decimal value to convert. + /// @param decimals The number of decimals in the fixed point representation. + /// 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); + } + /// Lossless version of `fromFixedDecimalLossy`. This will revert if the /// conversion is lossy. /// @param value As per `fromFixedDecimalLossy`. @@ -125,6 +147,16 @@ library LibDecimalFloat { return (signedCoefficient, exponent); } + /// Lossless version of `fromFixedDecimalLossyMem`. This will revert if the + /// conversion is lossy. + /// @param value As per `fromFixedDecimalLossyMem`. + /// @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); + } + /// Convert a signed coefficient and exponent to a fixed point decimal value. /// The conversion is impossible and will revert if the signed coefficient is /// negative. If the conversion overflows it will also revert. @@ -200,6 +232,20 @@ library LibDecimalFloat { } } + /// Same as toFixedDecimalLossy, 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. + /// @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. + /// @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); + } + /// Lossless version of `toFixedDecimalLossy`. This will revert if the /// conversion is lossy. /// @param signedCoefficient As per `toFixedDecimalLossy`. @@ -218,6 +264,19 @@ library LibDecimalFloat { return value; } + /// Same as toFixedDecimalLossless, 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. + /// @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); + } + /// Pack a signed coefficient and exponent into a single uint256. Clearly /// this involves fitting 64 bytes into 32 bytes, so there will be data loss. /// Normalized numbers are guaranteed to round trip through pack/unpack in @@ -242,6 +301,10 @@ library LibDecimalFloat { } } + 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 /// the inverse of `pack`. Note that the unpacked values are not necessarily /// normalized, especially if their provenance is unknown or user input. @@ -258,6 +321,10 @@ library LibDecimalFloat { } } + 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 @@ -379,6 +446,18 @@ library LibDecimalFloat { 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. + /// @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 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); + } + /// Subtract two floats together as a normalized result. /// /// This is effectively shorthand for adding the two floats with the second @@ -400,6 +479,18 @@ library LibDecimalFloat { 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 memory a, Float memory b) internal pure returns (Float memory result) { + (result.signedCoefficient, result.exponent) = + sub(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + } + /// Negates and normalizes a float. /// Equivalent to `0 - x`. /// @@ -432,6 +523,15 @@ library LibDecimalFloat { } } + /// Same as minus, 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 minus(Float memory float) internal pure returns (Float memory result) { + (result.signedCoefficient, result.exponent) = minus(float.signedCoefficient, float.exponent); + } + /// Returns the absolute value of a float. /// Identity if non-negative, negated if negative. Max negative signed value /// for the coefficient will be shifted one OOM so that it can be negated to @@ -451,6 +551,15 @@ library LibDecimalFloat { } } + /// 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. + function abs(Float memory float) internal pure returns (Float memory result) { + (result.signedCoefficient, result.exponent) = abs(float.signedCoefficient, float.exponent); + } + /// https://speleotrove.com/decimal/daops.html#refmult /// > multiply takes two operands. If either operand is a special value then /// > the general rules apply. @@ -506,6 +615,18 @@ library LibDecimalFloat { } } + /// Same as multiply, 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 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); + } + /// https://speleotrove.com/decimal/daops.html#refdivide /// > divide takes two operands. If either operand is a special value then /// > the general rules apply. @@ -571,6 +692,18 @@ library LibDecimalFloat { } } + /// Same as divide, 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 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); + } + /// 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); @@ -581,6 +714,15 @@ library LibDecimalFloat { return (signedCoefficient, exponent); } + /// Same as inv, 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 inv(Float memory float) internal pure returns (Float memory result) { + (result.signedCoefficient, result.exponent) = inv(float.signedCoefficient, float.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 @@ -605,6 +747,17 @@ library LibDecimalFloat { return signedCoefficientA == signedCoefficientB; } + /// 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. + function eq(Float memory a, Float memory b) internal pure returns (bool) { + return eq(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + } + /// Numeric less than for floats. /// A float is less than another if its numeric value is less than the other. /// For example, 1e2 is less than 1e3, and 1e2 is less than 2e2. @@ -629,6 +782,17 @@ library LibDecimalFloat { 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. + function lt(Float memory a, Float memory b) internal pure returns (bool) { + return lt(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + } + /// 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. @@ -653,6 +817,17 @@ 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. + function gt(Float memory a, Float memory b) internal pure returns (bool) { + return gt(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent); + } + /// Fractional component of a float. /// @param signedCoefficient The signed coefficient of the floating point /// number. @@ -667,6 +842,15 @@ library LibDecimalFloat { 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. + function frac(Float memory float) internal pure returns (Float memory result) { + (result.signedCoefficient, result.exponent) = frac(float.signedCoefficient, float.exponent); + } + /// Integer component of a float. /// @param signedCoefficient The signed coefficient of the floating point /// number. @@ -681,6 +865,15 @@ library LibDecimalFloat { return (characteristic, exponent); } + /// 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. + function floor(Float memory float) internal pure returns (Float memory result) { + (result.signedCoefficient, result.exponent) = floor(float.signedCoefficient, float.exponent); + } + /// 10^x for a float x. /// /// Internally uses log tables so is not perfectly accurate, but also doesn't @@ -731,6 +924,18 @@ library LibDecimalFloat { } } + /// 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. + /// @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 power10(address tablesDataContract, Float memory float) internal view returns (Float memory result) { + (result.signedCoefficient, result.exponent) = + power10(tablesDataContract, float.signedCoefficient, float.exponent); + } + /// log10(x) for a float x. /// /// Internally uses log tables so is not perfectly accurate, but also doesn't @@ -841,6 +1046,17 @@ library LibDecimalFloat { } } + /// 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 memory float) internal view returns (Float memory result) { + (result.signedCoefficient, result.exponent) = log10(tablesDataContract, float.signedCoefficient, float.exponent); + } + /// x^y = 10^(y * log10(x)) /// /// Due to the inaccuraces of log10 and power10, this is not perfectly @@ -868,4 +1084,22 @@ library LibDecimalFloat { (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. + 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); + } } diff --git a/test/src/lib/LibDecimalFloat.abs.t.sol b/test/src/lib/LibDecimalFloat.abs.t.sol index 2a9758ff..f4574691 100644 --- a/test/src/lib/LibDecimalFloat.abs.t.sol +++ b/test/src/lib/LibDecimalFloat.abs.t.sol @@ -3,9 +3,33 @@ pragma solidity =0.8.25; import {Test} from "forge-std/Test.sol"; -import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; contract LibDecimalFloatAbsTest is Test { + using LibDecimalFloat for Float; + + function absExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) { + return LibDecimalFloat.abs(signedCoefficient, exponent); + } + + function absExternal(Float memory float) external pure returns (Float memory) { + return LibDecimalFloat.abs(float); + } + /// Stack and mem are the same. + + function testAbsMem(Float memory float) external { + try this.absExternal(float.signedCoefficient, float.exponent) returns ( + int256 signedCoefficient, int256 exponent + ) { + Float memory floatAbs = this.absExternal(float); + assertEq(signedCoefficient, floatAbs.signedCoefficient); + assertEq(exponent, floatAbs.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.absExternal(float); + } + } + /// Anything non negative is identity. function testAbsNonNegative(int256 signedCoefficient, int256 exponent) external pure { signedCoefficient = bound(signedCoefficient, 0, type(int256).max); diff --git a/test/src/lib/LibDecimalFloat.add.t.sol b/test/src/lib/LibDecimalFloat.add.t.sol index c1f694ef..ff06cabf 100644 --- a/test/src/lib/LibDecimalFloat.add.t.sol +++ b/test/src/lib/LibDecimalFloat.add.t.sol @@ -1,12 +1,40 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, ADD_MAX_EXPONENT_DIFF} from "src/lib/LibDecimalFloat.sol"; +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 {Test} from "forge-std/Test.sol"; contract LibDecimalFloatDecimalAddTest is Test { + using LibDecimalFloat for Float; + + function addExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + returns (int256, int256) + { + 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); + } + + /// 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 ( + int256 signedCoefficient, int256 exponent + ) { + Float memory resultMem = a.add(b); + assertEq(signedCoefficient, resultMem.signedCoefficient); + assertEq(exponent, resultMem.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + a.add(b); + } + } + /// 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 e6495564..24290692 100644 --- a/test/src/lib/LibDecimalFloat.decimal.t.sol +++ b/test/src/lib/LibDecimalFloat.decimal.t.sol @@ -6,13 +6,16 @@ import { ExponentOverflow, NORMALIZED_MAX, NORMALIZED_MIN, - NegativeFixedDecimalConversion + NegativeFixedDecimalConversion, + Float } from "src/lib/LibDecimalFloat.sol"; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {Test, console2, stdError} from "forge-std/Test.sol"; contract LibDecimalFloatDecimalTest is Test { + using LibDecimalFloat for Float; + function toFixedDecimalLossyExternal(int256 signedCoefficient, int256 exponent, uint8 decimals) external pure @@ -21,6 +24,36 @@ contract LibDecimalFloatDecimalTest is Test { return LibDecimalFloat.toFixedDecimalLossy(signedCoefficient, exponent, decimals); } + function toFixedDecimalLossyExternal(Float memory 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 { + (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"); + 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 ( + uint256 value, bool lossless + ) { + (uint256 valueOut, bool losslessOut) = float.toFixedDecimalLossy(decimals); + assertEq(value, valueOut, "value"); + assertEq(lossless, losslessOut, "lossless"); + } catch (bytes memory err) { + vm.expectRevert(err); + (uint256 valueOut, bool losslessOut) = this.toFixedDecimalLossyExternal(float, decimals); + (valueOut, losslessOut); + } + } + /// Round trip from/to decimal values without precision loss function testFixedDecimalRoundTripLossless(uint256 value, uint8 decimals) external pure { value = bound(value, 0, uint256(NORMALIZED_MAX)); diff --git a/test/src/lib/LibDecimalFloat.decimalLossless.t.sol b/test/src/lib/LibDecimalFloat.decimalLossless.t.sol index e9253603..f54bc543 100644 --- a/test/src/lib/LibDecimalFloat.decimalLossless.t.sol +++ b/test/src/lib/LibDecimalFloat.decimalLossless.t.sol @@ -6,15 +6,62 @@ import { SIGNED_NORMALIZED_MAX, NORMALIZED_MAX, LossyConversionFromFloat, - LossyConversionToFloat + LossyConversionToFloat, + Float } from "../../../src/lib/LibDecimalFloat.sol"; import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatDecimalLosslessTest is Test { + using LibDecimalFloat for Float; + function fromFixedDecimalLosslessExternal(uint256 value, uint8 decimals) external pure returns (int256, int256) { return LibDecimalFloat.fromFixedDecimalLossless(value, decimals); } + function fromFixedDecimalLosslessMemExternal(uint256 value, uint8 decimals) external pure returns (Float memory) { + return LibDecimalFloat.fromFixedDecimalLosslessMem(value, decimals); + } + + function toFixedDecimalLosslessExternal(int256 signedCoefficient, int256 exponent, uint8 decimals) + external + pure + returns (uint256) + { + return LibDecimalFloat.toFixedDecimalLossless(signedCoefficient, exponent, decimals); + } + + function toFixedDecimalLosslessMemExternal(Float memory 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 { + (,, bool losslessPreflight) = LibDecimalFloat.fromFixedDecimalLossy(value, decimals); + if (!losslessPreflight) { + vm.expectRevert( + abi.encodeWithSelector(LossyConversionToFloat.selector, value / 10, 1 - int256(uint256(decimals))) + ); + } + (Float memory float) = LibDecimalFloat.fromFixedDecimalLosslessMem(value, decimals); + (int256 signedCoefficient, int256 exponent) = LibDecimalFloat.fromFixedDecimalLossless(value, decimals); + assertEq(float.signedCoefficient, signedCoefficient); + assertEq(float.exponent, exponent); + } + + /// 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 + ) { + uint256 valueFloat = float.toFixedDecimalLossless(decimals); + assertEq(valueFloat, value); + } catch (bytes memory err) { + vm.expectRevert(err); + uint256 valueFloat = this.toFixedDecimalLosslessMemExternal(float, decimals); + (valueFloat); + } + } + function testToFixedDecimalLosslessPass(int256 signedCoefficient, int256 exponent, uint8 decimals) external pure { signedCoefficient = bound(signedCoefficient, 0, 1e18); exponent = bound(exponent, 0, 30); diff --git a/test/src/lib/LibDecimalFloat.divide.t.sol b/test/src/lib/LibDecimalFloat.divide.t.sol index 243e359b..335e66cd 100644 --- a/test/src/lib/LibDecimalFloat.divide.t.sol +++ b/test/src/lib/LibDecimalFloat.divide.t.sol @@ -2,11 +2,39 @@ pragma solidity =0.8.25; import {THREES, ONES} from "../../lib/LibCommonResults.sol"; -import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, Float} from "src/lib/LibDecimalFloat.sol"; import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatDivideTest is Test { + using LibDecimalFloat for Float; + + function divideExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + returns (int256, int256) + { + return LibDecimalFloat.divide(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + + function divideExternal(Float memory floatA, Float memory floatB) external pure returns (Float memory) { + 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 ( + int256 signedCoefficient, int256 exponent + ) { + Float memory float = this.divideExternal(a, b); + assertEq(signedCoefficient, float.signedCoefficient); + assertEq(exponent, float.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.divideExternal(a, b); + } + } + function checkDivision( int256 signedCoefficientA, int256 exponentA, diff --git a/test/src/lib/LibDecimalFloat.eq.t.sol b/test/src/lib/LibDecimalFloat.eq.t.sol index bbaa64d0..2f2c5782 100644 --- a/test/src/lib/LibDecimalFloat.eq.t.sol +++ b/test/src/lib/LibDecimalFloat.eq.t.sol @@ -1,13 +1,38 @@ // 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 {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatEqTest is Test { + using LibDecimalFloat for Float; + + function eqExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + returns (bool) + { + return LibDecimalFloat.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + + function eqExternal(Float memory floatA, Float memory floatB) external pure returns (bool) { + return LibDecimalFloat.eq(floatA, floatB); + } + /// Stack and mem are the same. + + function testEqMem(Float memory a, Float memory b) external { + try this.eqExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns (bool eq) { + bool actual = this.eqExternal(a, b); + assertEq(eq, actual); + } catch (bytes memory err) { + vm.expectRevert(err); + this.eqExternal(a, b); + } + } + function testEqReference(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) external pure diff --git a/test/src/lib/LibDecimalFloat.floor.t.sol b/test/src/lib/LibDecimalFloat.floor.t.sol index 5537dfc4..d22abe8d 100644 --- a/test/src/lib/LibDecimalFloat.floor.t.sol +++ b/test/src/lib/LibDecimalFloat.floor.t.sol @@ -1,11 +1,35 @@ // 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 {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 memory float) external pure returns (Float memory) { + 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 + ) { + Float memory floatFloor = this.floorExternal(float); + assertEq(signedCoefficient, floatFloor.signedCoefficient); + assertEq(exponent, floatFloor.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.floorExternal(float); + } + } + function testFloorNotReverts(int256 x, int256 exponentX) external pure { LibDecimalFloat.floor(x, exponentX); } diff --git a/test/src/lib/LibDecimalFloat.frac.t.sol b/test/src/lib/LibDecimalFloat.frac.t.sol index 97be89c9..6c5863bc 100644 --- a/test/src/lib/LibDecimalFloat.frac.t.sol +++ b/test/src/lib/LibDecimalFloat.frac.t.sol @@ -1,11 +1,35 @@ // 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 {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 memory float) external pure returns (Float memory) { + return LibDecimalFloat.frac(float); + } + /// Stack and mem are the same. + + function testFracMem(Float memory float) external { + try this.fracExternal(float.signedCoefficient, float.exponent) returns ( + int256 signedCoefficient, int256 exponent + ) { + Float memory floatFrac = this.fracExternal(float); + assertEq(signedCoefficient, floatFrac.signedCoefficient); + assertEq(exponent, floatFrac.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.fracExternal(float); + } + } + function testFracNotReverts(int256 x, int256 exponentX) external pure { LibDecimalFloat.frac(x, exponentX); } diff --git a/test/src/lib/LibDecimalFloat.gt.t.sol b/test/src/lib/LibDecimalFloat.gt.t.sol index 23b1f49f..73d7c3d5 100644 --- a/test/src/lib/LibDecimalFloat.gt.t.sol +++ b/test/src/lib/LibDecimalFloat.gt.t.sol @@ -1,13 +1,38 @@ // 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 {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; 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 memory floatA, Float memory 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) { + 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 diff --git a/test/src/lib/LibDecimalFloat.inv.t.sol b/test/src/lib/LibDecimalFloat.inv.t.sol index a9490d61..ca9e95f6 100644 --- a/test/src/lib/LibDecimalFloat.inv.t.sol +++ b/test/src/lib/LibDecimalFloat.inv.t.sol @@ -2,10 +2,34 @@ pragma solidity =0.8.25; import {Test} from "forge-std/Test.sol"; -import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, Float} from "src/lib/LibDecimalFloat.sol"; import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.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); + } + + function invExternal(Float memory float) external pure returns (Float memory) { + 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 + ) { + Float memory floatInv = this.invExternal(float); + assertEq(signedCoefficient, floatInv.signedCoefficient); + assertEq(exponent, floatInv.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.invExternal(float); + } + } + /// Compare reference. function testInvReference(int256 signedCoefficient, int256 exponent) external pure { vm.assume(signedCoefficient != 0); diff --git a/test/src/lib/LibDecimalFloat.log10.t.sol b/test/src/lib/LibDecimalFloat.log10.t.sol index 7609185a..8638aded 100644 --- a/test/src/lib/LibDecimalFloat.log10.t.sol +++ b/test/src/lib/LibDecimalFloat.log10.t.sol @@ -1,12 +1,38 @@ // 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 {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); + } + + function log10External(Float memory float) external returns (Float memory) { + 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 + ) { + Float memory floatLog10 = this.log10External(float); + assertEq(signedCoefficient, floatLog10.signedCoefficient); + assertEq(exponent, floatLog10.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.log10External(float); + } + } + function checkLog10( int256 signedCoefficient, int256 exponent, diff --git a/test/src/lib/LibDecimalFloat.lt.t.sol b/test/src/lib/LibDecimalFloat.lt.t.sol index a26e461f..291c6807 100644 --- a/test/src/lib/LibDecimalFloat.lt.t.sol +++ b/test/src/lib/LibDecimalFloat.lt.t.sol @@ -1,13 +1,38 @@ // 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 {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; 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 memory floatA, Float memory floatB) external pure returns (bool) { + return LibDecimalFloat.lt(floatA, floatB); + } + /// Stack and mem are the same. + + function testLtMem(Float memory a, Float memory b) external { + try this.ltExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) 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 diff --git a/test/src/lib/LibDecimalFloat.minus.t.sol b/test/src/lib/LibDecimalFloat.minus.t.sol index 88ed4db3..d75cc471 100644 --- a/test/src/lib/LibDecimalFloat.minus.t.sol +++ b/test/src/lib/LibDecimalFloat.minus.t.sol @@ -1,12 +1,36 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, Float} from "src/lib/LibDecimalFloat.sol"; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {Test} from "forge-std/Test.sol"; -contract LibDecimalFloatSubTest is Test { +contract LibDecimalFloatMinusTest is Test { + using LibDecimalFloat for Float; + + function minusExternal(int256 signedCoefficient, int256 exponent) external pure returns (int256, int256) { + return LibDecimalFloat.minus(signedCoefficient, exponent); + } + + function minusExternal(Float memory float) external pure returns (Float memory) { + return LibDecimalFloat.minus(float); + } + /// Stack and mem are the same. + + function testMinusMem(Float memory float) external { + try this.minusExternal(float.signedCoefficient, float.exponent) returns ( + int256 signedCoefficient, int256 exponent + ) { + Float memory floatMinus = this.minusExternal(float); + assertEq(signedCoefficient, floatMinus.signedCoefficient); + assertEq(exponent, floatMinus.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + 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); diff --git a/test/src/lib/LibDecimalFloat.multiply.t.sol b/test/src/lib/LibDecimalFloat.multiply.t.sol index be4a0c8c..2cc26626 100644 --- a/test/src/lib/LibDecimalFloat.multiply.t.sol +++ b/test/src/lib/LibDecimalFloat.multiply.t.sol @@ -2,7 +2,10 @@ pragma solidity =0.8.25; import { - LibDecimalFloat, NORMALIZED_ZERO_SIGNED_COEFFICIENT, NORMALIZED_ZERO_EXPONENT + LibDecimalFloat, + NORMALIZED_ZERO_SIGNED_COEFFICIENT, + NORMALIZED_ZERO_EXPONENT, + Float } from "src/lib/LibDecimalFloat.sol"; import { EXPONENT_MIN, @@ -14,6 +17,34 @@ import {LibDecimalFloatSlow} from "test/lib/LibDecimalFloatSlow.sol"; import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatMultiplyTest is Test { + using LibDecimalFloat for Float; + + function multiplyExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + returns (int256, int256) + { + return LibDecimalFloat.multiply(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + + function multiplyExternal(Float memory floatA, Float memory floatB) external pure returns (Float memory) { + 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 ( + int256 signedCoefficient, int256 exponent + ) { + Float memory float = this.multiplyExternal(a, b); + assertEq(signedCoefficient, float.signedCoefficient); + assertEq(exponent, float.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.multiplyExternal(a, b); + } + } + /// Simple 0 multiply 0 /// 0 * 0 = 0 function testMultiplyZero0Exponent() external pure { diff --git a/test/src/lib/LibDecimalFloat.pack.t.sol b/test/src/lib/LibDecimalFloat.pack.t.sol index bbfe5be7..33d56d8e 100644 --- a/test/src/lib/LibDecimalFloat.pack.t.sol +++ b/test/src/lib/LibDecimalFloat.pack.t.sol @@ -1,11 +1,34 @@ // SPDX-License-Identifier: CAL pragma solidity ^0.8.25; -import {LibDecimalFloat, ExponentOverflow, EXPONENT_MIN, EXPONENT_MAX, PackedFloat} from "src/lib/LibDecimalFloat.sol"; +import { + LibDecimalFloat, + ExponentOverflow, + EXPONENT_MIN, + EXPONENT_MAX, + PackedFloat, + Float +} 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) { + 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); + } + /// Round trip from/to parts. function testPartsRoundTrip(int128 signedCoefficient, int128 exponent) external pure { PackedFloat packed = LibDecimalFloat.pack(signedCoefficient, exponent); @@ -26,4 +49,27 @@ contract LibDecimalFloatPackTest is Test { assertEq(signedCoefficient, signedCoefficientOut, "coefficient"); assertEq(exponent, exponentOut, "exponent"); } + + /// 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 a2175346..e3278ee9 100644 --- a/test/src/lib/LibDecimalFloat.power.t.sol +++ b/test/src/lib/LibDecimalFloat.power.t.sol @@ -3,11 +3,38 @@ pragma solidity =0.8.25; import {LogTest} from "../../abstract/LogTest.sol"; -import {LibDecimalFloat} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.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 memory floatA, Float memory floatB) external returns (Float memory) { + 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 ( + int256 signedCoefficient, int256 exponent + ) { + Float memory float = this.powerExternal(a, b); + assertEq(signedCoefficient, float.signedCoefficient); + assertEq(exponent, float.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.powerExternal(a, b); + } + } + function checkPower( int256 signedCoefficientA, int256 exponentA, diff --git a/test/src/lib/LibDecimalFloat.power10.t.sol b/test/src/lib/LibDecimalFloat.power10.t.sol index f903ce50..414057e9 100644 --- a/test/src/lib/LibDecimalFloat.power10.t.sol +++ b/test/src/lib/LibDecimalFloat.power10.t.sol @@ -1,12 +1,38 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, EXPONENT_MIN} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, EXPONENT_MIN, Float} from "src/lib/LibDecimalFloat.sol"; import {LogTest} from "../../abstract/LogTest.sol"; import {console2} from "forge-std/Test.sol"; contract LibDecimalFloatPower10Test is LogTest { + using LibDecimalFloat for Float; + + function power10External(int256 signedCoefficient, int256 exponent) external returns (int256, int256) { + address tables = logTables(); + return LibDecimalFloat.power10(tables, signedCoefficient, exponent); + } + + function power10External(Float memory float) external returns (Float memory) { + 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 ( + int256 signedCoefficient, int256 exponent + ) { + Float memory floatPower10 = this.power10External(float); + assertEq(signedCoefficient, floatPower10.signedCoefficient); + assertEq(exponent, floatPower10.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + this.power10External(float); + } + } + function checkPower10( int256 signedCoefficient, int256 exponent, diff --git a/test/src/lib/LibDecimalFloat.sub.t.sol b/test/src/lib/LibDecimalFloat.sub.t.sol index 17e4e4b9..45413af6 100644 --- a/test/src/lib/LibDecimalFloat.sub.t.sol +++ b/test/src/lib/LibDecimalFloat.sub.t.sol @@ -1,12 +1,40 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX} from "src/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, EXPONENT_MIN, EXPONENT_MAX, Float} from "src/lib/LibDecimalFloat.sol"; import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {Test} from "forge-std/Test.sol"; contract LibDecimalFloatSubTest is Test { + using LibDecimalFloat for Float; + + function subExternal(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) + external + pure + returns (int256, int256) + { + return LibDecimalFloat.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB); + } + + function subExternal(Float memory floatA, Float memory floatB) external pure returns (Float memory) { + return LibDecimalFloat.sub(floatA, floatB); + } + + /// Stack and mem are the same. + function testSubMem(Float memory a, Float memory b) external { + try this.subExternal(a.signedCoefficient, a.exponent, b.signedCoefficient, b.exponent) returns ( + int256 signedCoefficient, int256 exponent + ) { + Float memory float = this.subExternal(a, b); + assertEq(signedCoefficient, float.signedCoefficient); + assertEq(exponent, float.exponent); + } catch (bytes memory err) { + vm.expectRevert(err); + 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 From 23c6bb8d8915fbc7a4d4ed28df55f3bf86260414 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 1 Apr 2025 20:58:23 +0400 Subject: [PATCH 2/4] docs --- src/lib/LibDecimalFloat.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 7a1e1ac5..d198234b 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -38,6 +38,12 @@ 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; From 8da7820e2cd91de624b5dccd90af6bd6040db276 Mon Sep 17 00:00:00 2001 From: David Meister Date: Tue, 1 Apr 2025 21:15:35 +0400 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- test/src/lib/LibDecimalFloat.abs.t.sol | 3 ++- test/src/lib/LibDecimalFloat.eq.t.sol | 3 +-- test/src/lib/LibDecimalFloat.frac.t.sol | 3 +-- test/src/lib/LibDecimalFloat.lt.t.sol | 3 +-- test/src/lib/LibDecimalFloat.sub.t.sol | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/src/lib/LibDecimalFloat.abs.t.sol b/test/src/lib/LibDecimalFloat.abs.t.sol index f4574691..39309d38 100644 --- a/test/src/lib/LibDecimalFloat.abs.t.sol +++ b/test/src/lib/LibDecimalFloat.abs.t.sol @@ -15,7 +15,8 @@ contract LibDecimalFloatAbsTest is Test { function absExternal(Float memory float) external pure returns (Float memory) { return LibDecimalFloat.abs(float); } - /// Stack and mem are the same. + /// 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 ( diff --git a/test/src/lib/LibDecimalFloat.eq.t.sol b/test/src/lib/LibDecimalFloat.eq.t.sol index 2f2c5782..8dc0cc79 100644 --- a/test/src/lib/LibDecimalFloat.eq.t.sol +++ b/test/src/lib/LibDecimalFloat.eq.t.sol @@ -21,8 +21,7 @@ contract LibDecimalFloatEqTest is Test { function eqExternal(Float memory floatA, Float memory floatB) external pure returns (bool) { return LibDecimalFloat.eq(floatA, floatB); } - /// Stack and mem are the same. - + /// 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) { bool actual = this.eqExternal(a, b); diff --git a/test/src/lib/LibDecimalFloat.frac.t.sol b/test/src/lib/LibDecimalFloat.frac.t.sol index 6c5863bc..29380cce 100644 --- a/test/src/lib/LibDecimalFloat.frac.t.sol +++ b/test/src/lib/LibDecimalFloat.frac.t.sol @@ -15,8 +15,7 @@ contract LibDecimalFloatFracTest is Test { function fracExternal(Float memory float) external pure returns (Float memory) { return LibDecimalFloat.frac(float); } - /// Stack and mem are the same. - + /// 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 diff --git a/test/src/lib/LibDecimalFloat.lt.t.sol b/test/src/lib/LibDecimalFloat.lt.t.sol index 291c6807..f376346f 100644 --- a/test/src/lib/LibDecimalFloat.lt.t.sol +++ b/test/src/lib/LibDecimalFloat.lt.t.sol @@ -21,8 +21,7 @@ contract LibDecimalFloatLtTest is Test { function ltExternal(Float memory floatA, Float memory floatB) external pure returns (bool) { return LibDecimalFloat.lt(floatA, floatB); } - /// Stack and mem are the same. - + /// 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) { bool actual = this.ltExternal(a, b); diff --git a/test/src/lib/LibDecimalFloat.sub.t.sol b/test/src/lib/LibDecimalFloat.sub.t.sol index 45413af6..6ecf578b 100644 --- a/test/src/lib/LibDecimalFloat.sub.t.sol +++ b/test/src/lib/LibDecimalFloat.sub.t.sol @@ -21,7 +21,7 @@ contract LibDecimalFloatSubTest is Test { return LibDecimalFloat.sub(floatA, floatB); } - /// Stack and mem are the same. + /// 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 ( int256 signedCoefficient, int256 exponent From d984580b9211c3d656590cad7009b09ce1eb009c Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 1 Apr 2025 21:38:38 +0400 Subject: [PATCH 4/4] fmt --- test/src/lib/LibDecimalFloat.abs.t.sol | 2 +- test/src/lib/LibDecimalFloat.eq.t.sol | 1 + test/src/lib/LibDecimalFloat.frac.t.sol | 1 + test/src/lib/LibDecimalFloat.lt.t.sol | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/src/lib/LibDecimalFloat.abs.t.sol b/test/src/lib/LibDecimalFloat.abs.t.sol index 39309d38..7391ad4c 100644 --- a/test/src/lib/LibDecimalFloat.abs.t.sol +++ b/test/src/lib/LibDecimalFloat.abs.t.sol @@ -15,7 +15,7 @@ contract LibDecimalFloatAbsTest is Test { function absExternal(Float memory float) external pure returns (Float memory) { return LibDecimalFloat.abs(float); } - /// Validate that operations using stack-based parameters (int256, int256) + /// Validate that operations using stack-based parameters (int256, int256) /// and memory-based parameters (Float struct) yield identical results. function testAbsMem(Float memory float) external { diff --git a/test/src/lib/LibDecimalFloat.eq.t.sol b/test/src/lib/LibDecimalFloat.eq.t.sol index 8dc0cc79..b18485ac 100644 --- a/test/src/lib/LibDecimalFloat.eq.t.sol +++ b/test/src/lib/LibDecimalFloat.eq.t.sol @@ -22,6 +22,7 @@ contract LibDecimalFloatEqTest is Test { 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) { bool actual = this.eqExternal(a, b); diff --git a/test/src/lib/LibDecimalFloat.frac.t.sol b/test/src/lib/LibDecimalFloat.frac.t.sol index 29380cce..72a10abe 100644 --- a/test/src/lib/LibDecimalFloat.frac.t.sol +++ b/test/src/lib/LibDecimalFloat.frac.t.sol @@ -16,6 +16,7 @@ contract LibDecimalFloatFracTest is Test { 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 diff --git a/test/src/lib/LibDecimalFloat.lt.t.sol b/test/src/lib/LibDecimalFloat.lt.t.sol index f376346f..588eceae 100644 --- a/test/src/lib/LibDecimalFloat.lt.t.sol +++ b/test/src/lib/LibDecimalFloat.lt.t.sol @@ -22,6 +22,7 @@ contract LibDecimalFloatLtTest is Test { 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) { bool actual = this.ltExternal(a, b);