From 005a166988d23f0ff2221f6fd50a0761692df010 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 12:29:49 +0200 Subject: [PATCH 01/10] wip on non error underflow div --- src/error/ErrDecimalFloat.sol | 8 + src/lib/format/LibFormatDecimalFloat.sol | 2 +- .../LibDecimalFloatImplementation.sol | 141 +++++++++++++----- .../LibDecimalFloatImplementation.add.t.sol | 10 +- .../LibDecimalFloatImplementation.div.t.sol | 21 ++- ...bDecimalFloatImplementation.maximize.t.sol | 10 +- 6 files changed, 142 insertions(+), 50 deletions(-) diff --git a/src/error/ErrDecimalFloat.sol b/src/error/ErrDecimalFloat.sol index a1f7abe4..171fc676 100644 --- a/src/error/ErrDecimalFloat.sol +++ b/src/error/ErrDecimalFloat.sol @@ -33,3 +33,11 @@ error ZeroNegativePower(Float b); /// @dev Thrown when mulDiv internal to division overflows. error MulDivOverflow(uint256 x, uint256 y, uint256 denominator); + +/// @dev Thrown when a maximize underflows where it is not appropriate. +error MaximizeUnderflow(int256 signedCoefficient, int256 exponent); + +/// @dev Thrown when dividing by zero. +/// @param signedCoefficient The signed coefficient of the numerator. +/// @param exponent The exponent of the numerator. +error DivisionByZero(int256 signedCoefficient, int256 exponent); diff --git a/src/lib/format/LibFormatDecimalFloat.sol b/src/lib/format/LibFormatDecimalFloat.sol index 255cf5cc..7acf1e6f 100644 --- a/src/lib/format/LibFormatDecimalFloat.sol +++ b/src/lib/format/LibFormatDecimalFloat.sol @@ -57,7 +57,7 @@ library LibFormatDecimalFloat { uint256 scaleExponent; uint256 scale = 0; if (scientific) { - (signedCoefficient, exponent) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + (signedCoefficient, exponent) = LibDecimalFloatImplementation.maximizeFull(signedCoefficient, exponent); bool isAtLeastE76 = signedCoefficient / 1e76 != 0; scaleExponent = isAtLeastE76 ? uint256(76) : uint256(75); diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index b944146a..613ca577 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -2,7 +2,14 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {ExponentOverflow, Log10Negative, Log10Zero, MulDivOverflow} from "../../error/ErrDecimalFloat.sol"; +import { + ExponentOverflow, + Log10Negative, + Log10Zero, + MulDivOverflow, + MaximizeUnderflow, + DivisionByZero +} from "../../error/ErrDecimalFloat.sol"; import { LOG_TABLES, LOG_TABLES_SMALL, @@ -11,6 +18,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); @@ -225,19 +233,24 @@ library LibDecimalFloatImplementation { pure returns (int256 signedCoefficient, int256 exponent) { - if (signedCoefficientA == 0 && signedCoefficientB != 0) { + if (signedCoefficientB == 0) { + revert DivisionByZero(signedCoefficientA, exponentA); + } else if (signedCoefficientA == 0) { signedCoefficient = MAXIMIZED_ZERO_SIGNED_COEFFICIENT; exponent = MAXIMIZED_ZERO_EXPONENT; } else { + bool fullA; + bool fullB; // Move both coefficients into the e75/e76 range, so that the result // of division will not cause a mulDiv overflow. - (signedCoefficientA, exponentA) = maximize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB) = maximize(signedCoefficientB, exponentB); + (signedCoefficientA, exponentA, fullA) = maximize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB, fullB) = maximize(signedCoefficientB, exponentB); // mulDiv only works with unsigned integers, so get the absolute // values of the coefficients. uint256 signedCoefficientAAbs = absUnsignedSignedCoefficient(signedCoefficientA); uint256 signedCoefficientBAbs = absUnsignedSignedCoefficient(signedCoefficientB); + console2.log(signedCoefficientAAbs, signedCoefficientBAbs); uint256 scale = 1e76; int256 adjustExponent = 76; @@ -249,25 +262,67 @@ library LibDecimalFloatImplementation { // fit in 256 bits by the division of a denominator that is larger // than the scale up. if (signedCoefficientBAbs < scale) { - scale = 1e75; - adjustExponent = 75; + if (fullB) { + scale = 1e75; + adjustExponent = 75; + } else { + // This is potentially quite a slow edge case. + while (signedCoefficientBAbs < scale) { + scale /= 10; + adjustExponent -= 1; + } + } } - // The order of subtraction matters in edge cases. For non-negative - // exponentA, apply the adjust exponent first to move the value - // towards 0 before exponentB is applied. This reduces the chance of - // a transient overflow in the intermediate subtraction. - if (exponentA >= 0) { - exponent = exponentA - adjustExponent - exponentB; - } else { - exponent = exponentA - exponentB - adjustExponent; + + int256 underflowBy; + { + if (exponentA >= 0) { + if (exponentB <= 0) { + // This can't underflow because B is subtracted from A. + // (it could overflow). + } else { + // This can't underflow because subtracting a positive + // value from another positive value cannot underflow + // as the space of negative numbers is larger than + // positive values in signed integers. + } + } else { + if (exponentB <= 0) { + // This can't underflow because B is subtracted from A. + // A is negative so it can't overflow either. + } else { + int256 headroom = -(type(int256).min - exponentA); + underflowBy = exponentB > headroom ? exponentB - headroom : int256(0); + } + } } + if (underflowBy > 76) { + // If the underflow is this large then the result is zero. + signedCoefficient = MAXIMIZED_ZERO_SIGNED_COEFFICIENT; + exponent = MAXIMIZED_ZERO_EXPONENT; + } else { + // The order of subtraction matters in edge cases. For non-negative + // exponentA, apply the adjust exponent first to move the value + // towards 0 before exponentB is applied. This reduces the chance of + // a transient overflow in the intermediate subtraction. + if (exponentA >= 0) { + exponent = exponentA - adjustExponent - exponentB; + } else { + exponent = exponentA + underflowBy - exponentB - adjustExponent; + } - (signedCoefficient, exponent) = unabsUnsignedMulOrDivLossy( - signedCoefficientA, - signedCoefficientB, - mulDiv(signedCoefficientAAbs, scale, signedCoefficientBAbs), - exponent - ); + console2.log("final"); + (signedCoefficient, exponent) = unabsUnsignedMulOrDivLossy( + signedCoefficientA, + signedCoefficientB, + mulDiv(signedCoefficientAAbs, scale, signedCoefficientBAbs), + exponent + ); + signedCoefficient /= int256(10 ** uint256(underflowBy)); + if (signedCoefficient == 0) { + exponent = MAXIMIZED_ZERO_EXPONENT; + } + } } } @@ -440,8 +495,10 @@ library LibDecimalFloatImplementation { // Maximizing 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) = maximize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB) = maximize(signedCoefficientB, exponentB); + bool fullA; + bool fullB; + (signedCoefficientA, exponentA, fullA) = maximize(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB, fullB) = maximize(signedCoefficientB, exponentB); // We want A to represent the larger exponent. If this is not the case // then swap them. @@ -556,14 +613,17 @@ library LibDecimalFloatImplementation { { unchecked { { + int256 unmaximizedCoefficient = signedCoefficient; + int256 unmaximizedExponent = exponent; + (signedCoefficient, exponent) = maximizeFull(signedCoefficient, exponent); + if (signedCoefficient <= 0) { if (signedCoefficient == 0) { revert Log10Zero(); } else { - revert Log10Negative(signedCoefficient, exponent); + revert Log10Negative(unmaximizedCoefficient, unmaximizedExponent); } } - (signedCoefficient, exponent) = maximize(signedCoefficient, exponent); } // all powers of 10 look like 1 with a different exponent @@ -724,37 +784,40 @@ library LibDecimalFloatImplementation { } } - function maximize(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { + /// @return signedCoefficient The maximized signed coefficient. + /// @return exponent The maximized exponent. + /// @return full `true` if the result is fully maximized, `false` if it was + /// not possible to maximize without overflow. + function maximize(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256, bool) { unchecked { if (signedCoefficient == 0) { - return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); + return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT, true); } - int256 initialExponent = exponent; // Check if already maximized before dropping into a block full of // jumps. if (signedCoefficient / 1e75 == 0) { - if (signedCoefficient / 1e38 == 0) { + if (signedCoefficient / 1e38 == 0 && exponent >= type(int256).min + 38) { signedCoefficient *= 1e38; exponent -= 38; } - if (signedCoefficient / 1e57 == 0) { + if (signedCoefficient / 1e57 == 0 && exponent >= type(int256).min + 19) { signedCoefficient *= 1e19; exponent -= 19; } - if (signedCoefficient / 1e66 == 0) { + if (signedCoefficient / 1e66 == 0 && exponent >= type(int256).min + 10) { signedCoefficient *= 1e10; exponent -= 10; } - while (signedCoefficient / 1e74 == 0) { + while (signedCoefficient / 1e74 == 0 && exponent >= type(int256).min + 2) { signedCoefficient *= 1e2; exponent -= 2; } - if (signedCoefficient / 1e75 == 0) { + if (signedCoefficient / 1e75 == 0 && exponent >= type(int256).min + 1) { signedCoefficient *= 10; exponent -= 1; } @@ -764,17 +827,21 @@ library LibDecimalFloatImplementation { // know until we try. This pushes us into [1e76,type(int256).max] and // [-type(int256).max,-1e76] ranges, if that's possible. int256 trySignedCoefficient = signedCoefficient * 10; - if (signedCoefficient == trySignedCoefficient / 10) { + if (signedCoefficient == trySignedCoefficient / 10 && exponent >= type(int256).min + 1) { signedCoefficient = trySignedCoefficient; exponent -= 1; } - if (initialExponent < exponent) { - revert ExponentOverflow(signedCoefficient, initialExponent); - } + return (signedCoefficient, exponent, signedCoefficient / 1e75 != 0); + } + } - return (signedCoefficient, exponent); + function maximizeFull(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { + (int256 trySignedCoefficient, int256 tryExponent, bool full) = maximize(signedCoefficient, exponent); + if (!full) { + revert MaximizeUnderflow(signedCoefficient, exponent); } + return (trySignedCoefficient, tryExponent); } /// Rescale two floats so that they are possible to directly compare using diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol index c101c138..07594505 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol @@ -152,9 +152,9 @@ contract LibDecimalFloatImplementationAddTest is Test { vm.assume(signedCoefficientA != 0); vm.assume(signedCoefficientB != 0); - (int256 normalizedSignedCoefficientA, int256 normalizedExponentA) = + (int256 normalizedSignedCoefficientA, int256 normalizedExponentA, bool fullA) = LibDecimalFloatImplementation.maximize(signedCoefficientA, exponentA); - (int256 expectedSignedCoefficient, int256 expectedExponent) = + (int256 expectedSignedCoefficient, int256 expectedExponent, bool fullExpected) = LibDecimalFloatImplementation.maximize(signedCoefficientB, exponentB); vm.assume(normalizedSignedCoefficientA != 0); @@ -251,8 +251,10 @@ contract LibDecimalFloatImplementationAddTest is Test { int256 exponentB; int256 signedCoefficientAMaximized; int256 signedCoefficientBMaximized; - (signedCoefficientAMaximized, exponentA) = LibDecimalFloatImplementation.maximize(signedCoefficientA, 0); - (signedCoefficientBMaximized, exponentB) = LibDecimalFloatImplementation.maximize(signedCoefficientB, 0); + bool fullA; + bool fullB; + (signedCoefficientAMaximized, exponentA, fullA) = LibDecimalFloatImplementation.maximize(signedCoefficientA, 0); + (signedCoefficientBMaximized, exponentB, fullB) = LibDecimalFloatImplementation.maximize(signedCoefficientB, 0); if (signedCoefficientA == 0 || signedCoefficientB == 0) { exponentA = 0; diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol index 9ffa1ee6..d4d4b589 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol @@ -36,7 +36,7 @@ contract LibDecimalFloatImplementationDivTest is Test { function testDivZero(int256 signedCoefficient, int256 exponent) external { exponent = bound(exponent, type(int256).min / 2, type(int256).max); - (int256 signedCoefficientMaximized, int256 exponentMaximized) = + (int256 signedCoefficientMaximized, int256 exponentMaximized, bool full) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); if (signedCoefficient == 0) { vm.expectRevert(stdError.divisionError); @@ -53,6 +53,21 @@ contract LibDecimalFloatImplementationDivTest is Test { this.divExternal(signedCoefficient, exponent, 0, 0); } + function testDivMaxPositiveValue(int256 signedCoefficient, int256 exponent) external { + // (int256 signedCoefficientMaximized, int256 exponentMaximized) = + // LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + // vm.expectRevert( + // abi.encodeWithSelector( + // MulDivOverflow.selector, + // LibDecimalFloatImplementation.absUnsignedSignedCoefficient(signedCoefficientMaximized), + // 1e75, + // 0 + // ) + // ); + // this.divExternal(signedCoefficient, exponent, 1, -76); + LibDecimalFloatImplementation.div(signedCoefficient, exponent, type(int256).max, type(int32).max); + } + /// 1 / 3 gas by parts 10 function testDiv1Over3Gas10() external pure { (int256 c, int256 e) = LibDecimalFloatImplementation.div(1, 0, 3e37, -37); @@ -131,7 +146,7 @@ contract LibDecimalFloatImplementationDivTest is Test { /// Should be possible to divide every number by 1. function testDivBy1(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, type(int256).min + 76, type(int256).max); - (int256 expectedCoefficient, int256 expectedExponent) = + (int256 expectedCoefficient, int256 expectedExponent, bool full) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); int256 one = 1; @@ -146,7 +161,7 @@ contract LibDecimalFloatImplementationDivTest is Test { function testDivByNegativeOneFloat(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, type(int256).min + 76, type(int256).max - 1); - (int256 expectedCoefficient, int256 expectedExponent) = + (int256 expectedCoefficient, int256 expectedExponent, bool full) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); (expectedCoefficient, expectedExponent) = LibDecimalFloatImplementation.minus(expectedCoefficient, expectedExponent); diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol index 15e06504..c03fec60 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol @@ -33,7 +33,7 @@ contract LibDecimalFloatImplementationMaximizeTest is Test { /// Every normalized number is maximized. function testMaximizedEverything(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - (int256 actualSignedCoefficient, int256 actualExponent) = + (int256 actualSignedCoefficient, int256 actualExponent, bool full) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); assertTrue(isMaximized(actualSignedCoefficient, actualExponent)); } @@ -44,7 +44,7 @@ contract LibDecimalFloatImplementationMaximizeTest is Test { int256 expectedCoefficient, int256 expectedExponent ) internal pure { - (int256 actualSignedCoefficient, int256 actualExponent) = + (int256 actualSignedCoefficient, int256 actualExponent, bool full) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); assertEq(actualSignedCoefficient, expectedCoefficient); assertEq(actualExponent, expectedExponent); @@ -74,9 +74,9 @@ contract LibDecimalFloatImplementationMaximizeTest is Test { /// Maximization should be idempotent. function testMaximizedIdempotent(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - (int256 maximizedSignedCoefficient, int256 maximizedExponent) = + (int256 maximizedSignedCoefficient, int256 maximizedExponent, bool full) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); - (int256 actualSignedCoefficient, int256 actualExponent) = + (int256 actualSignedCoefficient, int256 actualExponent, bool fullActual) = LibDecimalFloatImplementation.maximize(maximizedSignedCoefficient, maximizedExponent); assertEq(actualSignedCoefficient, maximizedSignedCoefficient); assertEq(actualExponent, maximizedExponent); @@ -85,7 +85,7 @@ contract LibDecimalFloatImplementationMaximizeTest is Test { /// Maximization against reference. function testMaximizedReference(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - (int256 actualSignedCoefficient, int256 actualExponent) = + (int256 actualSignedCoefficient, int256 actualExponent, bool fullActual) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); (int256 expectedSignedCoefficient, int256 expectedExponent) = LibDecimalFloatSlow.maximizeSlow(signedCoefficient, exponent); From df0ea7804a4550c577fc452c70b58d6e165f5d5a Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 15:10:17 +0200 Subject: [PATCH 02/10] wip on div --- .../LibDecimalFloatImplementation.sol | 75 +++++++++---------- test/src/lib/LibDecimalFloat.sub.t.sol | 7 +- .../LibDecimalFloatImplementation.div.t.sol | 30 +------- .../LibDecimalFloatImplementation.inv.t.sol | 5 +- 4 files changed, 43 insertions(+), 74 deletions(-) diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 613ca577..9bdbb5db 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -231,14 +231,15 @@ library LibDecimalFloatImplementation { function div(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal pure - returns (int256 signedCoefficient, int256 exponent) + returns (int256, int256) { if (signedCoefficientB == 0) { revert DivisionByZero(signedCoefficientA, exponentA); } else if (signedCoefficientA == 0) { - signedCoefficient = MAXIMIZED_ZERO_SIGNED_COEFFICIENT; - exponent = MAXIMIZED_ZERO_EXPONENT; + return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); } else { + int256 signedCoefficient; + int256 exponent; bool fullA; bool fullB; // Move both coefficients into the e75/e76 range, so that the result @@ -250,7 +251,6 @@ library LibDecimalFloatImplementation { // values of the coefficients. uint256 signedCoefficientAAbs = absUnsignedSignedCoefficient(signedCoefficientA); uint256 signedCoefficientBAbs = absUnsignedSignedCoefficient(signedCoefficientB); - console2.log(signedCoefficientAAbs, signedCoefficientBAbs); uint256 scale = 1e76; int256 adjustExponent = 76; @@ -268,60 +268,53 @@ library LibDecimalFloatImplementation { } else { // This is potentially quite a slow edge case. while (signedCoefficientBAbs < scale) { - scale /= 10; - adjustExponent -= 1; + unchecked { + scale /= 10; + adjustExponent -= 1; + } } } } - - int256 underflowBy; - { - if (exponentA >= 0) { - if (exponentB <= 0) { - // This can't underflow because B is subtracted from A. - // (it could overflow). - } else { - // This can't underflow because subtracting a positive - // value from another positive value cannot underflow - // as the space of negative numbers is larger than - // positive values in signed integers. - } + if (exponentA >= 0) { + exponentA -= adjustExponent; + } else { + if (exponentB <= type(int256).max - adjustExponent) { + exponentB += adjustExponent; } else { - if (exponentB <= 0) { - // This can't underflow because B is subtracted from A. - // A is negative so it can't overflow either. - } else { - int256 headroom = -(type(int256).min - exponentA); - underflowBy = exponentB > headroom ? exponentB - headroom : int256(0); - } + // The numerator is at most ~1e76 because the exponent is negative + // and the denominator is incredibly large due to the exponent + // being very close to type(int256).max. This means the result + // is effectively zero. + return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); } } - if (underflowBy > 76) { - // If the underflow is this large then the result is zero. - signedCoefficient = MAXIMIZED_ZERO_SIGNED_COEFFICIENT; - exponent = MAXIMIZED_ZERO_EXPONENT; - } else { - // The order of subtraction matters in edge cases. For non-negative - // exponentA, apply the adjust exponent first to move the value - // towards 0 before exponentB is applied. This reduces the chance of - // a transient overflow in the intermediate subtraction. - if (exponentA >= 0) { - exponent = exponentA - adjustExponent - exponentB; - } else { - exponent = exponentA + underflowBy - exponentB - adjustExponent; + + int256 underflowExponentBy; + + // This is the only case that can underflow. + if (exponentA < 0 && exponentB > 0) { + unchecked { + int256 headroom = -(type(int256).min - exponentA); + underflowExponentBy = exponentB > headroom ? exponentB - headroom : int256(0); } + } + + if (underflowExponentBy > 76) { + return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); + } else { + exponent = exponentA + underflowExponentBy - exponentB; - console2.log("final"); (signedCoefficient, exponent) = unabsUnsignedMulOrDivLossy( signedCoefficientA, signedCoefficientB, mulDiv(signedCoefficientAAbs, scale, signedCoefficientBAbs), exponent ); - signedCoefficient /= int256(10 ** uint256(underflowBy)); + signedCoefficient /= int256(10 ** uint256(underflowExponentBy)); if (signedCoefficient == 0) { exponent = MAXIMIZED_ZERO_EXPONENT; } + return (signedCoefficient, exponent); } } } diff --git a/test/src/lib/LibDecimalFloat.sub.t.sol b/test/src/lib/LibDecimalFloat.sub.t.sol index 79148f4a..47c65932 100644 --- a/test/src/lib/LibDecimalFloat.sub.t.sol +++ b/test/src/lib/LibDecimalFloat.sub.t.sol @@ -28,10 +28,9 @@ contract LibDecimalFloatSubTest is Test { try this.subExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - Float float = this.subExternal(a, b); - (Float floatImplementation, bool lossless) = LibDecimalFloat.packLossy(signedCoefficient, exponent); - (lossless); - assertTrue(float.eq(floatImplementation)); + // Float float = this.subExternal(a, b); + // (int256 signedCoefficientFloat, int256 exponentFloat) = float.unpack(); + // assertTrue(LibDecimalFloatImplementation.eq(signedCoefficient, exponent, signedCoefficientFloat, exponentFloat)); } catch (bytes memory err) { vm.expectRevert(err); this.subExternal(a, b); diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol index d4d4b589..faa795e7 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol @@ -7,7 +7,7 @@ import { LibDecimalFloatImplementation, EXPONENT_MIN, EXPONENT_MAX, - MulDivOverflow + DivisionByZero } from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {THREES, ONES} from "../../../lib/LibCommonResults.sol"; @@ -36,35 +36,11 @@ contract LibDecimalFloatImplementationDivTest is Test { function testDivZero(int256 signedCoefficient, int256 exponent) external { exponent = bound(exponent, type(int256).min / 2, type(int256).max); - (int256 signedCoefficientMaximized, int256 exponentMaximized, bool full) = - LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); - if (signedCoefficient == 0) { - vm.expectRevert(stdError.divisionError); - } else { - vm.expectRevert( - abi.encodeWithSelector( - MulDivOverflow.selector, - LibDecimalFloatImplementation.absUnsignedSignedCoefficient(signedCoefficientMaximized), - 1e75, - 0 - ) - ); - } + vm.expectRevert(abi.encodeWithSelector(DivisionByZero.selector, signedCoefficient, exponent)); this.divExternal(signedCoefficient, exponent, 0, 0); } - function testDivMaxPositiveValue(int256 signedCoefficient, int256 exponent) external { - // (int256 signedCoefficientMaximized, int256 exponentMaximized) = - // LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); - // vm.expectRevert( - // abi.encodeWithSelector( - // MulDivOverflow.selector, - // LibDecimalFloatImplementation.absUnsignedSignedCoefficient(signedCoefficientMaximized), - // 1e75, - // 0 - // ) - // ); - // this.divExternal(signedCoefficient, exponent, 1, -76); + function testDivMaxPositiveValueNotRevert(int256 signedCoefficient, int256 exponent) external pure { LibDecimalFloatImplementation.div(signedCoefficient, exponent, type(int256).max, type(int32).max); } diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol index b08d6e97..ac6bc29e 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol @@ -8,7 +8,8 @@ import { LibDecimalFloatImplementation, EXPONENT_MIN, EXPONENT_MAX, - MulDivOverflow + MulDivOverflow, + DivisionByZero } from "src/lib/implementation/LibDecimalFloatImplementation.sol"; contract LibDecimalFloatImplementationInvTest is Test { @@ -42,7 +43,7 @@ contract LibDecimalFloatImplementationInvTest is Test { } function testInv0() external { - vm.expectRevert(abi.encodeWithSelector(MulDivOverflow.selector, 1e76, 1e75, 0)); + vm.expectRevert(abi.encodeWithSelector(DivisionByZero.selector, 1e76, -76)); this.invExternal(0, 0); } } From 485637b9587617a3ad98a9fe6c9daac440608551 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 15:28:44 +0200 Subject: [PATCH 03/10] update div --- .../LibDecimalFloatImplementation.sol | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 9bdbb5db..641208b7 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -275,21 +275,31 @@ library LibDecimalFloatImplementation { } } } - if (exponentA >= 0) { - exponentA -= adjustExponent; - } else { - if (exponentB <= type(int256).max - adjustExponent) { - exponentB += adjustExponent; + + // Attempt to apply the exponent adjustment. + // First we try to apply it to exponentA. + // If we cannot fully apply it we try to apply the rest to exponentB. + // If we still have some left over then we just return zero as + // the difference in exponents is too large to represent in + // a single result negative exponent. + { + if (exponentA >= type(int256).min + adjustExponent) { + exponentA -= adjustExponent; } else { - // The numerator is at most ~1e76 because the exponent is negative - // and the denominator is incredibly large due to the exponent - // being very close to type(int256).max. This means the result - // is effectively zero. - return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); + adjustExponent -= exponentA - type(int256).min; + exponentA = type(int256).min; + + if (adjustExponent > 0) { + if (exponentB <= type(int256).max - adjustExponent) { + exponentB += adjustExponent; + } else { + return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); + } + } } } - int256 underflowExponentBy; + int256 underflowExponentBy = 0; // This is the only case that can underflow. if (exponentA < 0 && exponentB > 0) { From d9a1bd4201cbc8abc2eb1cc4abcf752c9ee4ef93 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 15:43:23 +0200 Subject: [PATCH 04/10] wip on fixing sub --- .../LibDecimalFloatImplementation.sol | 33 ++++++++++--------- test/src/lib/LibDecimalFloat.sub.t.sol | 7 ++-- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 641208b7..5f637f36 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -18,7 +18,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); @@ -304,28 +303,32 @@ library LibDecimalFloatImplementation { // This is the only case that can underflow. if (exponentA < 0 && exponentB > 0) { unchecked { - int256 headroom = -(type(int256).min - exponentA); + int256 headroom = exponentA - type(int256).min; underflowExponentBy = exponentB > headroom ? exponentB - headroom : int256(0); } } - if (underflowExponentBy > 76) { - return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); - } else { - exponent = exponentA + underflowExponentBy - exponentB; + exponent = exponentA + underflowExponentBy - exponentB; + + (signedCoefficient, exponent) = unabsUnsignedMulOrDivLossy( + signedCoefficientA, + signedCoefficientB, + mulDiv(signedCoefficientAAbs, scale, signedCoefficientBAbs), + exponent + ); + + if (underflowExponentBy > 0) { + if (underflowExponentBy > 76) { + // This means the exponent is too small to represent. + return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); + } - (signedCoefficient, exponent) = unabsUnsignedMulOrDivLossy( - signedCoefficientA, - signedCoefficientB, - mulDiv(signedCoefficientAAbs, scale, signedCoefficientBAbs), - exponent - ); signedCoefficient /= int256(10 ** uint256(underflowExponentBy)); if (signedCoefficient == 0) { exponent = MAXIMIZED_ZERO_EXPONENT; } - return (signedCoefficient, exponent); } + return (signedCoefficient, exponent); } } @@ -500,8 +503,8 @@ library LibDecimalFloatImplementation { // simply ignoring one of them. bool fullA; bool fullB; - (signedCoefficientA, exponentA, fullA) = maximize(signedCoefficientA, exponentA); - (signedCoefficientB, exponentB, fullB) = maximize(signedCoefficientB, exponentB); + (signedCoefficientA, exponentA) = maximizeFull(signedCoefficientA, exponentA); + (signedCoefficientB, exponentB) = maximizeFull(signedCoefficientB, exponentB); // We want A to represent the larger exponent. If this is not the case // then swap them. diff --git a/test/src/lib/LibDecimalFloat.sub.t.sol b/test/src/lib/LibDecimalFloat.sub.t.sol index 47c65932..79148f4a 100644 --- a/test/src/lib/LibDecimalFloat.sub.t.sol +++ b/test/src/lib/LibDecimalFloat.sub.t.sol @@ -28,9 +28,10 @@ contract LibDecimalFloatSubTest is Test { try this.subExternal(signedCoefficientA, exponentA, signedCoefficientB, exponentB) returns ( int256 signedCoefficient, int256 exponent ) { - // Float float = this.subExternal(a, b); - // (int256 signedCoefficientFloat, int256 exponentFloat) = float.unpack(); - // assertTrue(LibDecimalFloatImplementation.eq(signedCoefficient, exponent, signedCoefficientFloat, exponentFloat)); + Float float = this.subExternal(a, b); + (Float floatImplementation, bool lossless) = LibDecimalFloat.packLossy(signedCoefficient, exponent); + (lossless); + assertTrue(float.eq(floatImplementation)); } catch (bytes memory err) { vm.expectRevert(err); this.subExternal(a, b); From 59c94d5b631db5f805dc05a859fc37001f93dc33 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 17:11:42 +0200 Subject: [PATCH 05/10] lint --- src/error/ErrDecimalFloat.sol | 3 +++ .../implementation/LibDecimalFloatImplementation.sol | 11 +++++++---- .../LibDecimalFloatImplementation.div.t.sol | 10 +++++----- .../LibDecimalFloatImplementation.inv.t.sol | 1 - 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/error/ErrDecimalFloat.sol b/src/error/ErrDecimalFloat.sol index 171fc676..9bda41c7 100644 --- a/src/error/ErrDecimalFloat.sol +++ b/src/error/ErrDecimalFloat.sol @@ -37,6 +37,9 @@ error MulDivOverflow(uint256 x, uint256 y, uint256 denominator); /// @dev Thrown when a maximize underflows where it is not appropriate. error MaximizeUnderflow(int256 signedCoefficient, int256 exponent); +/// @dev Thrown when a maximize overflows where it is not appropriate. +error MaximizeOverflow(int256 signedCoefficient, int256 exponent); + /// @dev Thrown when dividing by zero. /// @param signedCoefficient The signed coefficient of the numerator. /// @param exponent The exponent of the numerator. diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 5f637f36..de7e218d 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -8,7 +8,8 @@ import { Log10Zero, MulDivOverflow, MaximizeUnderflow, - DivisionByZero + DivisionByZero, + MaximizeOverflow } from "../../error/ErrDecimalFloat.sol"; import { LOG_TABLES, @@ -273,6 +274,9 @@ library LibDecimalFloatImplementation { } } } + if (!fullA) { + revert MaximizeOverflow(signedCoefficientA, exponentA); + } } // Attempt to apply the exponent adjustment. @@ -319,7 +323,8 @@ library LibDecimalFloatImplementation { if (underflowExponentBy > 0) { if (underflowExponentBy > 76) { - // This means the exponent is too small to represent. + // This means the exponent is too small to represent even if + // we truncate and downscale the signed coefficient. return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); } @@ -501,8 +506,6 @@ library LibDecimalFloatImplementation { // Maximizing 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. - bool fullA; - bool fullB; (signedCoefficientA, exponentA) = maximizeFull(signedCoefficientA, exponentA); (signedCoefficientB, exponentB) = maximizeFull(signedCoefficientB, exponentB); diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol index faa795e7..bebf82be 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol @@ -40,7 +40,7 @@ contract LibDecimalFloatImplementationDivTest is Test { this.divExternal(signedCoefficient, exponent, 0, 0); } - function testDivMaxPositiveValueNotRevert(int256 signedCoefficient, int256 exponent) external pure { + function testDivMaxPositiveValueDenominatorNotRevert(int256 signedCoefficient, int256 exponent) external pure { LibDecimalFloatImplementation.div(signedCoefficient, exponent, type(int256).max, type(int32).max); } @@ -122,8 +122,8 @@ contract LibDecimalFloatImplementationDivTest is Test { /// Should be possible to divide every number by 1. function testDivBy1(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, type(int256).min + 76, type(int256).max); - (int256 expectedCoefficient, int256 expectedExponent, bool full) = - LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + (int256 expectedCoefficient, int256 expectedExponent) = + LibDecimalFloatImplementation.maximizeFull(signedCoefficient, exponent); int256 one = 1; for (int256 oneExponent = 0; oneExponent >= -76; --oneExponent) { @@ -137,8 +137,8 @@ contract LibDecimalFloatImplementationDivTest is Test { function testDivByNegativeOneFloat(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, type(int256).min + 76, type(int256).max - 1); - (int256 expectedCoefficient, int256 expectedExponent, bool full) = - LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + (int256 expectedCoefficient, int256 expectedExponent) = + LibDecimalFloatImplementation.maximizeFull(signedCoefficient, exponent); (expectedCoefficient, expectedExponent) = LibDecimalFloatImplementation.minus(expectedCoefficient, expectedExponent); diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol index ac6bc29e..43269ef2 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.inv.t.sol @@ -8,7 +8,6 @@ import { LibDecimalFloatImplementation, EXPONENT_MIN, EXPONENT_MAX, - MulDivOverflow, DivisionByZero } from "src/lib/implementation/LibDecimalFloatImplementation.sol"; From 53adbd40a63dc0019520d2c6fbb6a83229867bd3 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 17:20:17 +0200 Subject: [PATCH 06/10] lint --- src/error/ErrDecimalFloat.sol | 3 --- .../LibDecimalFloatImplementation.sol | 3 +-- .../LibDecimalFloatImplementation.add.t.sol | 8 ++++---- ...bDecimalFloatImplementation.maximize.t.sol | 20 +++++++++---------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/error/ErrDecimalFloat.sol b/src/error/ErrDecimalFloat.sol index 9bda41c7..e91bb87a 100644 --- a/src/error/ErrDecimalFloat.sol +++ b/src/error/ErrDecimalFloat.sol @@ -34,9 +34,6 @@ error ZeroNegativePower(Float b); /// @dev Thrown when mulDiv internal to division overflows. error MulDivOverflow(uint256 x, uint256 y, uint256 denominator); -/// @dev Thrown when a maximize underflows where it is not appropriate. -error MaximizeUnderflow(int256 signedCoefficient, int256 exponent); - /// @dev Thrown when a maximize overflows where it is not appropriate. error MaximizeOverflow(int256 signedCoefficient, int256 exponent); diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index de7e218d..4524977f 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -7,7 +7,6 @@ import { Log10Negative, Log10Zero, MulDivOverflow, - MaximizeUnderflow, DivisionByZero, MaximizeOverflow } from "../../error/ErrDecimalFloat.sol"; @@ -848,7 +847,7 @@ library LibDecimalFloatImplementation { function maximizeFull(int256 signedCoefficient, int256 exponent) internal pure returns (int256, int256) { (int256 trySignedCoefficient, int256 tryExponent, bool full) = maximize(signedCoefficient, exponent); if (!full) { - revert MaximizeUnderflow(signedCoefficient, exponent); + revert MaximizeOverflow(signedCoefficient, exponent); } return (trySignedCoefficient, tryExponent); } diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol index 07594505..c1f2a7cd 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol @@ -152,10 +152,10 @@ contract LibDecimalFloatImplementationAddTest is Test { vm.assume(signedCoefficientA != 0); vm.assume(signedCoefficientB != 0); - (int256 normalizedSignedCoefficientA, int256 normalizedExponentA, bool fullA) = - LibDecimalFloatImplementation.maximize(signedCoefficientA, exponentA); - (int256 expectedSignedCoefficient, int256 expectedExponent, bool fullExpected) = - LibDecimalFloatImplementation.maximize(signedCoefficientB, exponentB); + (int256 normalizedSignedCoefficientA, int256 normalizedExponentA) = + LibDecimalFloatImplementation.maximizeFull(signedCoefficientA, exponentA); + (int256 expectedSignedCoefficient, int256 expectedExponent) = + LibDecimalFloatImplementation.maximizeFull(signedCoefficientB, exponentB); vm.assume(normalizedSignedCoefficientA != 0); vm.assume(expectedSignedCoefficient != 0); diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol index c03fec60..73be8237 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.maximize.t.sol @@ -33,8 +33,8 @@ contract LibDecimalFloatImplementationMaximizeTest is Test { /// Every normalized number is maximized. function testMaximizedEverything(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - (int256 actualSignedCoefficient, int256 actualExponent, bool full) = - LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + (int256 actualSignedCoefficient, int256 actualExponent) = + LibDecimalFloatImplementation.maximizeFull(signedCoefficient, exponent); assertTrue(isMaximized(actualSignedCoefficient, actualExponent)); } @@ -44,8 +44,8 @@ contract LibDecimalFloatImplementationMaximizeTest is Test { int256 expectedCoefficient, int256 expectedExponent ) internal pure { - (int256 actualSignedCoefficient, int256 actualExponent, bool full) = - LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + (int256 actualSignedCoefficient, int256 actualExponent) = + LibDecimalFloatImplementation.maximizeFull(signedCoefficient, exponent); assertEq(actualSignedCoefficient, expectedCoefficient); assertEq(actualExponent, expectedExponent); } @@ -74,10 +74,10 @@ contract LibDecimalFloatImplementationMaximizeTest is Test { /// Maximization should be idempotent. function testMaximizedIdempotent(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - (int256 maximizedSignedCoefficient, int256 maximizedExponent, bool full) = - LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); - (int256 actualSignedCoefficient, int256 actualExponent, bool fullActual) = - LibDecimalFloatImplementation.maximize(maximizedSignedCoefficient, maximizedExponent); + (int256 maximizedSignedCoefficient, int256 maximizedExponent) = + LibDecimalFloatImplementation.maximizeFull(signedCoefficient, exponent); + (int256 actualSignedCoefficient, int256 actualExponent) = + LibDecimalFloatImplementation.maximizeFull(maximizedSignedCoefficient, maximizedExponent); assertEq(actualSignedCoefficient, maximizedSignedCoefficient); assertEq(actualExponent, maximizedExponent); } @@ -85,8 +85,8 @@ contract LibDecimalFloatImplementationMaximizeTest is Test { /// Maximization against reference. function testMaximizedReference(int256 signedCoefficient, int256 exponent) external pure { exponent = bound(exponent, EXPONENT_MIN, EXPONENT_MAX); - (int256 actualSignedCoefficient, int256 actualExponent, bool fullActual) = - LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + (int256 actualSignedCoefficient, int256 actualExponent) = + LibDecimalFloatImplementation.maximizeFull(signedCoefficient, exponent); (int256 expectedSignedCoefficient, int256 expectedExponent) = LibDecimalFloatSlow.maximizeSlow(signedCoefficient, exponent); assertEq(actualSignedCoefficient, expectedSignedCoefficient); From ddc346d8aba73ffb2806197acaa4a0f9ce918f09 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 17:32:04 +0200 Subject: [PATCH 07/10] fix sub test --- test/src/lib/LibDecimalFloat.sub.t.sol | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/src/lib/LibDecimalFloat.sub.t.sol b/test/src/lib/LibDecimalFloat.sub.t.sol index 79148f4a..e2c6e2da 100644 --- a/test/src/lib/LibDecimalFloat.sub.t.sol +++ b/test/src/lib/LibDecimalFloat.sub.t.sol @@ -22,16 +22,24 @@ contract LibDecimalFloatSubTest is Test { return LibDecimalFloat.sub(floatA, floatB); } + function packLossyExternal(int256 signedCoefficient, int256 exponent) external pure returns (Float, bool) { + return LibDecimalFloat.packLossy(signedCoefficient, exponent); + } + 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 ( int256 signedCoefficient, int256 exponent ) { - Float float = this.subExternal(a, b); - (Float floatImplementation, bool lossless) = LibDecimalFloat.packLossy(signedCoefficient, exponent); - (lossless); - assertTrue(float.eq(floatImplementation)); + try this.packLossyExternal(signedCoefficient, exponent) returns (Float float, bool lossless) { + (lossless); + Float floatImplementation = this.subExternal(a, b); + assertTrue(float.eq(floatImplementation)); + } catch (bytes memory err) { + vm.expectRevert(err); + this.packLossyExternal(signedCoefficient, exponent); + } } catch (bytes memory err) { vm.expectRevert(err); this.subExternal(a, b); From 30ff6e554df497a8bba5d22663c219affedf2c60 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 17:35:06 +0200 Subject: [PATCH 08/10] lint --- .../implementation/LibDecimalFloatImplementation.add.t.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol index c1f2a7cd..62216172 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.add.t.sol @@ -251,10 +251,8 @@ contract LibDecimalFloatImplementationAddTest is Test { int256 exponentB; int256 signedCoefficientAMaximized; int256 signedCoefficientBMaximized; - bool fullA; - bool fullB; - (signedCoefficientAMaximized, exponentA, fullA) = LibDecimalFloatImplementation.maximize(signedCoefficientA, 0); - (signedCoefficientBMaximized, exponentB, fullB) = LibDecimalFloatImplementation.maximize(signedCoefficientB, 0); + (signedCoefficientAMaximized, exponentA) = LibDecimalFloatImplementation.maximizeFull(signedCoefficientA, 0); + (signedCoefficientBMaximized, exponentB) = LibDecimalFloatImplementation.maximizeFull(signedCoefficientB, 0); if (signedCoefficientA == 0 || signedCoefficientB == 0) { exponentA = 0; From 9bce16f10c1369510e16be05c67c8f675f277ad2 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 17:42:12 +0200 Subject: [PATCH 09/10] unchecked in div --- .../LibDecimalFloatImplementation.sol | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 4524977f..0be38a18 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -284,7 +284,7 @@ library LibDecimalFloatImplementation { // If we still have some left over then we just return zero as // the difference in exponents is too large to represent in // a single result negative exponent. - { + unchecked { if (exponentA >= type(int256).min + adjustExponent) { exponentA -= adjustExponent; } else { @@ -303,36 +303,36 @@ library LibDecimalFloatImplementation { int256 underflowExponentBy = 0; - // This is the only case that can underflow. - if (exponentA < 0 && exponentB > 0) { - unchecked { + unchecked { + // This is the only case that can underflow. + if (exponentA < 0 && exponentB > 0) { int256 headroom = exponentA - type(int256).min; underflowExponentBy = exponentB > headroom ? exponentB - headroom : int256(0); } - } - exponent = exponentA + underflowExponentBy - exponentB; + exponent = exponentA + underflowExponentBy - exponentB; - (signedCoefficient, exponent) = unabsUnsignedMulOrDivLossy( - signedCoefficientA, - signedCoefficientB, - mulDiv(signedCoefficientAAbs, scale, signedCoefficientBAbs), - exponent - ); + (signedCoefficient, exponent) = unabsUnsignedMulOrDivLossy( + signedCoefficientA, + signedCoefficientB, + mulDiv(signedCoefficientAAbs, scale, signedCoefficientBAbs), + exponent + ); - if (underflowExponentBy > 0) { - if (underflowExponentBy > 76) { - // This means the exponent is too small to represent even if - // we truncate and downscale the signed coefficient. - return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); - } + if (underflowExponentBy > 0) { + if (underflowExponentBy > 76) { + // This means the exponent is too small to represent even if + // we truncate and downscale the signed coefficient. + return (MAXIMIZED_ZERO_SIGNED_COEFFICIENT, MAXIMIZED_ZERO_EXPONENT); + } - signedCoefficient /= int256(10 ** uint256(underflowExponentBy)); - if (signedCoefficient == 0) { - exponent = MAXIMIZED_ZERO_EXPONENT; + signedCoefficient /= int256(10 ** uint256(underflowExponentBy)); + if (signedCoefficient == 0) { + exponent = MAXIMIZED_ZERO_EXPONENT; + } } + return (signedCoefficient, exponent); } - return (signedCoefficient, exponent); } } From e722e8bd5a9c44e9dabf7cb706b2feb6eae8e632 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 9 Sep 2025 18:09:26 +0200 Subject: [PATCH 10/10] fix tests --- crates/float/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 6bc060e6..5830f8a1 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -1510,7 +1510,7 @@ mod tests { assert!(matches!( err, - FloatError::DecimalFloat(DecimalFloatErrors::MulDivOverflow(_)) + FloatError::DecimalFloat(DecimalFloatErrors::DivisionByZero(_)) )); }