Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
209 lines
6.6 KiB
Plaintext
209 lines
6.6 KiB
Plaintext
import { ZERO } from "../constants.js";
|
|
import "../types/number.js";
|
|
import { invariant, repeat } from "../utils.js";
|
|
import { ApplyUnsignedRoundingMode } from "./ApplyUnsignedRoundingMode.js";
|
|
import { getPowerOf10 } from "./decimal-cache.js";
|
|
//IMPL: Helper function to find n1, e1, and r1 using direct calculation
|
|
function findN1E1R1(x, p) {
|
|
const maxN1 = getPowerOf10(p);
|
|
const minN1 = getPowerOf10(p - 1);
|
|
// Direct calculation: compute e1 from logarithm
|
|
// e1 is the exponent such that n1 * 10^(e1-p+1) <= x
|
|
// Taking log: log(n1) + (e1-p+1)*log(10) <= log(x)
|
|
// Since n1 is between 10^(p-1) and 10^p, we have:
|
|
// (p-1) + (e1-p+1) <= log10(x) < p + (e1-p+1)
|
|
// Simplifying: e1 <= log10(x) < e1 + 1
|
|
// Therefore: e1 = floor(log10(x))
|
|
const log10x = x.log(10);
|
|
let e1 = log10x.floor();
|
|
// Calculate n1 and r1 from e1
|
|
const divisor = getPowerOf10(e1.minus(p).plus(1));
|
|
let n1 = x.div(divisor).floor();
|
|
let r1 = n1.times(divisor);
|
|
// Verify and adjust if n1 is out of bounds
|
|
// This handles edge cases near powers of 10
|
|
if (n1.greaterThanOrEqualTo(maxN1)) {
|
|
e1 = e1.plus(1);
|
|
const newDivisor = getPowerOf10(e1.minus(p).plus(1));
|
|
n1 = x.div(newDivisor).floor();
|
|
r1 = n1.times(newDivisor);
|
|
} else if (n1.lessThan(minN1)) {
|
|
e1 = e1.minus(1);
|
|
const newDivisor = getPowerOf10(e1.minus(p).plus(1));
|
|
n1 = x.div(newDivisor).floor();
|
|
r1 = n1.times(newDivisor);
|
|
}
|
|
// Final verification with fallback to iterative search if needed
|
|
if (r1.lessThanOrEqualTo(x) && n1.lessThan(maxN1) && n1.greaterThanOrEqualTo(minN1)) {
|
|
return {
|
|
n1,
|
|
e1,
|
|
r1
|
|
};
|
|
}
|
|
// Fallback: iterative search (should rarely be needed)
|
|
const maxE1 = x.div(minN1).log(10).plus(p).minus(1).ceil();
|
|
let currentE1 = maxE1;
|
|
while (true) {
|
|
const currentDivisor = getPowerOf10(currentE1.minus(p).plus(1));
|
|
let currentN1 = x.div(currentDivisor).floor();
|
|
if (currentN1.lessThan(maxN1) && currentN1.greaterThanOrEqualTo(minN1)) {
|
|
const currentR1 = currentN1.times(currentDivisor);
|
|
if (currentR1.lessThanOrEqualTo(x)) {
|
|
return {
|
|
n1: currentN1,
|
|
e1: currentE1,
|
|
r1: currentR1
|
|
};
|
|
}
|
|
}
|
|
currentE1 = currentE1.minus(1);
|
|
}
|
|
}
|
|
//IMPL: Helper function to find n2, e2, and r2 using direct calculation
|
|
function findN2E2R2(x, p) {
|
|
const maxN2 = getPowerOf10(p);
|
|
const minN2 = getPowerOf10(p - 1);
|
|
// Direct calculation: similar to findN1E1R1 but with ceiling
|
|
const log10x = x.log(10);
|
|
let e2 = log10x.floor();
|
|
// Calculate n2 and r2 from e2
|
|
const divisor = getPowerOf10(e2.minus(p).plus(1));
|
|
let n2 = x.div(divisor).ceil();
|
|
let r2 = n2.times(divisor);
|
|
// Verify and adjust if n2 is out of bounds
|
|
if (n2.greaterThanOrEqualTo(maxN2)) {
|
|
e2 = e2.plus(1);
|
|
const newDivisor = getPowerOf10(e2.minus(p).plus(1));
|
|
n2 = x.div(newDivisor).ceil();
|
|
r2 = n2.times(newDivisor);
|
|
} else if (n2.lessThan(minN2)) {
|
|
e2 = e2.minus(1);
|
|
const newDivisor = getPowerOf10(e2.minus(p).plus(1));
|
|
n2 = x.div(newDivisor).ceil();
|
|
r2 = n2.times(newDivisor);
|
|
}
|
|
// Final verification with fallback to iterative search if needed
|
|
if (r2.greaterThanOrEqualTo(x) && n2.lessThan(maxN2) && n2.greaterThanOrEqualTo(minN2)) {
|
|
return {
|
|
n2,
|
|
e2,
|
|
r2
|
|
};
|
|
}
|
|
// Fallback: iterative search (should rarely be needed)
|
|
const minE2 = x.div(maxN2).log(10).plus(p).minus(1).floor();
|
|
let currentE2 = minE2;
|
|
while (true) {
|
|
const currentDivisor = getPowerOf10(currentE2.minus(p).plus(1));
|
|
let currentN2 = x.div(currentDivisor).ceil();
|
|
if (currentN2.lessThan(maxN2) && currentN2.greaterThanOrEqualTo(minN2)) {
|
|
const currentR2 = currentN2.times(currentDivisor);
|
|
if (currentR2.greaterThanOrEqualTo(x)) {
|
|
return {
|
|
n2: currentN2,
|
|
e2: currentE2,
|
|
r2: currentR2
|
|
};
|
|
}
|
|
}
|
|
currentE2 = currentE2.plus(1);
|
|
}
|
|
}
|
|
/**
|
|
* https://tc39.es/ecma402/#sec-torawprecision
|
|
* @param x a finite non-negative Number or BigInt
|
|
* @param minPrecision an integer between 1 and 21
|
|
* @param maxPrecision an integer between 1 and 21
|
|
*/
|
|
export function ToRawPrecision(x, minPrecision, maxPrecision, unsignedRoundingMode) {
|
|
// 1. Let p be maxPrecision.
|
|
const p = maxPrecision;
|
|
let m;
|
|
let e;
|
|
let xFinal;
|
|
// 2. If x = 0, then
|
|
if (x.isZero()) {
|
|
// a. Let m be the String value consisting of p occurrences of the character "0".
|
|
m = repeat("0", p);
|
|
// b. Let e be 0.
|
|
e = 0;
|
|
// c. Let xFinal be 0.
|
|
xFinal = ZERO;
|
|
} else {
|
|
// 3. Else,
|
|
// a. Let {n1, e1, r1} be the result of findN1E1R1(x, p).
|
|
const { n1, e1, r1 } = findN1E1R1(x, p);
|
|
// b. Let {n2, e2, r2} be the result of findN2E2R2(x, p).
|
|
const { n2, e2, r2 } = findN2E2R2(x, p);
|
|
// c. Let r be ApplyUnsignedRoundingMode(x, r1, r2, unsignedRoundingMode).
|
|
let r = ApplyUnsignedRoundingMode(x, r1, r2, unsignedRoundingMode);
|
|
let n;
|
|
// d. If r = r1, then
|
|
if (r.eq(r1)) {
|
|
// i. Let n be n1.
|
|
n = n1;
|
|
// ii. Let e be e1.
|
|
e = e1.toNumber();
|
|
// iii. Let xFinal be r1.
|
|
xFinal = r1;
|
|
} else {
|
|
// e. Else,
|
|
// i. Let n be n2.
|
|
n = n2;
|
|
// ii. Let e be e2.
|
|
e = e2.toNumber();
|
|
// iii. Let xFinal be r2.
|
|
xFinal = r2;
|
|
}
|
|
// f. Let m be the String representation of n.
|
|
m = n.toString();
|
|
}
|
|
let int;
|
|
// 4. If e ≥ p - 1, then
|
|
if (e >= p - 1) {
|
|
// a. Let m be the string-concatenation of m and p - 1 - e occurrences of the character "0".
|
|
m = m + repeat("0", e - p + 1);
|
|
// b. Let int be e + 1.
|
|
int = e + 1;
|
|
} else if (e >= 0) {
|
|
// 5. Else if e ≥ 0, then
|
|
// a. Let m be the string-concatenation of the first e + 1 characters of m, ".", and the remaining p - (e + 1) characters of m.
|
|
m = m.slice(0, e + 1) + "." + m.slice(m.length - (p - (e + 1)));
|
|
// b. Let int be e + 1.
|
|
int = e + 1;
|
|
} else {
|
|
// 6. Else,
|
|
// a. Assert: e < 0.
|
|
invariant(e < 0, "e should be less than 0");
|
|
// b. Let m be the string-concatenation of "0.", -e - 1 occurrences of the character "0", and m.
|
|
m = "0." + repeat("0", -e - 1) + m;
|
|
// c. Let int be 1.
|
|
int = 1;
|
|
}
|
|
// 7. If m contains ".", and maxPrecision > minPrecision, then
|
|
if (m.includes(".") && maxPrecision > minPrecision) {
|
|
// a. Let cut be maxPrecision - minPrecision.
|
|
let cut = maxPrecision - minPrecision;
|
|
// b. Repeat, while cut > 0 and the last character of m is "0",
|
|
while (cut > 0 && m[m.length - 1] === "0") {
|
|
// i. Remove the last character from m.
|
|
m = m.slice(0, m.length - 1);
|
|
// ii. Decrease cut by 1.
|
|
cut--;
|
|
}
|
|
// c. If the last character of m is ".", then
|
|
if (m[m.length - 1] === ".") {
|
|
// i. Remove the last character from m.
|
|
m = m.slice(0, m.length - 1);
|
|
}
|
|
}
|
|
// 8. Return the Record { [[FormattedString]]: m, [[RoundedNumber]]: xFinal, [[IntegerDigitsCount]]: int, [[RoundingMagnitude]]: e }.
|
|
return {
|
|
formattedString: m,
|
|
roundedNumber: xFinal,
|
|
integerDigitsCount: int,
|
|
roundingMagnitude: e
|
|
};
|
|
}
|