Skip to content

Commit

Permalink
1439 bug FixedPointNumber class power function (#1443)
Browse files Browse the repository at this point in the history
* fix: 1439 pow fixed

* fix: 1439 pow fixed

* fix: 1439 pow fixed

* fix: 1439 pow fixed
  • Loading branch information
lucanicoladebiasi authored Oct 28, 2024
1 parent 8c58520 commit f185f4b
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 126 deletions.
3 changes: 2 additions & 1 deletion docs/diagrams/architecture/vcdm.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ classDiagram
+bigint scaledValue
+FixedPointNumber NaN$
+FixedPointNumber NEGATIVE_INFINITY$
+FixedPointNumber ONE$
+FixedPointNumber POSITIVE_INFINITY$
+FixedPointNumber ZERO$
+FixedPointNumber abs()
Expand Down Expand Up @@ -96,7 +97,7 @@ classDiagram
+FixedPointNumber minus(FixedPointNumber that)
+FixedPointNumber modulo(FixedPointNumber that)
+FixedPointNumber negated()
+FixedPointNumber of(bigint|number|string exp)$
+FixedPointNumber of(bigint|number|string|FixedPointNumber exp)$
+FixedPointNumber plus(FixedPointNumber that)
+FixedPointNumber pow(FixedPointNumber that)
+FixedPointNumber sqrt()
Expand Down
84 changes: 37 additions & 47 deletions packages/core/src/vcdm/FixedPointNumber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class FixedPointNumber implements VeChainDataModel<FixedPointNumber> {
Number.NEGATIVE_INFINITY
);

/**
* Represents the one constant.
*/
public static readonly ONE = FixedPointNumber.of(1n);

/**
* The positive Infinite value.
*
Expand Down Expand Up @@ -306,6 +311,7 @@ class FixedPointNumber implements VeChainDataModel<FixedPointNumber> {
let fd = this.fractionalDigits;
let sv = this.scaledValue;
if (dp > fd) {
// Scale up.
sv *= FixedPointNumber.BASE ** (dp - fd);
fd = dp;
} else {
Expand Down Expand Up @@ -773,10 +779,17 @@ class FixedPointNumber implements VeChainDataModel<FixedPointNumber> {
* @throws {InvalidDataType} If `exp` is not a numeric expression.
*/
public static of(
exp: bigint | number | string,
exp: bigint | number | string | FixedPointNumber,
decimalPlaces: bigint = this.DEFAULT_FRACTIONAL_DECIMALS
): FixedPointNumber {
try {
if (exp instanceof FixedPointNumber) {
return new FixedPointNumber(
exp.fractionalDigits,
exp.scaledValue,
exp.edgeFlag
);
}
if (Number.isNaN(exp))
return new FixedPointNumber(decimalPlaces, 0n, Number.NaN);
if (exp === Number.NEGATIVE_INFINITY)
Expand Down Expand Up @@ -843,6 +856,10 @@ class FixedPointNumber implements VeChainDataModel<FixedPointNumber> {
/**
* Returns a FixedPointNumber whose value is the value of this FixedPointNumber raised to the power of `that` FixedPointNumber.
*
* This method implements the
* [Exponentiation by Squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring)
* algorithm.
*
* Limit cases
* * NaN ^ e = NaN
* * b ^ NaN = NaN
Expand All @@ -853,67 +870,40 @@ class FixedPointNumber implements VeChainDataModel<FixedPointNumber> {
* * ±Infinite ^ +e = +Infinite
*
* @param {FixedPointNumber} that - The exponent as a fixed-point number.
* It can be negative, it can be not an integer value
* ([bignumber.js pow](https://mikemcl.github.io/bignumber.js/#pow)
* doesn't support not integer exponents).
* truncated to its integer component because **Exponentiation by Squaring** is not valid for rational exponents.
* @return {FixedPointNumber} - The result of raising this fixed-point number to the power of the given exponent.
*
* @remarks The precision is the greater of the precision of the two operands.
* @remarks In fixed-precision math, the comparisons between powers of operands having different fractional
* precision can lead to differences.
*
* @see [bignumber.js exponentiatedBy](https://mikemcl.github.io/bignumber.js/#pow)
*/
public pow(that: FixedPointNumber): FixedPointNumber {
// Limit cases
if (this.isNaN() || that.isNaN()) return FixedPointNumber.NaN;
if (this.isInfinite())
return that.isZero()
? FixedPointNumber.of(1)
? FixedPointNumber.ONE
: that.isNegative()
? FixedPointNumber.ZERO
: FixedPointNumber.POSITIVE_INFINITY;
if (that.isNegativeInfinite()) return FixedPointNumber.ZERO;
if (that.isPositiveInfinite())
if (that.isPositiveInfinite()) {
return FixedPointNumber.POSITIVE_INFINITY;
const fd = this.maxFractionalDigits(that, this.fractionalDigits); // Max common fractional decimals.
return new FixedPointNumber(
fd,
FixedPointNumber.pow(
fd,
this.dp(fd).scaledValue,
that.dp(fd).scaledValue
)
).dp(this.fractionalDigits); // Minimize fractional decimals without precision loss.
}

/**
* Computes the power of a given base raised to a specified exponent.
*
* @param {bigint} fd - The scale factor for decimal precision.
* @param {bigint} base - The base number to be raised to the power.
* @param {bigint} exponent - The exponent to which the base should be raised.
* @return {bigint} The result of base raised to the power of exponent, scaled by the scale factor.
*/
private static pow(fd: bigint, base: bigint, exponent: bigint): bigint {
const sf = FixedPointNumber.BASE ** fd; // Scale factor.
if (exponent < 0n) {
return FixedPointNumber.pow(
fd,
FixedPointNumber.div(fd, sf, base),
-exponent
); // Recursive.
}
if (exponent === 0n) {
return 1n * sf;
}
if (exponent === sf) {
return base;
if (that.isZero()) return FixedPointNumber.ONE;
// Exponentiation by squaring works for natural exponent value.
let exponent = that.abs().bi;
let base = FixedPointNumber.of(this);
let result = FixedPointNumber.ONE;
while (exponent > 0n) {
// If the exponent is odd, multiply the result by the current base.
if (exponent % 2n === 1n) {
result = result.times(base);
}
// Square the base and halve the exponent.
base = base.times(base);
exponent = exponent / 2n;
}
return FixedPointNumber.pow(
fd,
this.mul(base, base, fd),
exponent - sf
); // Recursive.
// If exponent is negative, convert the problem to positive exponent.
return that.isNegative() ? FixedPointNumber.ONE.div(result) : result;
}

/**
Expand Down
Loading

1 comment on commit f185f4b

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage

Summary

Lines Statements Branches Functions
Coverage: 99%
99.05% (4320/4361) 97.69% (1398/1431) 99.11% (894/902)
Title Tests Skipped Failures Errors Time
core 808 0 💤 0 ❌ 0 🔥 2m 11s ⏱️
network 735 0 💤 0 ❌ 0 🔥 4m 49s ⏱️
errors 42 0 💤 0 ❌ 0 🔥 15.895s ⏱️

Please sign in to comment.