n2words 1.24.0 → 3.0.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 (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,123 +1,256 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * English language converter.
2
+ * English language converter - Functional Implementation v2
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ * Self-contained module with its own input validation, ready for subpath exports.
5
6
  *
6
- * Converts numbers to English words, supporting:
7
- * - Negative numbers (prepended with "minus")
8
- * - Decimal numbers (word "point" between whole and fractional parts)
9
- * - Numbers up to octillions
7
+ * Key optimization: Precompute all segment values (0-999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
10
9
  *
11
- * Merge rules:
12
- * - Hyphenated for compound tens (e.g., "twenty-three")
13
- * - "and" after hundreds (e.g., "one hundred and one")
14
- * - Space-separated for larger composites (e.g., "one thousand twenty-three")
10
+ * English-specific rules (handled in precomputation):
11
+ * - "and" after hundreds: "one hundred and twenty-three"
12
+ * - Hyphenated tens-ones: "twenty-one", "forty-two"
13
+ * - "and" before final segment when following scale word
14
+ */
15
+
16
+ import { parseNumericValue } from '../utils/parse-numeric.js'
17
+
18
+ // ============================================================================
19
+ // Vocabulary (module-level constants)
20
+ // ============================================================================
21
+
22
+ const ONES = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
23
+ const TEENS = ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']
24
+ const TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
25
+
26
+ const SCALES = ['thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'septillion', 'octillion']
27
+
28
+ const HUNDRED = 'hundred'
29
+ const ZERO = 'zero'
30
+ const NEGATIVE = 'minus'
31
+ const DECIMAL_SEP = 'point'
32
+
33
+ // ============================================================================
34
+ // Precomputed Lookup Tables (built once at module load)
35
+ // ============================================================================
36
+
37
+ /**
38
+ * Builds segment word for 0-999.
39
+ * Returns object with word and whether it contains "hundred" (for "and" logic).
15
40
  */
16
- export class English extends GreedyScaleLanguage {
17
- negativeWord = 'minus'
18
- decimalSeparatorWord = 'point'
19
- zeroWord = 'zero'
20
- scaleWordPairs = [
21
- [1_000_000_000_000_000_000_000_000_000n, 'octillion'],
22
- [1_000_000_000_000_000_000_000_000n, 'septillion'],
23
- [1_000_000_000_000_000_000_000n, 'sextillion'],
24
- [1_000_000_000_000_000_000n, 'quintillion'],
25
- [1_000_000_000_000_000n, 'quadrillion'],
26
- [1_000_000_000_000n, 'trillion'],
27
- [1_000_000_000n, 'billion'],
28
- [1_000_000n, 'million'],
29
- [1000n, 'thousand'],
30
- [100n, 'hundred'],
31
- [90n, 'ninety'],
32
- [80n, 'eighty'],
33
- [70n, 'seventy'],
34
- [60n, 'sixty'],
35
- [50n, 'fifty'],
36
- [40n, 'forty'],
37
- [30n, 'thirty'],
38
- [20n, 'twenty'],
39
- [19n, 'nineteen'],
40
- [18n, 'eighteen'],
41
- [17n, 'seventeen'],
42
- [16n, 'sixteen'],
43
- [15n, 'fifteen'],
44
- [14n, 'fourteen'],
45
- [13n, 'thirteen'],
46
- [12n, 'twelve'],
47
- [11n, 'eleven'],
48
- [10n, 'ten'],
49
- [9n, 'nine'],
50
- [8n, 'eight'],
51
- [7n, 'seven'],
52
- [6n, 'six'],
53
- [5n, 'five'],
54
- [4n, 'four'],
55
- [3n, 'three'],
56
- [2n, 'two'],
57
- [1n, 'one'],
58
- [0n, 'zero']
59
- ]
60
-
61
- /**
62
- * Merges two adjacent word-number pairs according to English grammar rules.
63
- *
64
- * English-specific rules:
65
- * - Implicit "one": `mergeScales({ 'one': 1n }, { 'hundred': 100n })` → `{ 'one hundred': 100n }`
66
- * - Hyphenated compounds: `mergeScales({ 'twenty': 20n }, { 'three': 3n })` → `{ 'twenty-three': 23n }`
67
- * - "and" after hundreds: `mergeScales({ 'one hundred': 100n }, { 'one': 1n })` → `{ 'one hundred and one': 101n }`
68
- * - Multiplication: `mergeScales({ 'one': 1n }, { 'thousand': 1000n })` → `{ 'one thousand': 1000n }`
69
- *
70
- * @param {Object} leftPair Left word-set as `{ word: BigInt }`.
71
- * @param {Object} rightPair Right word-set as `{ word: BigInt }`.
72
- * @returns {Object} Merged pair with combined word and resulting numeric value.
73
- *
74
- * @example
75
- * mergeScales({ 'one': 1n }, { 'hundred': 100n }); // { 'one hundred': 100n }
76
- * mergeScales({ 'twenty': 20n }, { 'three': 3n }); // { 'twenty-three': 23n }
77
- */
78
- mergeScales (leftPair, rightPair) {
79
- const leftWord = Object.keys(leftPair)[0]
80
- const leftNumber = Object.values(leftPair)[0]
81
- const rightWord = Object.keys(rightPair)[0]
82
- const rightNumber = Object.values(rightPair)[0]
83
-
84
- // Rule 1: Implicit "one" - omit when multiplying ("one hundred" → "hundred")
85
- if (leftNumber === 1n && rightNumber < 100n) {
86
- return { [rightWord]: rightNumber }
41
+ function buildSegment (n) {
42
+ if (n === 0) return { word: '', hasHundred: false }
43
+
44
+ const ones = n % 10
45
+ const tens = Math.floor(n / 10) % 10
46
+ const hundreds = Math.floor(n / 100)
47
+
48
+ let result = ''
49
+ let hasHundred = false
50
+
51
+ // Hundreds
52
+ if (hundreds > 0) {
53
+ result = ONES[hundreds] + ' ' + HUNDRED
54
+ hasHundred = true
55
+ }
56
+
57
+ // Tens and ones
58
+ let tensOnes = ''
59
+ if (tens === 1) {
60
+ tensOnes = TEENS[ones]
61
+ } else if (tens >= 2) {
62
+ if (ones > 0) {
63
+ tensOnes = TENS[tens] + '-' + ONES[ones]
64
+ } else {
65
+ tensOnes = TENS[tens]
87
66
  }
67
+ } else if (ones > 0) {
68
+ tensOnes = ONES[ones]
69
+ }
88
70
 
89
- // Rule 2: Hyphenate compounds under 100 ("twenty-three")
90
- if (leftNumber < 100n && leftNumber > rightNumber) {
91
- return { [`${leftWord}-${rightWord}`]: leftNumber + rightNumber }
71
+ // Combine with "and" after hundreds
72
+ if (result && tensOnes) {
73
+ return { word: result + ' and ' + tensOnes, hasHundred: true }
74
+ }
75
+
76
+ return { word: result || tensOnes, hasHundred }
77
+ }
78
+
79
+ // Precompute all 1000 segment words (0-999)
80
+ const SEGMENTS = new Array(1000)
81
+ const SEGMENTS_HAS_HUNDRED = new Array(1000)
82
+
83
+ for (let i = 0; i < 1000; i++) {
84
+ const result = buildSegment(i)
85
+ SEGMENTS[i] = result.word
86
+ SEGMENTS_HAS_HUNDRED[i] = result.hasHundred
87
+ }
88
+
89
+ // ============================================================================
90
+ // Conversion Functions
91
+ // ============================================================================
92
+
93
+ /**
94
+ * Converts a non-negative integer to English words.
95
+ *
96
+ * @param {bigint} n - Non-negative integer to convert
97
+ * @returns {string} English words
98
+ */
99
+ function integerToWords (n) {
100
+ if (n === 0n) return ZERO
101
+
102
+ // Fast path: numbers < 1000 (direct lookup)
103
+ if (n < 1000n) {
104
+ return SEGMENTS[Number(n)]
105
+ }
106
+
107
+ // Fast path: numbers < 1,000,000 (thousands)
108
+ if (n < 1_000_000n) {
109
+ const thousands = Number(n / 1000n)
110
+ const remainder = Number(n % 1000n)
111
+
112
+ let result = SEGMENTS[thousands] + ' ' + SCALES[0]
113
+
114
+ if (remainder > 0) {
115
+ const remainderWord = SEGMENTS[remainder]
116
+ // Insert "and" if remainder doesn't have hundred
117
+ if (!SEGMENTS_HAS_HUNDRED[remainder]) {
118
+ result += ' and ' + remainderWord
119
+ } else {
120
+ result += ' ' + remainderWord
121
+ }
92
122
  }
93
123
 
94
- // Rule 3: Add "and" before units after hundreds ("one hundred and one")
95
- if (leftNumber >= 100n && rightNumber < 100n) {
96
- return { [`${leftWord} and ${rightWord}`]: leftNumber + rightNumber }
124
+ return result
125
+ }
126
+
127
+ // For numbers >= 1,000,000, use scale decomposition
128
+ return buildLargeNumberWords(n)
129
+ }
130
+
131
+ /**
132
+ * Builds words for numbers >= 1,000,000.
133
+ * Uses BigInt division for faster segment extraction.
134
+ *
135
+ * @param {bigint} n - Number >= 1,000,000
136
+ * @returns {string} English words
137
+ */
138
+ function buildLargeNumberWords (n) {
139
+ // Extract segments using BigInt division (faster than string slicing)
140
+ // Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
141
+ const segments = []
142
+ let temp = n
143
+ while (temp > 0n) {
144
+ segments.push(Number(temp % 1000n))
145
+ temp = temp / 1000n
146
+ }
147
+
148
+ // Find the first (smallest index) non-zero segment - this is processed last
149
+ let firstNonZeroIdx = -1
150
+ for (let i = 0; i < segments.length; i++) {
151
+ if (segments[i] !== 0) {
152
+ firstNonZeroIdx = i
153
+ break
97
154
  }
155
+ }
156
+
157
+ // Build result string directly (avoids intermediate object allocations)
158
+ // Process from most-significant (end) to least-significant (start)
159
+ let result = ''
160
+ let prevWasScale = false
98
161
 
99
- // Rule 4: Multiply when right > left ("one thousand")
100
- if (rightNumber > leftNumber) {
101
- return { [`${leftWord} ${rightWord}`]: leftNumber * rightNumber }
162
+ for (let i = segments.length - 1; i >= 0; i--) {
163
+ const segment = segments[i]
164
+ if (segment === 0) continue
165
+
166
+ const segmentWord = SEGMENTS[segment]
167
+ const hasHundred = SEGMENTS_HAS_HUNDRED[segment]
168
+ const isLastSegment = (i === firstNonZeroIdx)
169
+
170
+ // Add "and" only before FINAL segment if it follows scale and doesn't have hundred
171
+ if (result && isLastSegment && prevWasScale && !hasHundred) {
172
+ result += ' and'
173
+ }
174
+
175
+ // Add segment word
176
+ if (result) result += ' '
177
+ result += segmentWord
178
+
179
+ // Add scale word (i=0 is units, i=1 is thousands, etc.)
180
+ if (i > 0) {
181
+ result += ' ' + SCALES[i - 1]
182
+ prevWasScale = true
183
+ } else {
184
+ prevWasScale = false
102
185
  }
186
+ }
187
+
188
+ return result
189
+ }
103
190
 
104
- return { [`${leftWord} ${rightWord}`]: leftNumber + rightNumber }
191
+ /**
192
+ * Converts decimal digits to English words.
193
+ *
194
+ * @param {string} decimalPart - Decimal digits (without the point)
195
+ * @returns {string} English words for decimal part
196
+ */
197
+ function decimalPartToWords (decimalPart) {
198
+ let result = ''
199
+
200
+ // Handle leading zeros
201
+ let i = 0
202
+ while (i < decimalPart.length && decimalPart[i] === '0') {
203
+ if (result) result += ' '
204
+ result += ZERO
205
+ i++
105
206
  }
207
+
208
+ // Convert remainder as a single number
209
+ const remainder = decimalPart.slice(i)
210
+ if (remainder) {
211
+ if (result) result += ' '
212
+ result += integerToWords(BigInt(remainder))
213
+ }
214
+
215
+ return result
106
216
  }
107
217
 
108
218
  /**
109
- * Converts a number to English cardinal (written) form.
219
+ * Converts a numeric value to English words.
110
220
  *
111
- * @param {number|string|bigint} value The number to convert.
112
- * @param {Object} [options] Conversion options (see English class options).
113
- * @returns {string} The number expressed in English words.
114
- * @throws {TypeError} If value is NaN or invalid type.
115
- * @throws {Error} If value is an invalid number string.
221
+ * This is the main public API. It accepts any valid numeric input
222
+ * (number, string, or bigint) and handles parsing internally.
223
+ *
224
+ * @param {number | string | bigint} value - The numeric value to convert
225
+ * @returns {string} The number in English words
226
+ * @throws {TypeError} If value is not a valid numeric type
227
+ * @throws {Error} If value is not a valid number format
116
228
  *
117
229
  * @example
118
- * convertToWords(42); // 'forty-two'
119
- * convertToWords('1.5'); // 'one point five'
230
+ * toWords(42) // 'forty-two'
231
+ * toWords(-3.14) // 'minus three point one four'
232
+ * toWords('1000000') // 'one million'
120
233
  */
121
- export default function convertToWords (value, options = {}) {
122
- return new English(options).convertToWords(value)
234
+ function toWords (value) {
235
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
236
+
237
+ let result = ''
238
+
239
+ if (isNegative) {
240
+ result = NEGATIVE + ' '
241
+ }
242
+
243
+ result += integerToWords(integerPart)
244
+
245
+ if (decimalPart) {
246
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
247
+ }
248
+
249
+ return result
123
250
  }
251
+
252
+ // ============================================================================
253
+ // Public API
254
+ // ============================================================================
255
+
256
+ export { toWords }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Converts a numeric value to Spanish words.
3
+ *
4
+ * This is the main public API. It accepts any valid numeric input
5
+ * (number, string, or bigint) and handles parsing internally.
6
+ *
7
+ * @param {number | string | bigint} value - The numeric value to convert
8
+ * @param {Object} [options] - Optional configuration
9
+ * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
10
+ * @returns {string} The number in Spanish words
11
+ * @throws {TypeError} If value is not a valid numeric type
12
+ * @throws {Error} If value is not a valid number format
13
+ *
14
+ * @example
15
+ * toWords(21) // 'veintiuno'
16
+ * toWords(21, {gender: 'feminine'}) // 'veintiuna'
17
+ * toWords(1000000) // 'un millón'
18
+ */
19
+ export function toWords(value: number | string | bigint, options?: {
20
+ gender?: "masculine" | "feminine" | undefined;
21
+ }): string;