From 33a786b69738edfdcb6d1cb9e8923d649d4c53ee Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 24 Aug 2025 21:54:02 +0400 Subject: [PATCH 1/9] div by one always --- .../LibDecimalFloatImplementation.sol | 12 ++++++++++-- .../LibDecimalFloatImplementation.div.t.sol | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 93a66a34..65cca269 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -280,7 +280,15 @@ library LibDecimalFloatImplementation { scale = 1e75; adjustExponent = 75; } - exponent = exponentA - exponentB - adjustExponent; + + // The order of subtraction matters in edge cases. The adjust + // exponent should move the calculation towards 0 before exponentB + // is applied. + if (exponentA > 0) { + exponent = exponentA - adjustExponent - exponentB; + } else { + exponent = exponentA - exponentB - adjustExponent; + } (signedCoefficient, exponent) = unabsUnsignedMulOrDivLossy( signedCoefficientA, @@ -763,7 +771,7 @@ library LibDecimalFloatImplementation { } if (initialExponent < exponent) { - revert ExponentOverflow(signedCoefficient, exponent); + revert ExponentOverflow(signedCoefficient, initialExponent); } return (signedCoefficient, exponent); diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol index 1090eb93..64622c4a 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol @@ -10,6 +10,8 @@ import { } from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {THREES, ONES} from "../../../lib/LibCommonResults.sol"; +import {console2} from "forge-std/console2.sol"; + contract LibDecimalFloatImplementationDivTest is Test { function checkDiv( int256 signedCoefficientA, @@ -19,6 +21,7 @@ contract LibDecimalFloatImplementationDivTest is Test { int256 signedCoefficientC, int256 exponentC ) internal pure { + console2.log("checkDiv"); (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.div(signedCoefficientA, exponentA, signedCoefficientB, exponentB); assertEq(signedCoefficient, signedCoefficientC, "coefficient"); @@ -100,6 +103,22 @@ contract LibDecimalFloatImplementationDivTest is Test { assertEq(exponent, -76); } + /// 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) = + LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + + int256 one = 1; + for (int256 oneExponent = 0; oneExponent >= -76; --oneExponent) { + checkDiv(signedCoefficient, exponent, one, oneExponent, expectedCoefficient, expectedExponent); + if (oneExponent == -76) { + break; + } + one *= 10; + } + } + /// forge-config: default.fuzz.runs = 100 function testUnnormalizedThreesDiv0(int256 exponentA, int256 exponentB) external pure { exponentA = bound(exponentA, EXPONENT_MIN / 2, EXPONENT_MAX / 2); From 4398d0b0593769f101932e614c45745d509e2a5c Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 25 Aug 2025 21:43:33 +0400 Subject: [PATCH 2/9] div by one --- test/src/lib/LibDecimalFloat.div.t.sol | 15 +++++++++++++++ .../LibDecimalFloatImplementation.div.t.sol | 3 --- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/test/src/lib/LibDecimalFloat.div.t.sol b/test/src/lib/LibDecimalFloat.div.t.sol index c841e0f5..8e951995 100644 --- a/test/src/lib/LibDecimalFloat.div.t.sol +++ b/test/src/lib/LibDecimalFloat.div.t.sol @@ -7,6 +7,8 @@ import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFl import {Test} from "forge-std/Test.sol"; +import {console2} from "forge-std/console2.sol"; + contract LibDecimalFloatDivTest is Test { using LibDecimalFloat for Float; @@ -40,4 +42,17 @@ contract LibDecimalFloatDivTest is Test { this.divExternal(a, b); } } + + function testDivByOneFloat(Float float) external pure { + int256 one = 1; + for (int256 oneExponent = 0; oneExponent >= -65; --oneExponent) { + console2.logInt(one); + console2.logInt(oneExponent); + LibDecimalFloat.div(float, LibDecimalFloat.packLossless(one, oneExponent)); + if (oneExponent == -65) { + break; + } + one *= 10; + } + } } diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol index 64622c4a..de4b0936 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol @@ -10,8 +10,6 @@ import { } from "src/lib/implementation/LibDecimalFloatImplementation.sol"; import {THREES, ONES} from "../../../lib/LibCommonResults.sol"; -import {console2} from "forge-std/console2.sol"; - contract LibDecimalFloatImplementationDivTest is Test { function checkDiv( int256 signedCoefficientA, @@ -21,7 +19,6 @@ contract LibDecimalFloatImplementationDivTest is Test { int256 signedCoefficientC, int256 exponentC ) internal pure { - console2.log("checkDiv"); (int256 signedCoefficient, int256 exponent) = LibDecimalFloatImplementation.div(signedCoefficientA, exponentA, signedCoefficientB, exponentB); assertEq(signedCoefficient, signedCoefficientC, "coefficient"); From 0e5fa7c54206913bb0e891d830ee5fc9414b8eb8 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 25 Aug 2025 21:57:52 +0400 Subject: [PATCH 3/9] lint --- test/src/lib/LibDecimalFloat.div.t.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/src/lib/LibDecimalFloat.div.t.sol b/test/src/lib/LibDecimalFloat.div.t.sol index 8e951995..f3294b66 100644 --- a/test/src/lib/LibDecimalFloat.div.t.sol +++ b/test/src/lib/LibDecimalFloat.div.t.sol @@ -7,8 +7,6 @@ import {LibDecimalFloatImplementation} from "src/lib/implementation/LibDecimalFl import {Test} from "forge-std/Test.sol"; -import {console2} from "forge-std/console2.sol"; - contract LibDecimalFloatDivTest is Test { using LibDecimalFloat for Float; @@ -46,8 +44,6 @@ contract LibDecimalFloatDivTest is Test { function testDivByOneFloat(Float float) external pure { int256 one = 1; for (int256 oneExponent = 0; oneExponent >= -65; --oneExponent) { - console2.logInt(one); - console2.logInt(oneExponent); LibDecimalFloat.div(float, LibDecimalFloat.packLossless(one, oneExponent)); if (oneExponent == -65) { break; From 64f24ff3aac73cb06ff5276f9129a623adba2776 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 26 Aug 2025 20:40:46 +0400 Subject: [PATCH 4/9] fix test --- test/src/lib/LibDecimalFloat.div.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/src/lib/LibDecimalFloat.div.t.sol b/test/src/lib/LibDecimalFloat.div.t.sol index f3294b66..0631b338 100644 --- a/test/src/lib/LibDecimalFloat.div.t.sol +++ b/test/src/lib/LibDecimalFloat.div.t.sol @@ -41,7 +41,9 @@ contract LibDecimalFloatDivTest is Test { } } - function testDivByOneFloat(Float float) external pure { + function testDivByOneFloat(int224 signedCoefficient, int32 exponent) external pure { + exponent = int32(bound(exponent, int256(type(int32).min) + 65, int256(type(int32).max))); + Float float = LibDecimalFloat.packLossless(signedCoefficient, exponent); int256 one = 1; for (int256 oneExponent = 0; oneExponent >= -65; --oneExponent) { LibDecimalFloat.div(float, LibDecimalFloat.packLossless(one, oneExponent)); From 3b1ab83e06700d4e4a653fd766ca98cc5f70844d Mon Sep 17 00:00:00 2001 From: David Meister Date: Tue, 26 Aug 2025 20:48:59 +0400 Subject: [PATCH 5/9] Update src/lib/implementation/LibDecimalFloatImplementation.sol Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../implementation/LibDecimalFloatImplementation.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/implementation/LibDecimalFloatImplementation.sol b/src/lib/implementation/LibDecimalFloatImplementation.sol index 65cca269..ff0a3f8b 100644 --- a/src/lib/implementation/LibDecimalFloatImplementation.sol +++ b/src/lib/implementation/LibDecimalFloatImplementation.sol @@ -280,11 +280,11 @@ library LibDecimalFloatImplementation { scale = 1e75; adjustExponent = 75; } - - // The order of subtraction matters in edge cases. The adjust - // exponent should move the calculation towards 0 before exponentB - // is applied. - if (exponentA > 0) { + // 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; From 836898d0960d3a87a3c9269700c03a82bc4c998e Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 26 Aug 2025 20:58:48 +0400 Subject: [PATCH 6/9] add div neg one test --- test/src/lib/LibDecimalFloat.div.t.sol | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/src/lib/LibDecimalFloat.div.t.sol b/test/src/lib/LibDecimalFloat.div.t.sol index 0631b338..57f27f72 100644 --- a/test/src/lib/LibDecimalFloat.div.t.sol +++ b/test/src/lib/LibDecimalFloat.div.t.sol @@ -46,11 +46,26 @@ contract LibDecimalFloatDivTest is Test { Float float = LibDecimalFloat.packLossless(signedCoefficient, exponent); int256 one = 1; for (int256 oneExponent = 0; oneExponent >= -65; --oneExponent) { - LibDecimalFloat.div(float, LibDecimalFloat.packLossless(one, oneExponent)); + Float result = LibDecimalFloat.div(float, LibDecimalFloat.packLossless(one, oneExponent)); + assertTrue(result.eq(float)); if (oneExponent == -65) { break; } one *= 10; } } + + function testDivByNegativeOneFloat(int224 signedCoefficient, int32 exponent) external pure { + exponent = int32(bound(exponent, int256(type(int32).min) + 65, int256(type(int32).max - 1))); + Float float = LibDecimalFloat.packLossless(signedCoefficient, exponent); + int256 negativeOne = -1; + for (int256 oneExponent = 0; oneExponent >= -65; --oneExponent) { + Float result = LibDecimalFloat.div(float, LibDecimalFloat.packLossless(negativeOne, oneExponent)); + assertTrue(result.eq(float.minus())); + if (oneExponent == -65) { + break; + } + negativeOne *= 10; + } + } } From 18c7886ab16d697fdbbc065a6121edb8a099a1cd Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 26 Aug 2025 21:02:52 +0400 Subject: [PATCH 7/9] more test --- .../LibDecimalFloatImplementation.div.t.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol index de4b0936..855bc041 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol @@ -116,6 +116,23 @@ 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) = + LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); + (expectedCoefficient, expectedExponent) = + LibDecimalFloatImplementation.minus(expectedCoefficient, expectedExponent); + + int256 negativeOne = -1; + for (int256 oneExponent = 0; oneExponent >= -76; --oneExponent) { + checkDiv(signedCoefficient, exponent, negativeOne, oneExponent, expectedCoefficient, expectedExponent); + if (oneExponent == -76) { + break; + } + negativeOne *= 10; + } + } + /// forge-config: default.fuzz.runs = 100 function testUnnormalizedThreesDiv0(int256 exponentA, int256 exponentB) external pure { exponentA = bound(exponentA, EXPONENT_MIN / 2, EXPONENT_MAX / 2); From 63310e4489ea5c73c819c5addd48701d1be09626 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Wed, 27 Aug 2025 15:00:36 +0400 Subject: [PATCH 8/9] fix tests --- test/src/lib/LibDecimalFloat.div.t.sol | 2 +- .../lib/implementation/LibDecimalFloatImplementation.div.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/lib/LibDecimalFloat.div.t.sol b/test/src/lib/LibDecimalFloat.div.t.sol index 57f27f72..211a7ade 100644 --- a/test/src/lib/LibDecimalFloat.div.t.sol +++ b/test/src/lib/LibDecimalFloat.div.t.sol @@ -56,7 +56,7 @@ contract LibDecimalFloatDivTest is Test { } function testDivByNegativeOneFloat(int224 signedCoefficient, int32 exponent) external pure { - exponent = int32(bound(exponent, int256(type(int32).min) + 65, int256(type(int32).max - 1))); + exponent = int32(bound(exponent, int256(type(int32).min) + 65, int256(type(int32).max) - 65)); Float float = LibDecimalFloat.packLossless(signedCoefficient, exponent); int256 negativeOne = -1; for (int256 oneExponent = 0; oneExponent >= -65; --oneExponent) { diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol index 855bc041..079e79dd 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol @@ -117,7 +117,7 @@ contract LibDecimalFloatImplementationDivTest is Test { } function testDivByNegativeOneFloat(int256 signedCoefficient, int256 exponent) external pure { - exponent = bound(exponent, type(int256).min + 76, type(int256).max - 1); + exponent = bound(exponent, type(int256).min + 77, type(int256).max - 1); (int256 expectedCoefficient, int256 expectedExponent) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); (expectedCoefficient, expectedExponent) = From 4540187e0d2382ba9a59b47b878c9cce3f527d21 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Wed, 27 Aug 2025 17:27:18 +0400 Subject: [PATCH 9/9] fix float bounds --- .../lib/implementation/LibDecimalFloatImplementation.div.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol index 079e79dd..eebc800a 100644 --- a/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol +++ b/test/src/lib/implementation/LibDecimalFloatImplementation.div.t.sol @@ -117,7 +117,7 @@ contract LibDecimalFloatImplementationDivTest is Test { } function testDivByNegativeOneFloat(int256 signedCoefficient, int256 exponent) external pure { - exponent = bound(exponent, type(int256).min + 77, type(int256).max - 1); + exponent = bound(exponent, type(int256).min + 76, type(int256).max); (int256 expectedCoefficient, int256 expectedExponent) = LibDecimalFloatImplementation.maximize(signedCoefficient, exponent); (expectedCoefficient, expectedExponent) =