UNPKG

@formatjs/ecma402-abstract

Version:

A collection of implementation for ECMAScript abstract operations

209 lines (208 loc) 6.76 kB
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 }; }