n2words 3.0.0 → 3.1.0

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.
Files changed (154) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/languages/am-Latn.js +2 -2
  3. package/dist/languages/am-Latn.js.map +1 -1
  4. package/dist/languages/am.js +2 -2
  5. package/dist/languages/am.js.map +1 -1
  6. package/dist/languages/ar.js +1 -1
  7. package/dist/languages/az.js +2 -2
  8. package/dist/languages/az.js.map +1 -1
  9. package/dist/languages/bn.js +2 -2
  10. package/dist/languages/bn.js.map +1 -1
  11. package/dist/languages/cs.js +2 -2
  12. package/dist/languages/cs.js.map +1 -1
  13. package/dist/languages/da.js +2 -2
  14. package/dist/languages/da.js.map +1 -1
  15. package/dist/languages/de.js +2 -2
  16. package/dist/languages/de.js.map +1 -1
  17. package/dist/languages/el.js +2 -2
  18. package/dist/languages/el.js.map +1 -1
  19. package/dist/languages/en.js +2 -2
  20. package/dist/languages/en.js.map +1 -1
  21. package/dist/languages/es.js +2 -2
  22. package/dist/languages/es.js.map +1 -1
  23. package/dist/languages/fa.js +1 -1
  24. package/dist/languages/fi.js +2 -2
  25. package/dist/languages/fi.js.map +1 -1
  26. package/dist/languages/fil.js +2 -2
  27. package/dist/languages/fil.js.map +1 -1
  28. package/dist/languages/fr-BE.js +2 -2
  29. package/dist/languages/fr-BE.js.map +1 -1
  30. package/dist/languages/fr.js +2 -2
  31. package/dist/languages/fr.js.map +1 -1
  32. package/dist/languages/gu.js +2 -2
  33. package/dist/languages/gu.js.map +1 -1
  34. package/dist/languages/ha.js +2 -2
  35. package/dist/languages/ha.js.map +1 -1
  36. package/dist/languages/hbo.js +2 -2
  37. package/dist/languages/hbo.js.map +1 -1
  38. package/dist/languages/he.js +2 -2
  39. package/dist/languages/he.js.map +1 -1
  40. package/dist/languages/hi.js +2 -2
  41. package/dist/languages/hi.js.map +1 -1
  42. package/dist/languages/hr.js +2 -2
  43. package/dist/languages/hr.js.map +1 -1
  44. package/dist/languages/hu.js +1 -1
  45. package/dist/languages/id.js +2 -2
  46. package/dist/languages/id.js.map +1 -1
  47. package/dist/languages/it.js +2 -2
  48. package/dist/languages/it.js.map +1 -1
  49. package/dist/languages/ja.js +2 -2
  50. package/dist/languages/ja.js.map +1 -1
  51. package/dist/languages/ka.js +3 -0
  52. package/dist/languages/ka.js.map +1 -0
  53. package/dist/languages/kn.js +2 -2
  54. package/dist/languages/kn.js.map +1 -1
  55. package/dist/languages/ko.js +2 -2
  56. package/dist/languages/ko.js.map +1 -1
  57. package/dist/languages/lt.js +2 -2
  58. package/dist/languages/lt.js.map +1 -1
  59. package/dist/languages/lv.js +2 -2
  60. package/dist/languages/lv.js.map +1 -1
  61. package/dist/languages/mr.js +2 -2
  62. package/dist/languages/mr.js.map +1 -1
  63. package/dist/languages/ms.js +2 -2
  64. package/dist/languages/ms.js.map +1 -1
  65. package/dist/languages/nb.js +2 -2
  66. package/dist/languages/nb.js.map +1 -1
  67. package/dist/languages/nl.js +2 -2
  68. package/dist/languages/nl.js.map +1 -1
  69. package/dist/languages/pa.js +2 -2
  70. package/dist/languages/pa.js.map +1 -1
  71. package/dist/languages/pl.js +2 -2
  72. package/dist/languages/pl.js.map +1 -1
  73. package/dist/languages/pt.js +2 -2
  74. package/dist/languages/pt.js.map +1 -1
  75. package/dist/languages/ro.js +1 -1
  76. package/dist/languages/ru.js +2 -2
  77. package/dist/languages/ru.js.map +1 -1
  78. package/dist/languages/sr-Cyrl.js +2 -2
  79. package/dist/languages/sr-Cyrl.js.map +1 -1
  80. package/dist/languages/sr-Latn.js +2 -2
  81. package/dist/languages/sr-Latn.js.map +1 -1
  82. package/dist/languages/sv.js +2 -2
  83. package/dist/languages/sv.js.map +1 -1
  84. package/dist/languages/sw.js +1 -1
  85. package/dist/languages/ta.js +2 -2
  86. package/dist/languages/ta.js.map +1 -1
  87. package/dist/languages/te.js +2 -2
  88. package/dist/languages/te.js.map +1 -1
  89. package/dist/languages/th.js +1 -1
  90. package/dist/languages/tr.js +2 -2
  91. package/dist/languages/tr.js.map +1 -1
  92. package/dist/languages/uk.js +2 -2
  93. package/dist/languages/uk.js.map +1 -1
  94. package/dist/languages/ur.js +2 -2
  95. package/dist/languages/ur.js.map +1 -1
  96. package/dist/languages/vi.js +2 -2
  97. package/dist/languages/vi.js.map +1 -1
  98. package/dist/languages/yo.js +3 -0
  99. package/dist/languages/yo.js.map +1 -0
  100. package/dist/languages/zh-Hans.js +1 -1
  101. package/dist/languages/zh-Hant.js +1 -1
  102. package/dist/n2words.js +2 -2
  103. package/dist/n2words.js.map +1 -1
  104. package/lib/languages/am-Latn.js +4 -9
  105. package/lib/languages/am.js +4 -9
  106. package/lib/languages/az.js +4 -9
  107. package/lib/languages/bn.js +51 -30
  108. package/lib/languages/cs.js +9 -26
  109. package/lib/languages/da.js +6 -13
  110. package/lib/languages/de.js +21 -33
  111. package/lib/languages/el.js +6 -13
  112. package/lib/languages/en.js +44 -58
  113. package/lib/languages/es.js +10 -30
  114. package/lib/languages/fi.js +6 -13
  115. package/lib/languages/fil.js +6 -17
  116. package/lib/languages/fr-BE.js +17 -26
  117. package/lib/languages/fr.js +18 -31
  118. package/lib/languages/gu.js +43 -30
  119. package/lib/languages/ha.js +4 -9
  120. package/lib/languages/hbo.js +7 -25
  121. package/lib/languages/he.js +7 -18
  122. package/lib/languages/hi.js +55 -30
  123. package/lib/languages/hr.js +61 -55
  124. package/lib/languages/id.js +8 -13
  125. package/lib/languages/it.js +64 -99
  126. package/lib/languages/ja.js +8 -20
  127. package/lib/languages/ka.d.ts +17 -0
  128. package/lib/languages/ka.js +291 -0
  129. package/lib/languages/kn.js +43 -30
  130. package/lib/languages/ko.js +6 -13
  131. package/lib/languages/lt.js +6 -15
  132. package/lib/languages/lv.js +6 -15
  133. package/lib/languages/mr.js +43 -30
  134. package/lib/languages/ms.js +8 -13
  135. package/lib/languages/nb.js +13 -23
  136. package/lib/languages/nl.js +11 -29
  137. package/lib/languages/pa.js +32 -37
  138. package/lib/languages/pl.js +7 -20
  139. package/lib/languages/pt.js +17 -31
  140. package/lib/languages/ru.js +64 -59
  141. package/lib/languages/sr-Cyrl.js +58 -52
  142. package/lib/languages/sr-Latn.js +58 -52
  143. package/lib/languages/sv.js +14 -24
  144. package/lib/languages/ta.js +55 -42
  145. package/lib/languages/te.js +33 -41
  146. package/lib/languages/tr.js +7 -18
  147. package/lib/languages/uk.js +61 -55
  148. package/lib/languages/ur.js +32 -37
  149. package/lib/languages/vi.js +23 -43
  150. package/lib/languages/yo.d.ts +7 -0
  151. package/lib/languages/yo.js +303 -0
  152. package/lib/n2words.d.ts +3 -1
  153. package/lib/n2words.js +4 -0
  154. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Amharic Latin language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter with precomputed lookup tables.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  * Latin/ASCII romanization of Amharic numerals.
