Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
477 changes: 239 additions & 238 deletions .gas-snapshot

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/error/ErrDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ error MaximizeOverflow(int256 signedCoefficient, int256 exponent);
/// @param signedCoefficient The signed coefficient of the numerator.
/// @param exponent The exponent of the numerator.
error DivisionByZero(int256 signedCoefficient, int256 exponent);

/// @dev Thrown when attempting to exponentiate a negative base.
error PowNegativeBase(int256 signedCoefficient, int256 exponent);
7 changes: 7 additions & 0 deletions src/error/ErrFormat.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity ^0.8.25;

/// @dev Thrown when the exponent cannot be formatted.
/// @param exponent The exponent that cannot be formatted.
error UnformatableExponent(int256 exponent);
8 changes: 4 additions & 4 deletions src/generated/LogTables.pointers.sol

Large diffs are not rendered by default.

89 changes: 64 additions & 25 deletions src/lib/LibDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,16 @@
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity ^0.8.25;

import {
LOG_TABLES,
LOG_TABLES_SMALL,
LOG_TABLES_SMALL_ALT,
ANTI_LOG_TABLES,
ANTI_LOG_TABLES_SMALL
} from "../generated/LogTables.pointers.sol";
import {
ExponentOverflow,
CoefficientOverflow,
Log10Zero,
NegativeFixedDecimalConversion,
LossyConversionFromFloat,
LossyConversionToFloat,
ZeroNegativePower
ZeroNegativePower,
PowNegativeBase
} from "../error/ErrDecimalFloat.sol";
import {
LibDecimalFloatImplementation,
EXPONENT_MAX,
EXPONENT_MIN
} from "./implementation/LibDecimalFloatImplementation.sol";
import {LibDecimalFloatImplementation} from "./implementation/LibDecimalFloatImplementation.sol";

type Float is bytes32;

