@formatjs/ecma402-abstract 3.0.2 → 3.0.4

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.
@@ -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
- var magnitude = x.log(10).floor();
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(Decimal.pow(10, -exponent));
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
- var newMagnitude = formatNumberResult.roundedNumber.log(10).floor();
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 = Decimal.pow(10, magnitude).toString();
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(Decimal.pow(10, -exponent));
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(Decimal.pow(10, -f));
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(Decimal.pow(10, f)).floor();
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(Decimal.pow(10, f)).ceil();
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
- //IMPL: Helper function to find n1, e1, and r1
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 = Decimal.pow(10, p);
8
- var minN1 = Decimal.pow(10, p - 1);
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 currentN1 = x.div(Decimal.pow(10, currentE1.minus(p).plus(1))).floor();
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(Decimal.pow(10, currentE1.minus(p).plus(1)));
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 = Decimal.pow(10, p);
29
- var minN2 = Decimal.pow(10, p - 1);
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 currentN2 = x.div(Decimal.pow(10, currentE2.minus(p).plus(1))).ceil();
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(Decimal.pow(10, currentE2.minus(p).plus(1)));
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.2",
4
+ "version": "3.0.4",
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/intl-localematcher": "0.7.2",
17
- "@formatjs/fast-memoize": "3.0.1"
16
+ "@formatjs/fast-memoize": "3.0.1",
17
+ "@formatjs/intl-localematcher": "0.7.2"
18
18
  },
19
19
  "bugs": "https://github.com/formatjs/formatjs/issues",
20
20
  "gitHead": "a7842673d8ad205171ad7c8cb8bb2f318b427c0c",
@@ -74,6 +74,15 @@ export interface DateTimeFormatLocaleInternalData {
74
74
  long: string[];
75
75
  short: string[];
76
76
  };
77
+ /**
78
+ * Stand-alone month names (used when month appears without other date fields)
79
+ * Falls back to format month if not provided
80
+ */
81
+ monthStandalone?: {
82
+ narrow: string[];
83
+ long: string[];
84
+ short: string[];
85
+ };
77
86
  timeZoneName: TimeZoneNameData;
78
87
  /**
79
88
  * So we can construct GMT+08:00