6
6
  *
7
7
  * Key features:
@@ -64,11 +64,6 @@ function buildSegment (n) {
64
64
  return parts.join(' ')
65
65
  }
66
66
 
67
- const SEGMENTS = new Array(1000)
68
- for (let i = 0; i < 1000; i++) {
69
- SEGMENTS[i] = buildSegment(i)
70
- }
71
-
72
67
  // ============================================================================
73
68
  // Conversion Functions
74
69
  // ============================================================================
@@ -77,7 +72,7 @@ function integerToWords (n) {
77
72
  if (n === 0n) return ZERO
78
73
 
79
74
  if (n < 1000n) {
80
- return SEGMENTS[Number(n)]
75
+ return buildSegment(Number(n))
81
76
  }
82
77
 
83
78
  return buildLargeNumberWords(n)
@@ -111,9 +106,9 @@ function buildLargeNumberWords (n) {
111
106
  const scaleWord = SCALE_WORDS[scaleIndex] || ''
112
107
 
113
108
  if (scaleIndex === 0) {
114
- parts.push(SEGMENTS[segment])
109
+ parts.push(buildSegment(segment))
115
110
  } else {
116
- parts.push(SEGMENTS[segment] + ' ' + scaleWord)
111
+ parts.push(buildSegment(segment) + ' ' + scaleWord)
117
112
  }
118
113
  }
119
114
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Amharic language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter with precomputed lookup tables.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  * Native Ge'ez script (ግዕዝ) output.
6
6
  *
7
7
  * Key features:
@@ -64,11 +64,6 @@ function buildSegment (n) {
64
64
  return parts.join(' ')
65
65
  }
66
66
 
67
- const SEGMENTS = new Array(1000)
68
- for (let i = 0; i < 1000; i++) {
69
- SEGMENTS[i] = buildSegment(i)
70
- }
71
-
72
67
  // ============================================================================
73
68
  // Conversion Functions
74
69
  // ============================================================================
@@ -77,7 +72,7 @@ function integerToWords (n) {
77
72
  if (n === 0n) return ZERO
78
73
 
79
74
  if (n < 1000n) {
80
- return SEGMENTS[Number(n)]
75
+ return buildSegment(Number(n))
81
76
  }
82
77
 
83
78
  return buildLargeNumberWords(n)
@@ -111,9 +106,9 @@ function buildLargeNumberWords (n) {
111
106
  const scaleWord = SCALE_WORDS[scaleIndex] || ''
112
107
 
113
108
  if (scaleIndex === 0) {
114
- parts.push(SEGMENTS[segment])
109
+ parts.push(buildSegment(segment))
115
110
  } else {
116
- parts.push(SEGMENTS[segment] + ' ' + scaleWord)
111
+ parts.push(buildSegment(segment) + ' ' + scaleWord)
117
112
  }
118
113
  }
119
114
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Azerbaijani language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter with precomputed lookup tables.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Turkic language patterns
@@ -64,11 +64,6 @@ function buildSegment (n) {
64
64
  return parts.join(' ')
65
65
  }
66
66
 
67
- const SEGMENTS = new Array(1000)
68
- for (let i = 0; i < 1000; i++) {
69
- SEGMENTS[i] = buildSegment(i)
70
- }
71
-
72
67
  // ============================================================================
73
68
  // Conversion Functions
74
69
  // ============================================================================
@@ -77,7 +72,7 @@ function integerToWords (n) {
77
72
  if (n === 0n) return ZERO
78
73
 
79
74
  if (n < 1000n) {
80
- return SEGMENTS[Number(n)]
75
+ return buildSegment(Number(n))
81
76
  }
82
77
 
83
78
  return buildLargeNumberWords(n)
@@ -111,12 +106,12 @@ function buildLargeNumberWords (n) {
111
106
  const scaleWord = SCALE_WORDS[scaleIndex] || ''
112
107
 
113
108
  if (scaleIndex === 0) {
114
- parts.push(SEGMENTS[segment])
109
+ parts.push(buildSegment(segment))
115
110
  } else if (scaleIndex === 1 && segment === 1) {
116
111
  // Omit "bir" before thousand
117
112
  parts.push(scaleWord)
118
113
  } else {
119
- parts.push(SEGMENTS[segment] + ' ' + scaleWord)
114
+ parts.push(buildSegment(segment) + ' ' + scaleWord)
120
115
  }
121
116
  }
122
117
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Bangla language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter for South Asian numbering.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Indian numbering system (হাজার, লাখ, কোটি)
@@ -38,26 +38,13 @@ const BELOW_HUNDRED = [
38
38
  const SCALE_WORDS = ['', 'হাজার', 'লাখ', 'কোটি', 'আরব', 'খরব', 'নীল', 'পদ্ম', 'শঙ্খ']
39
39
 
40
40
  // ============================================================================
41
- // Segment Splitting (inlined for performance)
41
+ // Segment Building
42
42
  // ============================================================================
43
43
 
44
- function groupByThreeThenTwos (n) {
45
- const numStr = n.toString()
46
- if (numStr.length <= 3) return [Number(numStr)]
47
-
48
- const segments = []
49
- segments.unshift(Number(numStr.slice(-3)))
50
-
51
- let remaining = numStr.slice(0, -3)
52
- while (remaining.length > 0) {
53
- segments.unshift(Number(remaining.slice(-2)))
54
- remaining = remaining.slice(0, -2)
55
- }
56
-
57
- return segments
58
- }
59
-
60
- function segmentToWords (n) {
44
+ /**
45
+ * Builds words for a 0-999 segment.
46
+ */
47
+ function buildSegment (n) {
61
48
  if (n === 0) return ''
62
49
  if (n < 100) return BELOW_HUNDRED[n]
63
50
 
@@ -74,25 +61,59 @@ function segmentToWords (n) {
74
61
  // Conversion Functions
75
62
  // ============================================================================
76
63
 
64
+ /**
65
+ * Converts a non-negative integer to Bengali words.
66
+ *
67
+ * Uses BigInt modulo for segment extraction (faster than string slicing).
68
+ * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
69
+ *
70
+ * @param {bigint} n - Non-negative integer to convert
71
+ * @returns {string} Bengali words
72
+ */
77
73
  function integerToWords (n) {
78
74
  if (n === 0n) return ZERO
79
75
 
80
- const segments = groupByThreeThenTwos(n)
81
- const segmentCount = segments.length
82
- const words = []
76
+ // Fast path: numbers < 1000 (direct lookup)
77
+ if (n < 1000n) {
78
+ return buildSegment(Number(n))
79
+ }
80
+
81
+ // Extract segments using BigInt modulo
82
+ // First segment is 3 digits (thousands), rest are 2 digits (lakhs, crores, etc.)
83
+ // Segments stored least-significant first
84
+ const segments = []
83
85
 
84
- for (let i = 0; i < segmentCount; i++) {
85
- const segmentValue = segments[i]
86
- if (segmentValue === 0) continue
86
+ // First segment: last 3 digits
87
+ segments.push(Number(n % 1000n))
88
+ let temp = n / 1000n
89
+
90
+ // Remaining segments: 2 digits each (lakh = 100k, crore = 10M, etc.)
91
+ while (temp > 0n) {
92
+ segments.push(Number(temp % 100n))
93
+ temp = temp / 100n
94
+ }
95
+
96
+ // Build result string (process from most-significant to least)
97
+ const words = []
98
+ for (let i = segments.length - 1; i >= 0; i--) {
99
+ const segment = segments[i]
100
+ if (segment === 0) continue
101
+
102
+ if (i === 0) {
103
+ // First segment (units place) can be 0-999
104
+ words.push(buildSegment(segment))
105
+ } else {
106
+ // Other segments are 0-99
107
+ words.push(BELOW_HUNDRED[segment])
108
+ }
87
109
 
88
- const scaleIndex = segmentCount - i - 1
89
- words.push(segmentToWords(segmentValue))
90
- if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {
91
- words.push(SCALE_WORDS[scaleIndex])
110
+ // Add scale word if not the units segment
111
+ if (i > 0 && SCALE_WORDS[i]) {
112
+ words.push(SCALE_WORDS[i])
92
113
  }
93
114
  }
94
115
 
95
- return words.join(' ').trim()
116
+ return words.join(' ')
96
117
  }
97
118
 
98
119
  function decimalPartToWords (decimalPart) {
@@ -1,13 +1,9 @@
1
1
  /**
2
2
  * Czech language converter - Functional Implementation
3
3
  *
4
- * A performance-optimized number-to-words converter using precomputed lookup tables.
5
4
  * Self-contained module with its own input validation, ready for subpath exports.
6
5
  *
7
- * Key optimization: Precompute all segment values (0-999) at module load.
8
- * This eliminates all per-call string manipulation for segment conversion.
9
- *
10
- * Czech-specific rules (handled in precomputation):
6
+ * Czech-specific rules:
11
7
  * - Three-form pluralization: 1 = singular, 2-4 = few, 5+ = many
12
8
  * - Irregular hundreds: sto, dvě stě, tři sta, čtyři sta, pět set...
13
9
  * - Gender: dva (masc) vs dvě (fem) for 2
@@ -50,12 +46,11 @@ const ZERO = 'nula'
50
46
  const NEGATIVE = 'mínus'
51
47
 
52
48
  // ============================================================================
53
- // Precomputed Lookup Tables (built once at module load)
49
+ // Segment Building
54
50
  // ============================================================================
55
51
 
56
52
  /**
57
53
  * Builds segment word for 0-999 (masculine, default form).
58
- * Only used during table construction.
59
54
  */
60
55
  function buildSegment (n) {
61
56
  if (n === 0) return ''
@@ -120,18 +115,6 @@ function buildSegmentWithHundreds (n) {
120
115
  return parts.join(' ')
121
116
  }
122
117
 
123
- // Precompute all 1000 segment words (0-999) - masculine form
124
- const SEGMENTS = new Array(1000)
125
- for (let i = 0; i < 1000; i++) {
126
- SEGMENTS[i] = buildSegment(i)
127
- }
128
-
129
- // Precompute all 1000 segment words with hundreds (irregular hundreds forms)
130
- const SEGMENTS_WITH_HUNDREDS = new Array(1000)
131
- for (let i = 0; i < 1000; i++) {
132
- SEGMENTS_WITH_HUNDREDS[i] = buildSegmentWithHundreds(i)
133
- }
134
-
135
118
  // ============================================================================
136
119
  // Helper Functions
137
120
  // ============================================================================
@@ -185,9 +168,9 @@ function getDecimalSeparator (integerPart) {
185
168
  function integerToWords (n) {
186
169
  if (n === 0n) return ZERO
187
170
 
188
- // Fast path: numbers < 1000 (direct lookup)
171
+ // Fast path: numbers < 1000
189
172
  if (n < 1000n) {
190
- return SEGMENTS[Number(n)]
173
+ return buildSegment(Number(n))
191
174
  }
192
175
 
193
176
  // Fast path: numbers < 1,000,000 (thousands)
@@ -202,12 +185,12 @@ function integerToWords (n) {
202
185
  // Omit "one" before tisíc
203
186
  result = scaleWord
204
187
  } else {
205
- result = SEGMENTS[Number(thousands)] + ' ' + scaleWord
188
+ result = buildSegment(Number(thousands)) + ' ' + scaleWord
206
189
  }
207
190
 
208
191
  if (remainder > 0) {
209
192
  // Use form with irregular hundreds (for "dvě stě" etc.)
210
- result += ' ' + SEGMENTS_WITH_HUNDREDS[remainder]
193
+ result += ' ' + buildSegmentWithHundreds(remainder)
211
194
  }
212
195
 
213
196
  return result
@@ -245,7 +228,7 @@ function buildLargeNumberWords (n) {
245
228
 
246
229
  if (i === 0) {
247
230
  // Units segment (no scale word) - use form with irregular hundreds
248
- result += SEGMENTS_WITH_HUNDREDS[Number(segment)]
231
+ result += buildSegmentWithHundreds(Number(segment))
249
232
  } else {
250
233
  // Scale word needed
251
234
  const forms = PLURAL_FORMS[i]
@@ -257,11 +240,11 @@ function buildLargeNumberWords (n) {
257
240
  result += scaleWord
258
241
  } else {
259
242
  // Use masculine form for multiplier before scale words
260
- result += SEGMENTS[Number(segment)] + ' ' + scaleWord
243
+ result += buildSegment(Number(segment)) + ' ' + scaleWord
261
244
  }
262
245
  } else {
263
246
  // Fallback for very large scales without defined forms
264
- result += SEGMENTS[Number(segment)]
247
+ result += buildSegment(Number(segment))
265
248
  }
266
249
  }
267
250
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Danish language converter - Functional Implementation
3
3
  *
4
- * A performance-optimized number-to-words converter using precomputed lookup tables.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Vigesimal (base-20) tens naming: halvtreds (50), treds (60), etc.
@@ -37,7 +37,7 @@ const DECIMAL_SEP = 'komma'
37
37
  const SCALES = ['millioner', 'millarder', 'billioner', 'billarder', 'trillioner', 'trillarder', 'quadrillioner', 'quadrillarder']
38
38
 
39
39
  // ============================================================================
40
- // Precomputed Lookup Tables (built once at module load)
40
+ // Segment Building
41
41
  // ============================================================================
42
42
 
43
43
  /**
@@ -83,13 +83,6 @@ function buildSegment (n) {
83
83
  return parts[0] || ''
84
84
  }
85
85
 
86
- // Precompute all 1000 segment words (0-999)
87
- const SEGMENTS = new Array(1000)
88
-
89
- for (let i = 0; i < 1000; i++) {
90
- SEGMENTS[i] = buildSegment(i)
91
- }
92
-
93
86
  // ============================================================================
94
87
  // Conversion Functions
95
88
  // ============================================================================
@@ -105,7 +98,7 @@ function integerToWords (n) {
105
98
 
106
99
  // Fast path: numbers < 1000 (direct lookup)
107
100
  if (n < 1000n) {
108
- return SEGMENTS[Number(n)]
101
+ return buildSegment(Number(n))
109
102
  }
110
103
 
111
104
  // Fast path: numbers < 1,000,000 (thousands)
@@ -114,11 +107,11 @@ function integerToWords (n) {
114
107
  const remainder = Number(n % 1000n)
115
108
 
116
109
  // Compound thousands: "ettusind", "firetusind"
117
- let result = SEGMENTS[thousands] + THOUSAND
110
+ let result = buildSegment(thousands) + THOUSAND
118
111
 
119
112
  if (remainder > 0) {
120
113
  // Add 'e' suffix and " og " for remainder: "firetusinde og ..."
121
- result += 'e og ' + SEGMENTS[remainder]
114
+ result += 'e og ' + buildSegment(remainder)
122
115
  }
123
116
 
124
117
  return result
@@ -162,7 +155,7 @@ function buildLargeNumberWords (n) {
162
155
  const segment = segments[i]
163
156
 
164
157
  if (segment !== 0) {
165
- const segmentWord = SEGMENTS[segment]
158
+ const segmentWord = buildSegment(segment)
166
159
 
167
160
  if (scaleIndex === 0) {
168
161
  // Units segment
@@ -1,18 +1,15 @@
1
1
  /**
2
2
  * German language converter - Functional Implementation
3
3
  *
4
- * A performance-optimized number-to-words converter using precomputed lookup tables.
5
4
  * Self-contained module with its own input validation, ready for subpath exports.
6
5
  *
7
- * Key optimization: Precompute all segment values (0-999) at module load.
8
- * This eliminates all per-call string manipulation for segment conversion.
9
- *
10
- * German-specific rules (handled in precomputation):
6
+ * Key features:
11
7
  * - Inverted tens-ones order: "einundzwanzig" (one-and-twenty) for 21-99
12
8
  * - Compound words without spaces below million level
13
9
  * - Three forms of 1: "eins" (standalone), "ein" (before hundert/tausend), "eine" (before Million+)
14
10
  * - Scale pluralization: Million → Millionen, Milliarde → Milliarden
15
11
  * - Spaces only around million+ scale words
12
+ * - BigInt modulo for efficient segment extraction
16
13
  */
17
14
 
18
15
  import { parseNumericValue } from '../utils/parse-numeric.js'
@@ -45,20 +42,22 @@ const NEGATIVE = 'minus'
45
42
  const DECIMAL_SEP = 'komma'
46
43
 
47
44
  // ============================================================================
48
- // Precomputed Lookup Tables (built once at module load)
45
+ // Segment Building
49
46
  // ============================================================================
50
47
 
51
48
  /**
52
49
  * Builds segment word for 0-999 (standalone form, uses "eins").
53
50
  * German inverts ones and tens: "einundzwanzig" = one-and-twenty
54
- * Only used during table construction.
51
+ *
52
+ * @param {number} n - Number 0-999
53
+ * @returns {string} German words for the segment
55
54
  */
56
55
  function buildSegment (n) {
57
56
  if (n === 0) return ''
58
57
 
59
58
  const ones = n % 10
60
- const tens = Math.floor(n / 10) % 10
61
- const hundreds = Math.floor(n / 100)
59
+ const tens = Math.trunc(n / 10) % 10
60
+ const hundreds = Math.trunc(n / 100)
62
61
 
63
62
  let result = ''
64
63
 
@@ -90,14 +89,17 @@ function buildSegment (n) {
90
89
  /**
91
90
  * Builds segment word for use before "tausend".
92
91
  * Uses "ein" instead of "eins" for 1.
92
+ *
93
+ * @param {number} n - Number 0-999
94
+ * @returns {string} German words for thousand context
93
95
  */
94
96
  function buildSegmentForThousand (n) {
95
97
  if (n === 0) return ''
96
98
  if (n === 1) return EIN // "eintausend"
97
99
 
98
100
  const ones = n % 10
99
- const tens = Math.floor(n / 10) % 10
100
- const hundreds = Math.floor(n / 100)
101
+ const tens = Math.trunc(n / 10) % 10
102
+ const hundreds = Math.trunc(n / 100)
101
103
 
102
104
  let result = ''
103
105
 
@@ -112,8 +114,6 @@ function buildSegmentForThousand (n) {
112
114
  } else if (tens >= 2) {
113
115
  result += TENS[tens]
114
116
  } else if (ones > 0 && hundreds > 0) {
115
- // After hundreds, ones 1 stays "eins" when not followed by scale
116
- // But we're going to tausend, so this path won't hit for n=1
117
117
  result += ONES[ones]
118
118
  } else if (ones > 0) {
119
119
  result += ONES[ones]
@@ -122,18 +122,6 @@ function buildSegmentForThousand (n) {
122
122
  return result
123
123
  }
124
124
 
125
- // Precompute all 1000 segment words (0-999) - standalone form
126
- const SEGMENTS = new Array(1000)
127
- for (let i = 0; i < 1000; i++) {
128
- SEGMENTS[i] = buildSegment(i)
129
- }
130
-
131
- // Precompute all 1000 segment words for thousand context
132
- const SEGMENTS_THOUSAND = new Array(1000)
133
- for (let i = 0; i < 1000; i++) {
134
- SEGMENTS_THOUSAND[i] = buildSegmentForThousand(i)
135
- }
136
-
137
125
  // ============================================================================
138
126
  // Conversion Functions
139
127
  // ============================================================================
@@ -147,21 +135,21 @@ for (let i = 0; i < 1000; i++) {
147
135
  function integerToWords (n) {
148
136
  if (n === 0n) return ZERO
149
137
 
150
- // Fast path: numbers < 1000 (direct lookup)
138
+ // Fast path: numbers < 1000
151
139
  if (n < 1000n) {
152
- return SEGMENTS[Number(n)]
140
+ return buildSegment(Number(n))
153
141
  }
154
142
 
155
- // Fast path: numbers < 1,000,000 (thousands)
143
+ // Fast path: numbers < 1,000,000
156
144
  if (n < 1_000_000n) {
157
145
  const thousands = Number(n / 1000n)
158
146
  const remainder = Number(n % 1000n)
159
147
 
160
148
  // Compound: "eintausendzweihundert" (no spaces)
161
- let result = SEGMENTS_THOUSAND[thousands] + SCALES[0]
149
+ let result = buildSegmentForThousand(thousands) + SCALES[0]
162
150
 
163
151
  if (remainder > 0) {
164
- result += SEGMENTS[remainder]
152
+ result += buildSegment(remainder)
165
153
  }
166
154
 
167
155
  return result
@@ -206,10 +194,10 @@ function buildLargeNumberWords (n) {
206
194
  if (segment !== 0) {
207
195
  if (scaleIndex === 0) {
208
196
  // Units segment (no scale word)
209
- parts.push({ words: SEGMENTS[segment], isScale: false, scaleLevel: 0 })
197
+ parts.push({ words: buildSegment(segment), isScale: false, scaleLevel: 0 })
210
198
  } else if (scaleIndex === 1) {
211
199
  // Thousands: compound without space
212
- const segWords = SEGMENTS_THOUSAND[segment]
200
+ const segWords = buildSegmentForThousand(segment)
213
201
  parts.push({ words: segWords + SCALES[0], isScale: false, scaleLevel: 1 })
214
202
  } else {
215
203
  // Million+ : space around scale word
@@ -217,7 +205,7 @@ function buildLargeNumberWords (n) {
217
205
  if (segment === 1) {
218
206
  segWords = 'eine' // "eine Million"
219
207
  } else {
220
- segWords = SEGMENTS[segment]
208
+ segWords = buildSegment(segment)
221
209
  }
222
210
  const scaleWord = segment === 1 ? SCALES[scaleIndex - 1] : SCALES_PLURAL[scaleIndex - 1]
223
211
  parts.push({ words: segWords, isScale: false, scaleLevel: scaleIndex })
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Greek language converter - Functional Implementation
3
3
  *
4
- * A performance-optimized number-to-words converter using precomputed lookup tables.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Space-separated number composition
@@ -35,7 +35,7 @@ const DECIMAL_SEP = 'κόμμα'
35
35
  const SCALES = ['εκατομμύριο', 'δισεκατομμύριο', 'τρισεκατομμύριο']
36
36
 
37
37
  // ============================================================================
38
- // Precomputed Lookup Tables (built once at module load)
38
+ // Segment Building
39
39
  // ============================================================================
40
40
 
41
41
  /**
@@ -73,13 +73,6 @@ function buildSegment (n) {
73
73
  return parts.join(' ')
74
74
  }
75
75
 
76
- // Precompute all 1000 segment words (0-999)
77
- const SEGMENTS = new Array(1000)
78
-
79
- for (let i = 0; i < 1000; i++) {
80
- SEGMENTS[i] = buildSegment(i)
81
- }
82
-
83
76
  // ============================================================================
84
77
  // Conversion Functions
85
78
  // ============================================================================
@@ -95,7 +88,7 @@ function integerToWords (n) {
95
88
 
96
89
  // Fast path: numbers < 1000 (direct lookup)
97
90
  if (n < 1000n) {
98
- return SEGMENTS[Number(n)]
91
+ return buildSegment(Number(n))
99
92
  }
100
93
 
101
94
  // Fast path: numbers < 1,000,000 (thousands)
@@ -108,11 +101,11 @@ function integerToWords (n) {
108
101
  if (thousands === 1) {
109
102
  result = THOUSAND
110
103
  } else {
111
- result = SEGMENTS[thousands] + ' ' + THOUSAND
104
+ result = buildSegment(thousands) + ' ' + THOUSAND
112
105
  }
113
106
 
114
107
  if (remainder > 0) {
115
- result += ' ' + SEGMENTS[remainder]
108
+ result += ' ' + buildSegment(remainder)
116
109
  }
117
110
 
118
111
  return result
@@ -155,7 +148,7 @@ function buildLargeNumberWords (n) {
155
148
  const segment = segments[i]
156
149
 
157
150
  if (segment !== 0) {
158
- const segmentWord = SEGMENTS[segment]
151
+ const segmentWord = buildSegment(segment)
159
152
 
160
153
  if (scaleIndex === 0) {
161
154
  // Units segment