@formatjs/ecma402-abstract 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NumberFormat/ComputeExponent.js +30 -3
- package/NumberFormat/ComputeExponentForMagnitude.js +2 -1
- package/NumberFormat/PartitionNumberPattern.js +2 -2
- package/NumberFormat/ToRawFixed.js +4 -3
- package/NumberFormat/ToRawPrecision.js +74 -11
- package/NumberFormat/decimal-cache.d.ts +12 -0
- package/NumberFormat/decimal-cache.js +15 -0
- package/NumberFormat/format_to_parts.js +4 -9
- package/package.json +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Decimal } from 'decimal.js';
|
|
2
2
|
import { ComputeExponentForMagnitude } from './ComputeExponentForMagnitude.js';
|
|
3
3
|
import { FormatNumericToString } from './FormatNumericToString.js';
|
|
4
|
+
import { getPowerOf10 } from './decimal-cache.js';
|
|
4
5
|
/**
|
|
5
6
|
* The abstract operation ComputeExponent computes an exponent (power of ten) by which to scale x
|
|
6
7
|
* according to the number formatting settings. It handles cases such as 999 rounding up to 1000,
|
|
@@ -15,15 +16,41 @@ export function ComputeExponent(internalSlots, x) {
|
|
|
15
16
|
if (x.isNegative()) {
|
|
16
17
|
x = x.negated();
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
// Fast path for simple numbers
|
|
20
|
+
// If x can be represented as a safe integer, use native Math.log10
|
|
21
|
+
var xNum = x.toNumber();
|
|
22
|
+
var magnitude;
|
|
23
|
+
if (Number.isFinite(xNum) &&
|
|
24
|
+
Number.isSafeInteger(xNum) &&
|
|
25
|
+
xNum > 0 &&
|
|
26
|
+
xNum <= 999999) {
|
|
27
|
+
// Use fast native logarithm for simple positive integers
|
|
28
|
+
var magNum = Math.floor(Math.log10(xNum));
|
|
29
|
+
magnitude = new Decimal(magNum);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
magnitude = x.log(10).floor();
|
|
33
|
+
}
|
|
19
34
|
var exponent = ComputeExponentForMagnitude(internalSlots, magnitude);
|
|
20
35
|
// Preserve more precision by doing multiplication when exponent is negative.
|
|
21
|
-
x = x.times(
|
|
36
|
+
x = x.times(getPowerOf10(-exponent));
|
|
22
37
|
var formatNumberResult = FormatNumericToString(internalSlots, x);
|
|
23
38
|
if (formatNumberResult.roundedNumber.isZero()) {
|
|
24
39
|
return [exponent, magnitude.toNumber()];
|
|
25
40
|
}
|
|
26
|
-
|
|
41
|
+
// Fast path for simple rounded numbers
|
|
42
|
+
var roundedNum = formatNumberResult.roundedNumber.toNumber();
|
|
43
|
+
var newMagnitude;
|
|
44
|
+
if (Number.isFinite(roundedNum) &&
|
|
45
|
+
Number.isSafeInteger(roundedNum) &&
|
|
46
|
+
roundedNum > 0 &&
|
|
47
|
+
roundedNum <= 999999) {
|
|
48
|
+
var newMagNum = Math.floor(Math.log10(roundedNum));
|
|
49
|
+
newMagnitude = new Decimal(newMagNum);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
newMagnitude = formatNumberResult.roundedNumber.log(10).floor();
|
|
53
|
+
}
|
|
27
54
|
if (newMagnitude.eq(magnitude.minus(exponent))) {
|
|
28
55
|
return [exponent, magnitude.toNumber()];
|
|
29
56
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Decimal } from 'decimal.js';
|
|
2
2
|
import { invariant } from '../utils.js';
|
|
3
|
+
import { getPowerOf10 } from './decimal-cache.js';
|
|
3
4
|
Decimal.set({
|
|
4
5
|
toExpPos: 100,
|
|
5
6
|
});
|
|
@@ -37,7 +38,7 @@ export function ComputeExponentForMagnitude(internalSlots, magnitude) {
|
|
|
37
38
|
if (!thresholdMap) {
|
|
38
39
|
return 0;
|
|
39
40
|
}
|
|
40
|
-
var num =
|
|
41
|
+
var num = getPowerOf10(magnitude).toString();
|
|
41
42
|
var thresholds = Object.keys(thresholdMap); // TODO: this can be pre-processed
|
|
42
43
|
if (num < thresholds[0]) {
|
|
43
44
|
return 0;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Decimal } from 'decimal.js';
|
|
2
1
|
import { invariant } from '../utils.js';
|
|
3
2
|
import { ComputeExponent } from './ComputeExponent.js';
|
|
4
3
|
import formatToParts from './format_to_parts.js';
|
|
5
4
|
import { FormatNumericToString } from './FormatNumericToString.js';
|
|
5
|
+
import { getPowerOf10 } from './decimal-cache.js';
|
|
6
6
|
/**
|
|
7
7
|
* https://tc39.es/ecma402/#sec-partitionnumberpattern
|
|
8
8
|
*/
|
|
@@ -45,7 +45,7 @@ export function PartitionNumberPattern(internalSlots, _x) {
|
|
|
45
45
|
// IMPL: We need to record the magnitude of the number
|
|
46
46
|
magnitude = _a[1];
|
|
47
47
|
// 8.d. Let x be x × 10^(-exponent).
|
|
48
|
-
x = x.times(
|
|
48
|
+
x = x.times(getPowerOf10(-exponent));
|
|
49
49
|
}
|
|
50
50
|
// 8.e. Let formatNumberResult be FormatNumericToString(internalSlots, x).
|
|
51
51
|
var formatNumberResult = FormatNumericToString(internalSlots, x);
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { Decimal } from 'decimal.js';
|
|
2
2
|
import { repeat } from '../utils.js';
|
|
3
3
|
import { ApplyUnsignedRoundingMode } from './ApplyUnsignedRoundingMode.js';
|
|
4
|
+
import { getPowerOf10 } from './decimal-cache.js';
|
|
4
5
|
//IMPL: Setting Decimal configuration
|
|
5
6
|
Decimal.set({
|
|
6
7
|
toExpPos: 100,
|
|
7
8
|
});
|
|
8
9
|
//IMPL: Helper function to calculate raw fixed value
|
|
9
10
|
function ToRawFixedFn(n, f) {
|
|
10
|
-
return n.times(
|
|
11
|
+
return n.times(getPowerOf10(-f));
|
|
11
12
|
}
|
|
12
13
|
//IMPL: Helper function to find n1 and r1
|
|
13
14
|
function findN1R1(x, f, roundingIncrement) {
|
|
14
|
-
var nx = x.times(
|
|
15
|
+
var nx = x.times(getPowerOf10(f)).floor();
|
|
15
16
|
var n1 = nx.div(roundingIncrement).floor().times(roundingIncrement);
|
|
16
17
|
var r1 = ToRawFixedFn(n1, f);
|
|
17
18
|
return {
|
|
@@ -21,7 +22,7 @@ function findN1R1(x, f, roundingIncrement) {
|
|
|
21
22
|
}
|
|
22
23
|
//IMPL: Helper function to find n2 and r2
|
|
23
24
|
function findN2R2(x, f, roundingIncrement) {
|
|
24
|
-
var nx = x.times(
|
|
25
|
+
var nx = x.times(getPowerOf10(f)).ceil();
|
|
25
26
|
var n2 = nx.div(roundingIncrement).ceil().times(roundingIncrement);
|
|
26
27
|
var r2 = ToRawFixedFn(n2, f);
|
|
27
28
|
return {
|
|
@@ -1,17 +1,52 @@
|
|
|
1
|
-
import { Decimal } from 'decimal.js';
|
|
2
1
|
import { ZERO } from '../constants.js';
|
|
3
2
|
import { invariant, repeat } from '../utils.js';
|
|
4
3
|
import { ApplyUnsignedRoundingMode } from './ApplyUnsignedRoundingMode.js';
|
|
5
|
-
|
|
4
|
+
import { getPowerOf10 } from './decimal-cache.js';
|
|
5
|
+
//IMPL: Helper function to find n1, e1, and r1 using direct calculation
|
|
6
6
|
function findN1E1R1(x, p) {
|
|
7
|
-
var maxN1 =
|
|
8
|
-
var minN1 =
|
|
7
|
+
var maxN1 = getPowerOf10(p);
|
|
8
|
+
var minN1 = getPowerOf10(p - 1);
|
|
9
|
+
// Direct calculation: compute e1 from logarithm
|
|
10
|
+
// e1 is the exponent such that n1 * 10^(e1-p+1) <= x
|
|
11
|
+
// Taking log: log(n1) + (e1-p+1)*log(10) <= log(x)
|
|
12
|
+
// Since n1 is between 10^(p-1) and 10^p, we have:
|
|
13
|
+
// (p-1) + (e1-p+1) <= log10(x) < p + (e1-p+1)
|
|
14
|
+
// Simplifying: e1 <= log10(x) < e1 + 1
|
|
15
|
+
// Therefore: e1 = floor(log10(x))
|
|
16
|
+
var log10x = x.log(10);
|
|
17
|
+
var e1 = log10x.floor();
|
|
18
|
+
// Calculate n1 and r1 from e1
|
|
19
|
+
var divisor = getPowerOf10(e1.minus(p).plus(1));
|
|
20
|
+
var n1 = x.div(divisor).floor();
|
|
21
|
+
var r1 = n1.times(divisor);
|
|
22
|
+
// Verify and adjust if n1 is out of bounds
|
|
23
|
+
// This handles edge cases near powers of 10
|
|
24
|
+
if (n1.greaterThanOrEqualTo(maxN1)) {
|
|
25
|
+
e1 = e1.plus(1);
|
|
26
|
+
var newDivisor = getPowerOf10(e1.minus(p).plus(1));
|
|
27
|
+
n1 = x.div(newDivisor).floor();
|
|
28
|
+
r1 = n1.times(newDivisor);
|
|
29
|
+
}
|
|
30
|
+
else if (n1.lessThan(minN1)) {
|
|
31
|
+
e1 = e1.minus(1);
|
|
32
|
+
var newDivisor = getPowerOf10(e1.minus(p).plus(1));
|
|
33
|
+
n1 = x.div(newDivisor).floor();
|
|
34
|
+
r1 = n1.times(newDivisor);
|
|
35
|
+
}
|
|
36
|
+
// Final verification with fallback to iterative search if needed
|
|
37
|
+
if (r1.lessThanOrEqualTo(x) &&
|
|
38
|
+
n1.lessThan(maxN1) &&
|
|
39
|
+
n1.greaterThanOrEqualTo(minN1)) {
|
|
40
|
+
return { n1: n1, e1: e1, r1: r1 };
|
|
41
|
+
}
|
|
42
|
+
// Fallback: iterative search (should rarely be needed)
|
|
9
43
|
var maxE1 = x.div(minN1).log(10).plus(p).minus(1).ceil();
|
|
10
44
|
var currentE1 = maxE1;
|
|
11
45
|
while (true) {
|
|
12
|
-
var
|
|
46
|
+
var currentDivisor = getPowerOf10(currentE1.minus(p).plus(1));
|
|
47
|
+
var currentN1 = x.div(currentDivisor).floor();
|
|
13
48
|
if (currentN1.lessThan(maxN1) && currentN1.greaterThanOrEqualTo(minN1)) {
|
|
14
|
-
var currentR1 = currentN1.times(
|
|
49
|
+
var currentR1 = currentN1.times(currentDivisor);
|
|
15
50
|
if (currentR1.lessThanOrEqualTo(x)) {
|
|
16
51
|
return {
|
|
17
52
|
n1: currentN1,
|
|
@@ -23,16 +58,44 @@ function findN1E1R1(x, p) {
|
|
|
23
58
|
currentE1 = currentE1.minus(1);
|
|
24
59
|
}
|
|
25
60
|
}
|
|
26
|
-
//IMPL: Helper function to find n2, e2, and r2
|
|
61
|
+
//IMPL: Helper function to find n2, e2, and r2 using direct calculation
|
|
27
62
|
function findN2E2R2(x, p) {
|
|
28
|
-
var maxN2 =
|
|
29
|
-
var minN2 =
|
|
63
|
+
var maxN2 = getPowerOf10(p);
|
|
64
|
+
var minN2 = getPowerOf10(p - 1);
|
|
65
|
+
// Direct calculation: similar to findN1E1R1 but with ceiling
|
|
66
|
+
var log10x = x.log(10);
|
|
67
|
+
var e2 = log10x.floor();
|
|
68
|
+
// Calculate n2 and r2 from e2
|
|
69
|
+
var divisor = getPowerOf10(e2.minus(p).plus(1));
|
|
70
|
+
var n2 = x.div(divisor).ceil();
|
|
71
|
+
var r2 = n2.times(divisor);
|
|
72
|
+
// Verify and adjust if n2 is out of bounds
|
|
73
|
+
if (n2.greaterThanOrEqualTo(maxN2)) {
|
|
74
|
+
e2 = e2.plus(1);
|
|
75
|
+
var newDivisor = getPowerOf10(e2.minus(p).plus(1));
|
|
76
|
+
n2 = x.div(newDivisor).ceil();
|
|
77
|
+
r2 = n2.times(newDivisor);
|
|
78
|
+
}
|
|
79
|
+
else if (n2.lessThan(minN2)) {
|
|
80
|
+
e2 = e2.minus(1);
|
|
81
|
+
var newDivisor = getPowerOf10(e2.minus(p).plus(1));
|
|
82
|
+
n2 = x.div(newDivisor).ceil();
|
|
83
|
+
r2 = n2.times(newDivisor);
|
|
84
|
+
}
|
|
85
|
+
// Final verification with fallback to iterative search if needed
|
|
86
|
+
if (r2.greaterThanOrEqualTo(x) &&
|
|
87
|
+
n2.lessThan(maxN2) &&
|
|
88
|
+
n2.greaterThanOrEqualTo(minN2)) {
|
|
89
|
+
return { n2: n2, e2: e2, r2: r2 };
|
|
90
|
+
}
|
|
91
|
+
// Fallback: iterative search (should rarely be needed)
|
|
30
92
|
var minE2 = x.div(maxN2).log(10).plus(p).minus(1).floor();
|
|
31
93
|
var currentE2 = minE2;
|
|
32
94
|
while (true) {
|
|
33
|
-
var
|
|
95
|
+
var currentDivisor = getPowerOf10(currentE2.minus(p).plus(1));
|
|
96
|
+
var currentN2 = x.div(currentDivisor).ceil();
|
|
34
97
|
if (currentN2.lessThan(maxN2) && currentN2.greaterThanOrEqualTo(minN2)) {
|
|
35
|
-
var currentR2 = currentN2.times(
|
|
98
|
+
var currentR2 = currentN2.times(currentDivisor);
|
|
36
99
|
if (currentR2.greaterThanOrEqualTo(x)) {
|
|
37
100
|
return {
|
|
38
101
|
n2: currentN2,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Decimal } from 'decimal.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cached function to compute powers of 10 for Decimal.js operations.
|
|
4
|
+
* This cache significantly reduces overhead in ComputeExponent and ToRawFixed
|
|
5
|
+
* by memoizing expensive Decimal.pow(10, n) calculations.
|
|
6
|
+
*
|
|
7
|
+
* Common exponents (e.g., -20 to 20) are used repeatedly in number formatting,
|
|
8
|
+
* so caching provides substantial performance benefits.
|
|
9
|
+
*
|
|
10
|
+
* @param exponent - Can be a number or Decimal. If Decimal, it will be converted to string for cache key.
|
|
11
|
+
*/
|
|
12
|
+
export declare const getPowerOf10: (exponent: number | Decimal) => Decimal;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Decimal } from 'decimal.js';
|
|
2
|
+
import { memoize } from '@formatjs/fast-memoize';
|
|
3
|
+
/**
|
|
4
|
+
* Cached function to compute powers of 10 for Decimal.js operations.
|
|
5
|
+
* This cache significantly reduces overhead in ComputeExponent and ToRawFixed
|
|
6
|
+
* by memoizing expensive Decimal.pow(10, n) calculations.
|
|
7
|
+
*
|
|
8
|
+
* Common exponents (e.g., -20 to 20) are used repeatedly in number formatting,
|
|
9
|
+
* so caching provides substantial performance benefits.
|
|
10
|
+
*
|
|
11
|
+
* @param exponent - Can be a number or Decimal. If Decimal, it will be converted to string for cache key.
|
|
12
|
+
*/
|
|
13
|
+
export var getPowerOf10 = memoize(function (exponent) {
|
|
14
|
+
return Decimal.pow(10, exponent);
|
|
15
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Decimal } from 'decimal.js';
|
|
2
2
|
import { S_UNICODE_REGEX } from '../regex.generated.js';
|
|
3
|
+
import { getPowerOf10 } from './decimal-cache.js';
|
|
3
4
|
import { digitMapping } from './digit-mapping.generated.js';
|
|
4
5
|
import { GetUnsignedRoundingMode } from './GetUnsignedRoundingMode.js';
|
|
5
6
|
import { ToRawFixed } from './ToRawFixed.js';
|
|
@@ -163,9 +164,7 @@ export default function formatToParts(numberResult, data, pl, options) {
|
|
|
163
164
|
var unitName = void 0;
|
|
164
165
|
var currencyNameData = data.currencies[options.currency];
|
|
165
166
|
if (currencyNameData) {
|
|
166
|
-
unitName = selectPlural(pl, numberResult.roundedNumber
|
|
167
|
-
.times(Decimal.pow(10, exponent))
|
|
168
|
-
.toNumber(), currencyNameData.displayName);
|
|
167
|
+
unitName = selectPlural(pl, numberResult.roundedNumber.times(getPowerOf10(exponent)).toNumber(), currencyNameData.displayName);
|
|
169
168
|
}
|
|
170
169
|
else {
|
|
171
170
|
// Fallback for unknown currency
|
|
@@ -202,9 +201,7 @@ export default function formatToParts(numberResult, data, pl, options) {
|
|
|
202
201
|
var unitPattern = void 0;
|
|
203
202
|
if (unitData) {
|
|
204
203
|
// Simple unit pattern
|
|
205
|
-
unitPattern = selectPlural(pl, numberResult.roundedNumber
|
|
206
|
-
.times(Decimal.pow(10, exponent))
|
|
207
|
-
.toNumber(), data.units.simple[unit][unitDisplay]);
|
|
204
|
+
unitPattern = selectPlural(pl, numberResult.roundedNumber.times(getPowerOf10(exponent)).toNumber(), data.units.simple[unit][unitDisplay]);
|
|
208
205
|
}
|
|
209
206
|
else {
|
|
210
207
|
// See: http://unicode.org/reports/tr35/tr35-general.html#perUnitPatterns
|
|
@@ -212,9 +209,7 @@ export default function formatToParts(numberResult, data, pl, options) {
|
|
|
212
209
|
// Implementation note: we are not following TR-35 here because we need to format to parts!
|
|
213
210
|
var _c = unit.split('-per-'), numeratorUnit = _c[0], denominatorUnit = _c[1];
|
|
214
211
|
unitData = data.units.simple[numeratorUnit];
|
|
215
|
-
var numeratorUnitPattern = selectPlural(pl, numberResult.roundedNumber
|
|
216
|
-
.times(Decimal.pow(10, exponent))
|
|
217
|
-
.toNumber(), data.units.simple[numeratorUnit][unitDisplay]);
|
|
212
|
+
var numeratorUnitPattern = selectPlural(pl, numberResult.roundedNumber.times(getPowerOf10(exponent)).toNumber(), data.units.simple[numeratorUnit][unitDisplay]);
|
|
218
213
|
var perUnitPattern = data.units.simple[denominatorUnit].perUnit[unitDisplay];
|
|
219
214
|
if (perUnitPattern) {
|
|
220
215
|
// perUnitPattern exists, combine it with numeratorUnitPattern
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formatjs/ecma402-abstract",
|
|
3
3
|
"description": "A collection of implementation for ECMAScript abstract operations",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.5",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Long Ho <holevietlong@gmail.com",
|
|
7
7
|
"type": "module",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"decimal.js": "^10.4.3",
|
|
15
15
|
"tslib": "^2.8.0",
|
|
16
|
-
"@formatjs/
|
|
17
|
-
"@formatjs/
|
|
16
|
+
"@formatjs/intl-localematcher": "0.7.3",
|
|
17
|
+
"@formatjs/fast-memoize": "3.0.1"
|
|
18
18
|
},
|
|
19
19
|
"bugs": "https://github.com/formatjs/formatjs/issues",
|
|
20
20
|
"gitHead": "a7842673d8ad205171ad7c8cb8bb2f318b427c0c",
|