From 19d6197247f25b7523315a7a23afe92cef834d46 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 29 Jul 2025 21:14:26 +0200 Subject: [PATCH 1/4] pow 0 --- src/lib/LibDecimalFloat.sol | 5 +++++ test/src/lib/LibDecimalFloat.pow.t.sol | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 7fb83e8a..73b09d46 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -640,6 +640,11 @@ library LibDecimalFloat { /// logarithm tables. function pow(Float a, Float b, address tablesDataContract) internal view returns (Float) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); + if (signedCoefficientA == 0) { + // If a is zero, then a^b is always zero, regardless of b. + // This is a special case because log10(0) is undefined. + return Float.wrap(0); + } (int256 signedCoefficientC, int256 exponentC) = LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficientA, exponentA); diff --git a/test/src/lib/LibDecimalFloat.pow.t.sol b/test/src/lib/LibDecimalFloat.pow.t.sol index a4fed971..d7ad735e 100644 --- a/test/src/lib/LibDecimalFloat.pow.t.sol +++ b/test/src/lib/LibDecimalFloat.pow.t.sol @@ -44,6 +44,14 @@ contract LibDecimalFloatPowTest is LogTest { checkPow(1785215562, 0, 18, 0, 3388, 163); } + function testPowZero(int32 exponentA, Float b) external { + // If a is zero then the result is always zero. + Float a = LibDecimalFloat.packLossless(0, exponentA); + address tables = logTables(); + Float c = a.pow(b, tables); + assertTrue(c.isZero(), "c is not zero"); + } + function checkRoundTrip(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal { From 415290abc183d8502e8d52e6d951075ab95bf7f2 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 29 Jul 2025 21:34:42 +0200 Subject: [PATCH 2/4] handle more pow edge cases --- src/error/ErrDecimalFloat.sol | 3 +++ src/lib/LibDecimalFloat.sol | 16 +++++++++++++--- test/src/lib/LibDecimalFloat.pow.t.sol | 14 +++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/error/ErrDecimalFloat.sol b/src/error/ErrDecimalFloat.sol index 60f814b9..187810fc 100644 --- a/src/error/ErrDecimalFloat.sol +++ b/src/error/ErrDecimalFloat.sol @@ -25,3 +25,6 @@ error LossyConversionToFloat(int256 signedCoefficient, int256 exponent); /// @dev Thrown when converting a float to some value when the conversion /// is lossy. error LossyConversionFromFloat(int256 signedCoefficient, int256 exponent); + +/// @dev Thrown when attempting to exponentiate a negative float. +error NegativeFloatExponentiation(int256 signedCoefficient, int256 exponent); diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 73b09d46..92846a4a 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -14,7 +14,8 @@ import { Log10Zero, NegativeFixedDecimalConversion, LossyConversionFromFloat, - LossyConversionToFloat + LossyConversionToFloat, + NegativeFloatExponentiation } from "../error/ErrDecimalFloat.sol"; import { LibDecimalFloatImplementation, @@ -89,6 +90,10 @@ library LibDecimalFloat { address constant LOG_TABLES_ADDRESS = 0x295180b25A5059a2e7eC64272ba4F85047B4146A; + Float constant FLOAT_ZERO = Float.wrap(0); + + Float constant FLOAT_ONE = Float.wrap(bytes32(uint256(1))); + /// type(int224).max, type(int32).max Float constant FLOAT_MAX_VALUE = Float.wrap(bytes32(uint256(0x7fffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffff))); @@ -640,10 +645,15 @@ library LibDecimalFloat { /// logarithm tables. function pow(Float a, Float b, address tablesDataContract) internal view returns (Float) { (int256 signedCoefficientA, int256 exponentA) = a.unpack(); - if (signedCoefficientA == 0) { + if (b.isZero()) { + return FLOAT_ONE; + } else if (signedCoefficientA == 0) { // If a is zero, then a^b is always zero, regardless of b. // This is a special case because log10(0) is undefined. - return Float.wrap(0); + return FLOAT_ZERO; + } else if (signedCoefficientA < 0) { + // If a is negative, then we can't take the logarithm, so we revert. + revert NegativeFloatExponentiation(signedCoefficientA, exponentA); } (int256 signedCoefficientC, int256 exponentC) = diff --git a/test/src/lib/LibDecimalFloat.pow.t.sol b/test/src/lib/LibDecimalFloat.pow.t.sol index d7ad735e..c942907b 100644 --- a/test/src/lib/LibDecimalFloat.pow.t.sol +++ b/test/src/lib/LibDecimalFloat.pow.t.sol @@ -44,7 +44,19 @@ contract LibDecimalFloatPowTest is LogTest { checkPow(1785215562, 0, 18, 0, 3388, 163); } - function testPowZero(int32 exponentA, Float b) external { + /// a^0 = 1 for all a including 0^0. + function testPowBZero(Float a, int32 exponentB) external { + Float b = LibDecimalFloat.packLossless(0, exponentB); + // If b is zero then the result is always 1. + address tables = logTables(); + Float c = a.pow(b, tables); + assertTrue(c.eq(LibDecimalFloat.packLossless(1, 0)), "c is not 1"); + } + + /// 0^b is defined as 0 for all b != 0. + function testPowAZero(int32 exponentA, Float b) external { + // 0^0 is defined as 1. + vm.assume(!b.isZero()); // If a is zero then the result is always zero. Float a = LibDecimalFloat.packLossless(0, exponentA); address tables = logTables(); From 7f679145eb263570fccb5ec2b6ab511d8d769333 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 29 Jul 2025 21:52:47 +0200 Subject: [PATCH 3/4] fix pow edge cases --- src/error/ErrDecimalFloat.sol | 6 ++++-- src/lib/LibDecimalFloat.sol | 10 ++++++---- test/src/lib/LibDecimalFloat.pow.t.sol | 13 ++++++++++--- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/error/ErrDecimalFloat.sol b/src/error/ErrDecimalFloat.sol index 187810fc..eef6de0a 100644 --- a/src/error/ErrDecimalFloat.sol +++ b/src/error/ErrDecimalFloat.sol @@ -2,6 +2,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 thedavidmeister pragma solidity ^0.8.25; +import {Float} from "../lib/LibDecimalFloat.sol"; + /// @dev Thrown when a coefficient overflows. error CoefficientOverflow(int256 signedCoefficient, int256 exponent); @@ -26,5 +28,5 @@ error LossyConversionToFloat(int256 signedCoefficient, int256 exponent); /// is lossy. error LossyConversionFromFloat(int256 signedCoefficient, int256 exponent); -/// @dev Thrown when attempting to exponentiate a negative float. -error NegativeFloatExponentiation(int256 signedCoefficient, int256 exponent); +/// @dev Thrown when attempting to exponentiate 0^b where b is negative. +error ZeroNegativePower(Float b); diff --git a/src/lib/LibDecimalFloat.sol b/src/lib/LibDecimalFloat.sol index 92846a4a..812655f8 100644 --- a/src/lib/LibDecimalFloat.sol +++ b/src/lib/LibDecimalFloat.sol @@ -15,7 +15,7 @@ import { NegativeFixedDecimalConversion, LossyConversionFromFloat, LossyConversionToFloat, - NegativeFloatExponentiation + ZeroNegativePower } from "../error/ErrDecimalFloat.sol"; import { LibDecimalFloatImplementation, @@ -648,12 +648,14 @@ library LibDecimalFloat { if (b.isZero()) { return FLOAT_ONE; } else if (signedCoefficientA == 0) { + if (b.lt(FLOAT_ZERO)) { + // If b is negative, and a is 0, so we revert. + revert ZeroNegativePower(b); + } + // If a is zero, then a^b is always zero, regardless of b. // This is a special case because log10(0) is undefined. return FLOAT_ZERO; - } else if (signedCoefficientA < 0) { - // If a is negative, then we can't take the logarithm, so we revert. - revert NegativeFloatExponentiation(signedCoefficientA, exponentA); } (int256 signedCoefficientC, int256 exponentC) = diff --git a/test/src/lib/LibDecimalFloat.pow.t.sol b/test/src/lib/LibDecimalFloat.pow.t.sol index c942907b..76da2cd5 100644 --- a/test/src/lib/LibDecimalFloat.pow.t.sol +++ b/test/src/lib/LibDecimalFloat.pow.t.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.25; import {LogTest} from "../../abstract/LogTest.sol"; import {LibDecimalFloat, Float} from "src/lib/LibDecimalFloat.sol"; - +import {ZeroNegativePower} from "src/error/ErrDecimalFloat.sol"; import {console2} from "forge-std/Test.sol"; contract LibDecimalFloatPowTest is LogTest { @@ -53,10 +53,10 @@ contract LibDecimalFloatPowTest is LogTest { assertTrue(c.eq(LibDecimalFloat.packLossless(1, 0)), "c is not 1"); } - /// 0^b is defined as 0 for all b != 0. + /// 0^b is defined as 0 for all b > 0. function testPowAZero(int32 exponentA, Float b) external { // 0^0 is defined as 1. - vm.assume(!b.isZero()); + vm.assume(b.gt(LibDecimalFloat.FLOAT_ZERO)); // If a is zero then the result is always zero. Float a = LibDecimalFloat.packLossless(0, exponentA); address tables = logTables(); @@ -64,6 +64,13 @@ contract LibDecimalFloatPowTest is LogTest { assertTrue(c.isZero(), "c is not zero"); } + /// 0^a is error for all a < 0. + function testPowAZeroNegative(Float b) external { + vm.assume(b.lt(LibDecimalFloat.FLOAT_ZERO)); + vm.expectRevert(abi.encodeWithSelector(ZeroNegativePower.selector, b)); + this.powExternal(LibDecimalFloat.FLOAT_ZERO, b); + } + function checkRoundTrip(int256 signedCoefficientA, int256 exponentA, int256 signedCoefficientB, int256 exponentB) internal { From 2fbc49eb51f662370dddcfe87d4f275cd26a747a Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 29 Jul 2025 21:56:17 +0200 Subject: [PATCH 4/4] test zero one --- test/src/lib/LibDecimalFloat.constants.t.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/src/lib/LibDecimalFloat.constants.t.sol b/test/src/lib/LibDecimalFloat.constants.t.sol index 0813d690..cff9c063 100644 --- a/test/src/lib/LibDecimalFloat.constants.t.sol +++ b/test/src/lib/LibDecimalFloat.constants.t.sol @@ -26,4 +26,16 @@ contract LibDecimalFloatConstantsTest is Test { ); assertEq(Float.unwrap(e), Float.unwrap(expected)); } + + function testFloatZero() external pure { + Float zero = LibDecimalFloat.FLOAT_ZERO; + Float expected = LibDecimalFloat.packLossless(0, 0); + assertEq(Float.unwrap(zero), Float.unwrap(expected)); + } + + function testFloatOne() external pure { + Float one = LibDecimalFloat.FLOAT_ONE; + Float expected = LibDecimalFloat.packLossless(1, 0); + assertEq(Float.unwrap(one), Float.unwrap(expected)); + } }