Expand Down Expand Up @@ -116,8 +105,12 @@ library LibDecimalFloat {
// Catch an edge case where unsigned value looks like a negative
// value when coerced.
if (value > uint256(type(int256).max)) {
// value is divided by 10 so won't truncate when cast.
// forge-lint: disable-next-line(unsafe-typecast)
return (int256(value / 10), exponent + 1, value % 10 == 0);
} else {
// case that would truncate is handled above.
// forge-lint: disable-next-line(unsafe-typecast)
return (int256(value), exponent, true);
}
}
Expand Down Expand Up @@ -191,6 +184,7 @@ library LibDecimalFloat {
return (0, true);
} else {
// Safe to do this conversion because we revert above on negative.
// forge-lint: disable-next-line(unsafe-typecast)
uint256 unsignedCoefficient = uint256(signedCoefficient);
int256 finalExponent;

Expand All @@ -216,6 +210,9 @@ library LibDecimalFloat {

// At this point, scale cannot revert, so it is safe to do
// this unchecked.
// finalExponent is negative here so making it absolute will
// always fit in uint256.
// forge-lint: disable-next-line(unsafe-typecast)
scale = 10 ** uint256(-finalExponent);
fixedDecimal = unsignedCoefficient / scale;

Expand All @@ -225,6 +222,8 @@ library LibDecimalFloat {
return (fixedDecimal, fixedDecimal * scale == unsignedCoefficient);
}
} else if (finalExponent > 0) {
// finalExponent is positive here.
// forge-lint: disable-next-line(unsafe-typecast)
scale = 10 ** uint256(finalExponent);
fixedDecimal = unsignedCoefficient * scale;
unchecked {
Expand Down Expand Up @@ -298,6 +297,10 @@ library LibDecimalFloat {
unchecked {
int256 initialSignedCoefficient = signedCoefficient;
int256 initialExponent = exponent;
// lossless is true if the signed coefficient fits in int224.
// truncation here is intentional if it happens as that is what we
// are testing for.
// forge-lint: disable-next-line(unsafe-typecast)
lossless = int224(signedCoefficient) == signedCoefficient;

// The reason that we can do unchecked exponent addition here is that
Expand All @@ -310,6 +313,9 @@ library LibDecimalFloat {
exponent += 5;
}

// truncation here is intentional if it happens as that is what we
// are testing for.
// forge-lint: disable-next-line(unsafe-typecast)
while (int224(signedCoefficient) != signedCoefficient) {
signedCoefficient /= 10;
++exponent;
Expand All @@ -320,6 +326,9 @@ library LibDecimalFloat {
}
}

// truncation here is intentional if it happens as that is what we
// are testing for.
// forge-lint: disable-next-line(unsafe-typecast)
if (int32(exponent) != exponent) {
// If the exponent is negative then this is a number too small
// to pack. We return zero but it is not a lossless conversion.
Expand Down Expand Up @@ -666,34 +675,64 @@ library LibDecimalFloat {
/// logarithm tables.
function pow(Float a, Float b, address tablesDataContract) internal view returns (Float) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();

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);
}
} else if (signedCoefficientA <= 0) {
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;
// 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 {
revert PowNegativeBase(signedCoefficientA, exponentA);
}
}
Comment thread
thedavidmeister marked this conversation as resolved.
// Handle identity case for positive values of a, i.e. a^1.
else if (b.eq(FLOAT_ONE) && a.gt(FLOAT_ZERO)) {
return a;
} else if (b.lt(FLOAT_ZERO)) {
return pow(a.inv(), b.minus(), tablesDataContract);
}

(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(int256 characteristicB, int256 mantissaB) =
LibDecimalFloatImplementation.characteristicMantissa(signedCoefficientB, exponentB);

uint256 exponentBInteger =
uint256(LibDecimalFloatImplementation.withTargetExponent(characteristicB, exponentB, 0));

// Exponentiation by squaring.
(int256 signedCoefficientResult, int256 exponentResult) = (1, 0);
(int256 signedCoefficientBase, int256 exponentBase) = a.unpack();
while (exponentBInteger >= 1) {
if (exponentBInteger & 0x01 == 0x01) {
(signedCoefficientResult, exponentResult) = LibDecimalFloatImplementation.mul(
signedCoefficientResult, exponentResult, signedCoefficientBase, exponentBase
);
}
Comment thread
thedavidmeister marked this conversation as resolved.
exponentBInteger >>= 1;
(signedCoefficientBase, exponentBase) = LibDecimalFloatImplementation.mul(
signedCoefficientBase, exponentBase, signedCoefficientBase, exponentBase
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

(int256 signedCoefficientC, int256 exponentC) =
LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficientA, exponentA);

(int256 signedCoefficientB, int256 exponentB) = b.unpack();

(signedCoefficientC, exponentC) =
LibDecimalFloatImplementation.mul(signedCoefficientC, exponentC, signedCoefficientB, exponentB);
LibDecimalFloatImplementation.mul(signedCoefficientC, exponentC, mantissaB, exponentB);

(signedCoefficientC, exponentC) =
LibDecimalFloatImplementation.pow10(tablesDataContract, signedCoefficientC, exponentC);

(signedCoefficientC, exponentC) =
LibDecimalFloatImplementation.mul(signedCoefficientC, exponentC, signedCoefficientResult, exponentResult);

(Float c, bool lossless) = packLossy(signedCoefficientC, exponentC);
// We don't care if power is lossy because it's an approximation anyway.
(lossless);
Expand Down
52 changes: 44 additions & 8 deletions src/lib/format/LibFormatDecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ pragma solidity ^0.8.25;

import {LibDecimalFloat, Float} from "../LibDecimalFloat.sol";

import {LibFixedPointDecimalFormat} from "rain.math.fixedpoint/lib/format/LibFixedPointDecimalFormat.sol";
import {LibDecimalFloatImplementation} from "../../lib/implementation/LibDecimalFloatImplementation.sol";

import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol";

import {UnformatableExponent} from "../../error/ErrFormat.sol";

library LibFormatDecimalFloat {
function countSigFigs(int256 signedCoefficient, int256 exponent) internal pure returns (uint256) {
if (signedCoefficient == 0) {
Expand All @@ -32,8 +33,12 @@ library LibFormatDecimalFloat {
// Adjust for exponent
if (exponent < 0) {
exponent = -exponent;
// exponent > 0
// forge-lint: disable-next-line(unsafe-typecast)
sigFigs = sigFigs > uint256(exponent) ? sigFigs : uint256(exponent);
} else if (exponent > 0) {
// exponent > 0
// forge-lint: disable-next-line(unsafe-typecast)
sigFigs += uint256(exponent);
}

Expand All @@ -59,24 +64,49 @@ library LibFormatDecimalFloat {
if (scientific) {
(signedCoefficient, exponent) = LibDecimalFloatImplementation.maximizeFull(signedCoefficient, exponent);

bool isAtLeastE76 = signedCoefficient / 1e76 != 0;
scaleExponent = isAtLeastE76 ? uint256(76) : uint256(75);
scale = uint256(10) ** scaleExponent;
if (signedCoefficient / 1e76 != 0) {
scaleExponent = 76;
scale = 1e76;
} else {
scaleExponent = 75;
scale = 1e75;
}
} else {
if (exponent > 0) {
// exponent > 0
// forge-lint: disable-next-line(unsafe-typecast)
signedCoefficient *= int256(10) ** uint256(exponent);
exponent = 0;
}
if (exponent < 0) {
if (exponent < -76) {
revert UnformatableExponent(exponent);
}
// negating a signed exponent will always fit in uint256.
// forge-lint: disable-next-line(unsafe-typecast)
scale = uint256(10) ** uint256(-exponent);
// negating a signed exponent will always fit in uint256.
// forge-lint: disable-next-line(unsafe-typecast)
scaleExponent = uint256(-exponent);
} else {
scaleExponent = uint256(exponent);
// exponent is zero here.
scaleExponent = 0;
}
}

int256 integral = scale != 0 ? signedCoefficient / int256(scale) : signedCoefficient;
int256 fractional = scale != 0 ? signedCoefficient % int256(scale) : int256(0);
int256 integral = signedCoefficient;
int256 fractional = int256(0);
if (scale != 0) {
// scale is one of two possible values so won't truncate when cast
// or explicitly has a guard against it truncating.
// forge-lint: disable-next-line(unsafe-typecast)
integral = signedCoefficient / int256(scale);
// scale is one of two possible values so won't truncate when cast
// or explicitly has a guard against it truncating.
// forge-lint: disable-next-line(unsafe-typecast)
fractional = signedCoefficient % int256(scale);
}

Comment thread
thedavidmeister marked this conversation as resolved.
bool isNeg = false;
if (integral < 0) {
isNeg = true;
Expand All @@ -94,6 +124,9 @@ library LibFormatDecimalFloat {
if (fractional != 0) {
uint256 fracLeadingZeros = 0;
uint256 fracScale = scale / 10;
// fracScale being 10x less than scale means it cannot overflow
// when cast to `int256`.
// forge-lint: disable-next-line(unsafe-typecast)
while (fractional / int256(fracScale) == 0) {
Comment thread
thedavidmeister marked this conversation as resolved.
fracScale /= 10;
fracLeadingZeros++;
Expand All @@ -113,7 +146,10 @@ library LibFormatDecimalFloat {
}

string memory integralString = Strings.toString(integral);

// scaleExponent comes from either hardcoded values or `exponent` which
// is an `int256` that was cast to `uint256` above, which can be cast
// back to `int256` without truncation.
// forge-lint: disable-next-line(unsafe-typecast)
int256 displayExponent = exponent + int256(scaleExponent);
string memory exponentString =
(displayExponent == 0 || !scientific) ? "" : string.concat("e", Strings.toString(displayExponent));
Expand Down
Loading