n2words 5.0.0 → 5.1.1
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/CHANGELOG.md +133 -40
- package/README.md +6 -4
- package/dist/am-ET.js +2 -2
- package/dist/am-ET.umd.js +2 -2
- package/dist/am-Latn-ET.js +2 -2
- package/dist/am-Latn-ET.umd.js +2 -2
- package/dist/ar-SA.js +2 -2
- package/dist/ar-SA.umd.js +2 -2
- package/dist/az-AZ.js +2 -2
- package/dist/az-AZ.umd.js +2 -2
- package/dist/bn-BD.js +2 -2
- package/dist/bn-BD.umd.js +2 -2
- package/dist/cs-CZ.js +2 -2
- package/dist/cs-CZ.umd.js +2 -2
- package/dist/da-DK.js +2 -2
- package/dist/da-DK.umd.js +2 -2
- package/dist/de-DE.js +2 -2
- package/dist/de-DE.umd.js +2 -2
- package/dist/el-GR.js +2 -2
- package/dist/el-GR.umd.js +2 -2
- package/dist/en-AU.js +2 -2
- package/dist/en-AU.umd.js +2 -2
- package/dist/en-BD.js +2 -2
- package/dist/en-BD.umd.js +2 -2
- package/dist/en-CA.js +2 -2
- package/dist/en-CA.umd.js +2 -2
- package/dist/en-GB.js +2 -2
- package/dist/en-GB.umd.js +2 -2
- package/dist/en-GH.js +2 -2
- package/dist/en-GH.umd.js +2 -2
- package/dist/en-IE.js +2 -2
- package/dist/en-IE.umd.js +2 -2
- package/dist/en-IN.js +2 -2
- package/dist/en-IN.umd.js +2 -2
- package/dist/en-KE.js +2 -2
- package/dist/en-KE.umd.js +2 -2
- package/dist/en-MY.js +2 -2
- package/dist/en-MY.umd.js +2 -2
- package/dist/en-NG.js +2 -2
- package/dist/en-NG.umd.js +2 -2
- package/dist/en-NZ.js +2 -2
- package/dist/en-NZ.umd.js +2 -2
- package/dist/en-PH.js +2 -2
- package/dist/en-PH.umd.js +2 -2
- package/dist/en-PK.js +2 -2
- package/dist/en-PK.umd.js +2 -2
- package/dist/en-SG.js +2 -2
- package/dist/en-SG.umd.js +2 -2
- package/dist/en-US.js +2 -2
- package/dist/en-US.umd.js +2 -2
- package/dist/en-ZA.js +2 -2
- package/dist/en-ZA.umd.js +2 -2
- package/dist/es-ES.js +2 -2
- package/dist/es-ES.umd.js +2 -2
- package/dist/es-MX.js +2 -2
- package/dist/es-MX.umd.js +2 -2
- package/dist/es-US.js +2 -2
- package/dist/es-US.umd.js +2 -2
- package/dist/fa-IR.js +2 -2
- package/dist/fa-IR.umd.js +2 -2
- package/dist/fi-FI.js +2 -2
- package/dist/fi-FI.umd.js +2 -2
- package/dist/fil-PH.js +2 -2
- package/dist/fil-PH.umd.js +2 -2
- package/dist/fr-BE.js +2 -2
- package/dist/fr-BE.umd.js +2 -2
- package/dist/fr-FR.js +2 -2
- package/dist/fr-FR.umd.js +2 -2
- package/dist/gu-IN.js +2 -2
- package/dist/gu-IN.umd.js +2 -2
- package/dist/ha-NG.js +2 -2
- package/dist/ha-NG.umd.js +2 -2
- package/dist/hbo-IL.js +2 -2
- package/dist/hbo-IL.umd.js +2 -2
- package/dist/he-IL.js +2 -2
- package/dist/he-IL.umd.js +2 -2
- package/dist/hi-IN.js +2 -2
- package/dist/hi-IN.umd.js +2 -2
- package/dist/hr-HR.js +2 -2
- package/dist/hr-HR.umd.js +2 -2
- package/dist/hu-HU.js +2 -2
- package/dist/hu-HU.umd.js +2 -2
- package/dist/id-ID.js +2 -2
- package/dist/id-ID.umd.js +2 -2
- package/dist/it-IT.js +2 -2
- package/dist/it-IT.umd.js +2 -2
- package/dist/ja-JP.js +2 -2
- package/dist/ja-JP.umd.js +2 -2
- package/dist/ka-GE.js +2 -2
- package/dist/ka-GE.umd.js +2 -2
- package/dist/kn-IN.js +2 -2
- package/dist/kn-IN.umd.js +2 -2
- package/dist/ko-KR.js +2 -2
- package/dist/ko-KR.umd.js +2 -2
- package/dist/lt-LT.js +2 -2
- package/dist/lt-LT.umd.js +2 -2
- package/dist/lv-LV.js +2 -2
- package/dist/lv-LV.umd.js +2 -2
- package/dist/mr-IN.js +2 -2
- package/dist/mr-IN.umd.js +2 -2
- package/dist/ms-MY.js +2 -2
- package/dist/ms-MY.umd.js +2 -2
- package/dist/nb-NO.js +2 -2
- package/dist/nb-NO.umd.js +2 -2
- package/dist/nl-NL.js +2 -2
- package/dist/nl-NL.umd.js +2 -2
- package/dist/pa-IN.js +2 -2
- package/dist/pa-IN.umd.js +2 -2
- package/dist/pl-PL.js +2 -2
- package/dist/pl-PL.umd.js +2 -2
- package/dist/pt-BR.js +2 -2
- package/dist/pt-BR.umd.js +2 -2
- package/dist/pt-PT.js +2 -2
- package/dist/pt-PT.umd.js +2 -2
- package/dist/ro-RO.js +2 -2
- package/dist/ro-RO.umd.js +2 -2
- package/dist/ru-RU.js +2 -2
- package/dist/ru-RU.umd.js +2 -2
- package/dist/sr-Cyrl-RS.js +2 -2
- package/dist/sr-Cyrl-RS.umd.js +2 -2
- package/dist/sr-Latn-RS.js +2 -2
- package/dist/sr-Latn-RS.umd.js +2 -2
- package/dist/sv-SE.js +2 -2
- package/dist/sv-SE.umd.js +2 -2
- package/dist/sw-KE.js +2 -2
- package/dist/sw-KE.umd.js +2 -2
- package/dist/ta-IN.js +2 -2
- package/dist/ta-IN.umd.js +2 -2
- package/dist/te-IN.js +2 -2
- package/dist/te-IN.umd.js +2 -2
- package/dist/th-TH.js +2 -2
- package/dist/th-TH.umd.js +2 -2
- package/dist/tr-TR.js +2 -2
- package/dist/tr-TR.umd.js +2 -2
- package/dist/uk-UA.js +2 -2
- package/dist/uk-UA.umd.js +2 -2
- package/dist/ur-PK.js +2 -2
- package/dist/ur-PK.umd.js +2 -2
- package/dist/vi-VN.js +2 -2
- package/dist/vi-VN.umd.js +2 -2
- package/dist/yo-NG.js +2 -2
- package/dist/yo-NG.umd.js +2 -2
- package/dist/zh-Hans-CN.js +2 -2
- package/dist/zh-Hans-CN.umd.js +2 -2
- package/dist/zh-Hant-TW.js +2 -2
- package/dist/zh-Hant-TW.umd.js +2 -2
- package/package.json +33 -24
- package/src/am-ET.d.ts +3 -5
- package/src/am-ET.js +41 -16
- package/src/am-Latn-ET.d.ts +3 -5
- package/src/am-Latn-ET.js +45 -16
- package/src/ar-SA.d.ts +44 -18
- package/src/ar-SA.js +93 -40
- package/src/az-AZ.d.ts +3 -5
- package/src/az-AZ.js +58 -20
- package/src/bn-BD.d.ts +3 -5
- package/src/bn-BD.js +32 -16
- package/src/cs-CZ.d.ts +3 -6
- package/src/cs-CZ.js +66 -42
- package/src/da-DK.d.ts +3 -6
- package/src/da-DK.js +53 -48
- package/src/de-DE.d.ts +17 -11
- package/src/de-DE.js +88 -57
- package/src/el-GR.d.ts +3 -6
- package/src/el-GR.js +45 -32
- package/src/en-AU.d.ts +17 -11
- package/src/en-AU.js +56 -41
- package/src/en-BD.d.ts +17 -11
- package/src/en-BD.js +60 -41
- package/src/en-CA.d.ts +36 -18
- package/src/en-CA.js +67 -46
- package/src/en-GB.d.ts +17 -11
- package/src/en-GB.js +56 -41
- package/src/en-GH.d.ts +32 -3
- package/src/en-GH.js +104 -26
- package/src/en-IE.d.ts +17 -11
- package/src/en-IE.js +56 -41
- package/src/en-IN.d.ts +17 -11
- package/src/en-IN.js +60 -41
- package/src/en-KE.d.ts +28 -3
- package/src/en-KE.js +93 -26
- package/src/en-MY.d.ts +26 -3
- package/src/en-MY.js +91 -26
- package/src/en-NG.d.ts +17 -11
- package/src/en-NG.js +56 -41
- package/src/en-NZ.d.ts +32 -3
- package/src/en-NZ.js +85 -31
- package/src/en-PH.d.ts +32 -3
- package/src/en-PH.js +97 -26
- package/src/en-PK.d.ts +17 -11
- package/src/en-PK.js +60 -41
- package/src/en-SG.d.ts +28 -3
- package/src/en-SG.js +93 -26
- package/src/en-US.d.ts +36 -18
- package/src/en-US.js +70 -47
- package/src/en-ZA.d.ts +17 -11
- package/src/en-ZA.js +56 -41
- package/src/es-ES.d.ts +53 -21
- package/src/es-ES.js +104 -56
- package/src/es-MX.d.ts +53 -21
- package/src/es-MX.js +104 -56
- package/src/es-US.d.ts +53 -21
- package/src/es-US.js +92 -51
- package/src/fa-IR.d.ts +3 -5
- package/src/fa-IR.js +28 -13
- package/src/fi-FI.d.ts +3 -6
- package/src/fi-FI.js +47 -29
- package/src/fil-PH.d.ts +3 -5
- package/src/fil-PH.js +61 -28
- package/src/fr-BE.d.ts +31 -15
- package/src/fr-BE.js +128 -57
- package/src/fr-FR.d.ts +31 -16
- package/src/fr-FR.js +97 -60
- package/src/gu-IN.d.ts +3 -5
- package/src/gu-IN.js +31 -16
- package/src/ha-NG.d.ts +3 -5
- package/src/ha-NG.js +55 -27
- package/src/hbo-IL.d.ts +26 -12
- package/src/hbo-IL.js +92 -51
- package/src/he-IL.d.ts +17 -10
- package/src/he-IL.js +92 -50
- package/src/hi-IN.d.ts +3 -5
- package/src/hi-IN.js +30 -17
- package/src/hr-HR.d.ts +21 -10
- package/src/hr-HR.js +89 -33
- package/src/hu-HU.d.ts +3 -5
- package/src/hu-HU.js +57 -23
- package/src/id-ID.d.ts +3 -5
- package/src/id-ID.js +56 -23
- package/src/it-IT.d.ts +17 -11
- package/src/it-IT.js +74 -43
- package/src/ja-JP.d.ts +3 -6
- package/src/ja-JP.js +39 -26
- package/src/ka-GE.d.ts +3 -6
- package/src/ka-GE.js +38 -26
- package/src/kn-IN.d.ts +3 -5
- package/src/kn-IN.js +31 -16
- package/src/ko-KR.d.ts +3 -6
- package/src/ko-KR.js +34 -26
- package/src/lt-LT.d.ts +21 -11
- package/src/lt-LT.js +64 -42
- package/src/lv-LV.d.ts +21 -11
- package/src/lv-LV.js +79 -51
- package/src/mr-IN.d.ts +3 -5
- package/src/mr-IN.js +31 -16
- package/src/ms-MY.d.ts +3 -5
- package/src/ms-MY.js +58 -24
- package/src/nb-NO.d.ts +3 -6
- package/src/nb-NO.js +54 -34
- package/src/nl-NL.d.ts +41 -20
- package/src/nl-NL.js +111 -69
- package/src/pa-IN.d.ts +3 -5
- package/src/pa-IN.js +32 -16
- package/src/pl-PL.d.ts +21 -11
- package/src/pl-PL.js +69 -45
- package/src/pt-BR.d.ts +22 -11
- package/src/pt-BR.js +93 -53
- package/src/pt-PT.d.ts +17 -11
- package/src/pt-PT.js +80 -48
- package/src/ro-RO.d.ts +21 -11
- package/src/ro-RO.js +77 -39
- package/src/ru-RU.d.ts +35 -15
- package/src/ru-RU.js +100 -38
- package/src/sr-Cyrl-RS.d.ts +35 -15
- package/src/sr-Cyrl-RS.js +106 -43
- package/src/sr-Latn-RS.d.ts +35 -15
- package/src/sr-Latn-RS.js +106 -43
- package/src/sv-SE.d.ts +3 -6
- package/src/sv-SE.js +53 -34
- package/src/sw-KE.d.ts +3 -5
- package/src/sw-KE.js +50 -20
- package/src/ta-IN.d.ts +3 -5
- package/src/ta-IN.js +29 -17
- package/src/te-IN.d.ts +3 -5
- package/src/te-IN.js +31 -16
- package/src/th-TH.d.ts +3 -5
- package/src/th-TH.js +42 -19
- package/src/tr-TR.d.ts +17 -11
- package/src/tr-TR.js +63 -37
- package/src/uk-UA.d.ts +21 -10
- package/src/uk-UA.js +89 -33
- package/src/ur-PK.d.ts +3 -5
- package/src/ur-PK.js +32 -16
- package/src/utils/check-max.d.ts +26 -0
- package/src/utils/check-max.js +33 -0
- package/src/utils/expand-scientific.d.ts +0 -4
- package/src/utils/expand-scientific.js +7 -9
- package/src/utils/is-plain-object.d.ts +3 -4
- package/src/utils/is-plain-object.js +3 -4
- package/src/utils/parse-cardinal.d.ts +1 -2
- package/src/utils/parse-cardinal.js +12 -9
- package/src/utils/parse-currency.d.ts +1 -2
- package/src/utils/parse-currency.js +9 -11
- package/src/utils/parse-ordinal.d.ts +0 -1
- package/src/utils/parse-ordinal.js +9 -10
- package/src/utils/resolve-options.d.ts +17 -0
- package/src/utils/resolve-options.js +56 -0
- package/src/utils/scale.d.ts +49 -0
- package/src/utils/scale.js +65 -0
- package/src/vi-VN.d.ts +3 -6
- package/src/vi-VN.js +41 -28
- package/src/yo-NG.d.ts +3 -5
- package/src/yo-NG.js +49 -33
- package/src/zh-Hans-CN.d.ts +45 -20
- package/src/zh-Hans-CN.js +84 -31
- package/src/zh-Hant-TW.d.ts +45 -20
- package/src/zh-Hant-TW.js +85 -34
- package/src/utils/validate-options.d.ts +0 -8
- package/src/utils/validate-options.js +0 -16
package/src/en-SG.js
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
import { parseCardinalValue } from './utils/parse-cardinal.js'
|
|
21
21
|
import { parseCurrencyValue } from './utils/parse-currency.js'
|
|
22
22
|
import { parseOrdinalValue } from './utils/parse-ordinal.js'
|
|
23
|
-
import {
|
|
23
|
+
import { checkMax } from './utils/check-max.js'
|
|
24
|
+
import { western } from './utils/scale.js'
|
|
25
|
+
import { resolveOptions } from './utils/resolve-options.js'
|
|
24
26
|
|
|
25
27
|
// ============================================================================
|
|
26
28
|
// Vocabulary (module-level constants)
|
|
@@ -35,9 +37,13 @@ const SCALES = [
|
|
|
35
37
|
'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion',
|
|
36
38
|
'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion',
|
|
37
39
|
'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'novemdecillion',
|
|
38
|
-
'vigintillion'
|
|
40
|
+
'vigintillion',
|
|
39
41
|
]
|
|
40
42
|
|
|
43
|
+
export const cardinalMax = western(SCALES.length)
|
|
44
|
+
export const ordinalMax = western(SCALES.length)
|
|
45
|
+
export const currencyMax = western(SCALES.length)
|
|
46
|
+
|
|
41
47
|
const HUNDRED = 'hundred'
|
|
42
48
|
const ZERO = 'zero'
|
|
43
49
|
const NEGATIVE = 'minus'
|
|
@@ -60,7 +66,11 @@ const CENTS = 'cents'
|
|
|
60
66
|
|
|
61
67
|
const segmentResult = { word: '', hasHundred: false }
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
/**
|
|
70
|
+
* @param {number} n - Number 0-999
|
|
71
|
+
* @returns {{ word: string, hasHundred: boolean }} The segment words and whether it contains a hundreds part
|
|
72
|
+
*/
|
|
73
|
+
function buildSegment(n) {
|
|
64
74
|
if (n === 0) {
|
|
65
75
|
segmentResult.word = ''
|
|
66
76
|
segmentResult.hasHundred = false
|
|
@@ -74,20 +84,24 @@ function buildSegment (n) {
|
|
|
74
84
|
let tensOnes = ''
|
|
75
85
|
if (tens === 1) {
|
|
76
86
|
tensOnes = TEENS[ones]
|
|
77
|
-
}
|
|
87
|
+
}
|
|
88
|
+
else if (tens >= 2) {
|
|
78
89
|
tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
|
|
79
|
-
}
|
|
90
|
+
}
|
|
91
|
+
else if (ones > 0) {
|
|
80
92
|
tensOnes = ONES[ones]
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
if (hundreds > 0) {
|
|
84
96
|
if (tensOnes) {
|
|
85
97
|
segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
|
|
86
|
-
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
87
100
|
segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
|
|
88
101
|
}
|
|
89
102
|
segmentResult.hasHundred = true
|
|
90
|
-
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
91
105
|
segmentResult.word = tensOnes
|
|
92
106
|
segmentResult.hasHundred = false
|
|
93
107
|
}
|
|
@@ -99,7 +113,11 @@ function buildSegment (n) {
|
|
|
99
113
|
// Conversion Functions
|
|
100
114
|
// ============================================================================
|
|
101
115
|
|
|
102
|
-
|
|
116
|
+
/**
|
|
117
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
118
|
+
* @returns {string} English words
|
|
119
|
+
*/
|
|
120
|
+
function integerToWords(n) {
|
|
103
121
|
if (n === 0n) return ZERO
|
|
104
122
|
|
|
105
123
|
if (n < 1000n) {
|
|
@@ -124,7 +142,11 @@ function integerToWords (n) {
|
|
|
124
142
|
return buildLargeNumberWords(n)
|
|
125
143
|
}
|
|
126
144
|
|
|
127
|
-
|
|
145
|
+
/**
|
|
146
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
147
|
+
* @returns {string} English words
|
|
148
|
+
*/
|
|
149
|
+
function buildLargeNumberWords(n) {
|
|
128
150
|
const segments = []
|
|
129
151
|
let temp = n
|
|
130
152
|
while (temp > 0n) {
|
|
@@ -160,7 +182,8 @@ function buildLargeNumberWords (n) {
|
|
|
160
182
|
if (i > 0) {
|
|
161
183
|
result += ' ' + SCALES[i - 1]
|
|
162
184
|
prevWasScale = true
|
|
163
|
-
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
164
187
|
prevWasScale = false
|
|
165
188
|
}
|
|
166
189
|
}
|
|
@@ -168,7 +191,11 @@ function buildLargeNumberWords (n) {
|
|
|
168
191
|
return result
|
|
169
192
|
}
|
|
170
193
|
|
|
171
|
-
|
|
194
|
+
/**
|
|
195
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
196
|
+
* @returns {string} English words for decimal part
|
|
197
|
+
*/
|
|
198
|
+
function decimalPartToWords(decimalPart) {
|
|
172
199
|
let result = ''
|
|
173
200
|
|
|
174
201
|
let i = 0
|
|
@@ -189,14 +216,16 @@ function decimalPartToWords (decimalPart) {
|
|
|
189
216
|
|
|
190
217
|
/**
|
|
191
218
|
* Converts a numeric value to Singaporean English words.
|
|
192
|
-
*
|
|
193
219
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
194
220
|
* @returns {string} The number in English words
|
|
195
221
|
* @throws {TypeError} If value is not a valid numeric type
|
|
196
222
|
* @throws {Error} If value is not a valid number format
|
|
197
223
|
*/
|
|
198
|
-
function toCardinal
|
|
224
|
+
function toCardinal(value) {
|
|
199
225
|
const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
|
|
226
|
+
// Both the integer part and the decimal's significant digits are spelled via
|
|
227
|
+
// the scale builder, so both must clear the ceiling.
|
|
228
|
+
checkMax(integerPart, cardinalMax, decimalPart)
|
|
200
229
|
|
|
201
230
|
let result = ''
|
|
202
231
|
|
|
@@ -217,7 +246,11 @@ function toCardinal (value) {
|
|
|
217
246
|
// ORDINAL
|
|
218
247
|
// ============================================================================
|
|
219
248
|
|
|
220
|
-
|
|
249
|
+
/**
|
|
250
|
+
* @param {number} n - Number 0-999
|
|
251
|
+
* @returns {string} Ordinal words for this segment
|
|
252
|
+
*/
|
|
253
|
+
function buildOrdinalSegment(n) {
|
|
221
254
|
const ones = n % 10
|
|
222
255
|
const tens = Math.trunc(n / 10) % 10
|
|
223
256
|
const hundreds = Math.trunc(n / 100)
|
|
@@ -225,20 +258,24 @@ function buildOrdinalSegment (n) {
|
|
|
225
258
|
let tensOnesOrdinal = ''
|
|
226
259
|
if (tens === 1) {
|
|
227
260
|
tensOnesOrdinal = ORDINAL_TEENS[ones]
|
|
228
|
-
}
|
|
261
|
+
}
|
|
262
|
+
else if (tens >= 2) {
|
|
229
263
|
if (ones > 0) {
|
|
230
264
|
tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
|
|
231
|
-
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
232
267
|
tensOnesOrdinal = ORDINAL_TENS[tens]
|
|
233
268
|
}
|
|
234
|
-
}
|
|
269
|
+
}
|
|
270
|
+
else if (ones > 0) {
|
|
235
271
|
tensOnesOrdinal = ORDINAL_ONES[ones]
|
|
236
272
|
}
|
|
237
273
|
|
|
238
274
|
if (hundreds > 0) {
|
|
239
275
|
if (tensOnesOrdinal) {
|
|
240
276
|
return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
|
|
241
|
-
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
242
279
|
return ONES[hundreds] + ' hundredth'
|
|
243
280
|
}
|
|
244
281
|
}
|
|
@@ -246,7 +283,11 @@ function buildOrdinalSegment (n) {
|
|
|
246
283
|
return tensOnesOrdinal
|
|
247
284
|
}
|
|
248
285
|
|
|
249
|
-
|
|
286
|
+
/**
|
|
287
|
+
* @param {bigint} n - Positive integer to convert
|
|
288
|
+
* @returns {string} Ordinal English words
|
|
289
|
+
*/
|
|
290
|
+
function integerToOrdinal(n) {
|
|
250
291
|
if (n < 1000n) {
|
|
251
292
|
return buildOrdinalSegment(Number(n))
|
|
252
293
|
}
|
|
@@ -266,7 +307,11 @@ function integerToOrdinal (n) {
|
|
|
266
307
|
return buildLargeOrdinal(n)
|
|
267
308
|
}
|
|
268
309
|
|
|
269
|
-
|
|
310
|
+
/**
|
|
311
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
312
|
+
* @returns {string} Ordinal English words
|
|
313
|
+
*/
|
|
314
|
+
function buildLargeOrdinal(n) {
|
|
270
315
|
const segments = []
|
|
271
316
|
let temp = n
|
|
272
317
|
while (temp > 0n) {
|
|
@@ -295,10 +340,12 @@ function buildLargeOrdinal (n) {
|
|
|
295
340
|
if (isLowestSegment) {
|
|
296
341
|
if (i === 0) {
|
|
297
342
|
result += buildOrdinalSegment(segment)
|
|
298
|
-
}
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
299
345
|
result += buildSegment(segment).word + ' ' + SCALES[i - 1] + 'th'
|
|
300
346
|
}
|
|
301
|
-
}
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
302
349
|
result += buildSegment(segment).word
|
|
303
350
|
if (i > 0) {
|
|
304
351
|
result += ' ' + SCALES[i - 1]
|
|
@@ -309,8 +356,14 @@ function buildLargeOrdinal (n) {
|
|
|
309
356
|
return result
|
|
310
357
|
}
|
|
311
358
|
|
|
312
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Converts a numeric value to Singaporean English ordinal words.
|
|
361
|
+
* @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
|
|
362
|
+
* @returns {string} The number as ordinal words (e.g., "first", "forty-second")
|
|
363
|
+
*/
|
|
364
|
+
function toOrdinal(value) {
|
|
313
365
|
const integerPart = parseOrdinalValue(value)
|
|
366
|
+
checkMax(integerPart, ordinalMax)
|
|
314
367
|
return integerToOrdinal(integerPart)
|
|
315
368
|
}
|
|
316
369
|
|
|
@@ -318,10 +371,24 @@ function toOrdinal (value) {
|
|
|
318
371
|
// CURRENCY
|
|
319
372
|
// ============================================================================
|
|
320
373
|
|
|
321
|
-
|
|
322
|
-
|
|
374
|
+
/**
|
|
375
|
+
* @typedef {object} CurrencyOptions
|
|
376
|
+
* @property {boolean} [and] - Use "and" between dollars and cents
|
|
377
|
+
*/
|
|
378
|
+
|
|
379
|
+
/** @type {Required<CurrencyOptions>} */
|
|
380
|
+
export const currencyDefaults = { and: true }
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Converts a numeric value to Singaporean English currency words.
|
|
384
|
+
* @param {number | string | bigint} value - The currency amount to convert
|
|
385
|
+
* @param {CurrencyOptions} [options] - Optional configuration
|
|
386
|
+
* @returns {string} The amount in Singaporean English currency words
|
|
387
|
+
*/
|
|
388
|
+
function toCurrency(value, options) {
|
|
323
389
|
const { isNegative, dollars, cents } = parseCurrencyValue(value)
|
|
324
|
-
|
|
390
|
+
checkMax(dollars, currencyMax)
|
|
391
|
+
const { and: useAnd } = resolveOptions(options, currencyDefaults)
|
|
325
392
|
|
|
326
393
|
let result = ''
|
|
327
394
|
if (isNegative) result = NEGATIVE + ' '
|
package/src/en-US.d.ts
CHANGED
|
@@ -1,17 +1,45 @@
|
|
|
1
|
+
export const cardinalMax: bigint;
|
|
2
|
+
export const ordinalMax: bigint;
|
|
3
|
+
export const currencyMax: bigint;
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} CardinalOptions
|
|
6
|
+
* @property {boolean} [hundredPairing] - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
|
|
7
|
+
* @property {boolean} [and] - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
|
|
8
|
+
*/
|
|
9
|
+
/** @type {Required<CardinalOptions>} */
|
|
10
|
+
export const cardinalDefaults: Required<CardinalOptions>;
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} CurrencyOptions
|
|
13
|
+
* @property {boolean} [and] - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
|
|
14
|
+
*/
|
|
15
|
+
/** @type {Required<CurrencyOptions>} */
|
|
16
|
+
export const currencyDefaults: Required<CurrencyOptions>;
|
|
17
|
+
export type CardinalOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
|
|
20
|
+
*/
|
|
21
|
+
hundredPairing?: boolean | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
|
|
24
|
+
*/
|
|
25
|
+
and?: boolean | undefined;
|
|
26
|
+
};
|
|
27
|
+
export type CurrencyOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
|
|
30
|
+
*/
|
|
31
|
+
and?: boolean | undefined;
|
|
32
|
+
};
|
|
1
33
|
/**
|
|
2
34
|
* Converts a numeric value to American English words.
|
|
3
35
|
*
|
|
4
36
|
* This is the main public API. It accepts any valid numeric input
|
|
5
37
|
* (number, string, or bigint) and handles parsing internally.
|
|
6
|
-
*
|
|
7
38
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
8
|
-
* @param {
|
|
9
|
-
* @param {boolean} [options.hundredPairing=false] - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
|
|
10
|
-
* @param {boolean} [options.and=false] - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
|
|
39
|
+
* @param {CardinalOptions} [options] - Optional configuration
|
|
11
40
|
* @returns {string} The number in American English words
|
|
12
41
|
* @throws {TypeError} If value is not a valid numeric type
|
|
13
42
|
* @throws {Error} If value is not a valid number format
|
|
14
|
-
*
|
|
15
43
|
* @example
|
|
16
44
|
* toCardinal(42) // 'forty-two'
|
|
17
45
|
* toCardinal(101) // 'one hundred one'
|
|
@@ -19,18 +47,13 @@
|
|
|
19
47
|
* toCardinal(1500) // 'one thousand five hundred'
|
|
20
48
|
* toCardinal(1500, { hundredPairing: true }) // 'fifteen hundred'
|
|
21
49
|
*/
|
|
22
|
-
export function toCardinal(value: number | string | bigint, options?:
|
|
23
|
-
hundredPairing?: boolean | undefined;
|
|
24
|
-
and?: boolean | undefined;
|
|
25
|
-
}): string;
|
|
50
|
+
export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
|
|
26
51
|
/**
|
|
27
52
|
* Converts a numeric value to American English ordinal words.
|
|
28
|
-
*
|
|
29
53
|
* @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
|
|
30
54
|
* @returns {string} The number as ordinal words (e.g., "first", "forty-second")
|
|
31
55
|
* @throws {TypeError} If value is not a valid numeric type
|
|
32
56
|
* @throws {RangeError} If value is negative, zero, or has a decimal part
|
|
33
|
-
*
|
|
34
57
|
* @example
|
|
35
58
|
* toOrdinal(1) // 'first'
|
|
36
59
|
* toOrdinal(2) // 'second'
|
|
@@ -44,20 +67,15 @@ export function toCardinal(value: number | string | bigint, options?: {
|
|
|
44
67
|
export function toOrdinal(value: number | string | bigint): string;
|
|
45
68
|
/**
|
|
46
69
|
* Converts a numeric value to American English currency words.
|
|
47
|
-
*
|
|
48
70
|
* @param {number | string | bigint} value - The currency amount to convert
|
|
49
|
-
* @param {
|
|
50
|
-
* @param {boolean} [options.and=true] - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
|
|
71
|
+
* @param {CurrencyOptions} [options] - Optional configuration
|
|
51
72
|
* @returns {string} The amount in American English currency words
|
|
52
73
|
* @throws {TypeError} If value is not a valid numeric type
|
|
53
74
|
* @throws {Error} If value is not a valid number format
|
|
54
|
-
*
|
|
55
75
|
* @example
|
|
56
76
|
* toCurrency(42.50) // 'forty-two dollars and fifty cents'
|
|
57
77
|
* toCurrency(1) // 'one dollar'
|
|
58
78
|
* toCurrency(0.99) // 'ninety-nine cents'
|
|
59
79
|
* toCurrency(42.50, { and: false }) // 'forty-two dollars fifty cents'
|
|
60
80
|
*/
|
|
61
|
-
export function toCurrency(value: number | string | bigint, options?:
|
|
62
|
-
and?: boolean | undefined;
|
|
63
|
-
}): string;
|
|
81
|
+
export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;
|
package/src/en-US.js
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
import { parseCardinalValue } from './utils/parse-cardinal.js'
|
|
21
21
|
import { parseCurrencyValue } from './utils/parse-currency.js'
|
|
22
22
|
import { parseOrdinalValue } from './utils/parse-ordinal.js'
|
|
23
|
-
import {
|
|
23
|
+
import { checkMax } from './utils/check-max.js'
|
|
24
|
+
import { western } from './utils/scale.js'
|
|
25
|
+
import { resolveOptions } from './utils/resolve-options.js'
|
|
24
26
|
|
|
25
27
|
// ============================================================================
|
|
26
28
|
// VOCABULARY
|
|
@@ -35,8 +37,15 @@ const SCALES = [
|
|
|
35
37
|
'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion',
|
|
36
38
|
'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion',
|
|
37
39
|
'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'novemdecillion',
|
|
38
|
-
'vigintillion'
|
|
40
|
+
'vigintillion',
|
|
39
41
|
]
|
|
42
|
+
|
|
43
|
+
// Each form's maximum supported value (a bigint), derived from the scale table
|
|
44
|
+
// so it can't drift. Exported per form so they split cleanly and the gate/docs
|
|
45
|
+
// read each as a fact. en-US shares one ceiling across all three forms.
|
|
46
|
+
export const cardinalMax = western(SCALES.length)
|
|
47
|
+
export const ordinalMax = western(SCALES.length)
|
|
48
|
+
export const currencyMax = western(SCALES.length)
|
|
40
49
|
const HUNDRED = 'hundred'
|
|
41
50
|
const ZERO = 'zero'
|
|
42
51
|
const NEGATIVE = 'minus'
|
|
@@ -62,12 +71,11 @@ const segmentResult = { word: '', hasHundred: false }
|
|
|
62
71
|
|
|
63
72
|
/**
|
|
64
73
|
* Builds words for a 0-999 segment.
|
|
65
|
-
*
|
|
66
74
|
* @param {number} n - Number 0-999
|
|
67
75
|
* @param {boolean} useAnd - Whether to use "and" after hundreds
|
|
68
|
-
* @returns {{ word: string, hasHundred: boolean }}
|
|
76
|
+
* @returns {{ word: string, hasHundred: boolean }} The segment words and whether a hundreds place was used
|
|
69
77
|
*/
|
|
70
|
-
function buildSegment
|
|
78
|
+
function buildSegment(n, useAnd) {
|
|
71
79
|
if (n === 0) {
|
|
72
80
|
segmentResult.word = ''
|
|
73
81
|
segmentResult.hasHundred = false
|
|
@@ -82,9 +90,11 @@ function buildSegment (n, useAnd) {
|
|
|
82
90
|
let tensOnes = ''
|
|
83
91
|
if (tens === 1) {
|
|
84
92
|
tensOnes = TEENS[ones]
|
|
85
|
-
}
|
|
93
|
+
}
|
|
94
|
+
else if (tens >= 2) {
|
|
86
95
|
tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
|
|
87
|
-
}
|
|
96
|
+
}
|
|
97
|
+
else if (ones > 0) {
|
|
88
98
|
tensOnes = ONES[ones]
|
|
89
99
|
}
|
|
90
100
|
|
|
@@ -93,11 +103,13 @@ function buildSegment (n, useAnd) {
|
|
|
93
103
|
if (tensOnes) {
|
|
94
104
|
const connector = useAnd ? ' and ' : ' '
|
|
95
105
|
segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + connector + tensOnes
|
|
96
|
-
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
97
108
|
segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
|
|
98
109
|
}
|
|
99
110
|
segmentResult.hasHundred = true
|
|
100
|
-
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
101
113
|
segmentResult.word = tensOnes
|
|
102
114
|
segmentResult.hasHundred = false
|
|
103
115
|
}
|
|
@@ -111,13 +123,12 @@ function buildSegment (n, useAnd) {
|
|
|
111
123
|
|
|
112
124
|
/**
|
|
113
125
|
* Converts a non-negative integer to English words.
|
|
114
|
-
*
|
|
115
126
|
* @param {bigint} n - Non-negative integer to convert
|
|
116
127
|
* @param {boolean} hundredPairing - Use hundred-pairing for 1100-9999
|
|
117
128
|
* @param {boolean} useAnd - Use "and" after hundreds and before final segment
|
|
118
129
|
* @returns {string} English words
|
|
119
130
|
*/
|
|
120
|
-
function integerToWords
|
|
131
|
+
function integerToWords(n, hundredPairing, useAnd) {
|
|
121
132
|
if (n === 0n) return ZERO
|
|
122
133
|
|
|
123
134
|
// Fast path: numbers < 1000
|
|
@@ -173,12 +184,11 @@ function integerToWords (n, hundredPairing, useAnd) {
|
|
|
173
184
|
/**
|
|
174
185
|
* Builds words for numbers >= 1,000,000.
|
|
175
186
|
* Uses BigInt division for faster segment extraction.
|
|
176
|
-
*
|
|
177
187
|
* @param {bigint} n - Number >= 1,000,000
|
|
178
188
|
* @param {boolean} useAnd - Use "and" after hundreds and before final segment
|
|
179
189
|
* @returns {string} English words
|
|
180
190
|
*/
|
|
181
|
-
function buildLargeNumberWords
|
|
191
|
+
function buildLargeNumberWords(n, useAnd) {
|
|
182
192
|
// Extract segments using BigInt division
|
|
183
193
|
// Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
|
|
184
194
|
const segments = []
|
|
@@ -223,7 +233,8 @@ function buildLargeNumberWords (n, useAnd) {
|
|
|
223
233
|
if (i > 0) {
|
|
224
234
|
result += ' ' + SCALES[i - 1]
|
|
225
235
|
prevWasScale = true
|
|
226
|
-
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
227
238
|
prevWasScale = false
|
|
228
239
|
}
|
|
229
240
|
}
|
|
@@ -233,12 +244,11 @@ function buildLargeNumberWords (n, useAnd) {
|
|
|
233
244
|
|
|
234
245
|
/**
|
|
235
246
|
* Converts decimal digits to English words.
|
|
236
|
-
*
|
|
237
247
|
* @param {string} decimalPart - Decimal digits (without the point)
|
|
238
248
|
* @param {boolean} useAnd - Use "and" in number conversion
|
|
239
249
|
* @returns {string} English words for decimal part
|
|
240
250
|
*/
|
|
241
|
-
function decimalPartToWords
|
|
251
|
+
function decimalPartToWords(decimalPart, useAnd) {
|
|
242
252
|
let result = ''
|
|
243
253
|
|
|
244
254
|
// Handle leading zeros
|
|
@@ -259,20 +269,25 @@ function decimalPartToWords (decimalPart, useAnd) {
|
|
|
259
269
|
return result
|
|
260
270
|
}
|
|
261
271
|
|
|
272
|
+
/**
|
|
273
|
+
* @typedef {object} CardinalOptions
|
|
274
|
+
* @property {boolean} [hundredPairing] - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
|
|
275
|
+
* @property {boolean} [and] - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
/** @type {Required<CardinalOptions>} */
|
|
279
|
+
export const cardinalDefaults = { hundredPairing: false, and: false }
|
|
280
|
+
|
|
262
281
|
/**
|
|
263
282
|
* Converts a numeric value to American English words.
|
|
264
283
|
*
|
|
265
284
|
* This is the main public API. It accepts any valid numeric input
|
|
266
285
|
* (number, string, or bigint) and handles parsing internally.
|
|
267
|
-
*
|
|
268
286
|
* @param {number | string | bigint} value - The numeric value to convert
|
|
269
|
-
* @param {
|
|
270
|
-
* @param {boolean} [options.hundredPairing=false] - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
|
|
271
|
-
* @param {boolean} [options.and=false] - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
|
|
287
|
+
* @param {CardinalOptions} [options] - Optional configuration
|
|
272
288
|
* @returns {string} The number in American English words
|
|
273
289
|
* @throws {TypeError} If value is not a valid numeric type
|
|
274
290
|
* @throws {Error} If value is not a valid number format
|
|
275
|
-
*
|
|
276
291
|
* @example
|
|
277
292
|
* toCardinal(42) // 'forty-two'
|
|
278
293
|
* toCardinal(101) // 'one hundred one'
|
|
@@ -280,12 +295,13 @@ function decimalPartToWords (decimalPart, useAnd) {
|
|
|
280
295
|
* toCardinal(1500) // 'one thousand five hundred'
|
|
281
296
|
* toCardinal(1500, { hundredPairing: true }) // 'fifteen hundred'
|
|
282
297
|
*/
|
|
283
|
-
function toCardinal
|
|
284
|
-
options = validateOptions(options)
|
|
298
|
+
function toCardinal(value, options) {
|
|
285
299
|
const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
|
|
300
|
+
// Both the integer part and the decimal's significant digits are spelled via
|
|
301
|
+
// the scale builder, so both must clear the ceiling.
|
|
302
|
+
checkMax(integerPart, cardinalMax, decimalPart)
|
|
286
303
|
|
|
287
|
-
|
|
288
|
-
const { hundredPairing = false, and: useAnd = false } = options
|
|
304
|
+
const { hundredPairing, and: useAnd } = resolveOptions(options, cardinalDefaults)
|
|
289
305
|
|
|
290
306
|
let result = ''
|
|
291
307
|
|
|
@@ -309,11 +325,10 @@ function toCardinal (value, options) {
|
|
|
309
325
|
/**
|
|
310
326
|
* Builds ordinal words for a 0-999 segment (final segment only).
|
|
311
327
|
* Returns ordinal form: "first", "twenty-third", "one hundred forty-fifth"
|
|
312
|
-
*
|
|
313
328
|
* @param {number} n - Number 0-999
|
|
314
329
|
* @returns {string} Ordinal words for this segment
|
|
315
330
|
*/
|
|
316
|
-
function buildOrdinalSegment
|
|
331
|
+
function buildOrdinalSegment(n) {
|
|
317
332
|
const ones = n % 10
|
|
318
333
|
const tens = Math.trunc(n / 10) % 10
|
|
319
334
|
const hundreds = Math.trunc(n / 100)
|
|
@@ -323,15 +338,18 @@ function buildOrdinalSegment (n) {
|
|
|
323
338
|
if (tens === 1) {
|
|
324
339
|
// Teens: 10-19 → "tenth" through "nineteenth"
|
|
325
340
|
tensOnesOrdinal = ORDINAL_TEENS[ones]
|
|
326
|
-
}
|
|
341
|
+
}
|
|
342
|
+
else if (tens >= 2) {
|
|
327
343
|
if (ones > 0) {
|
|
328
344
|
// Compound: "twenty-first", "thirty-second", etc.
|
|
329
345
|
tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
|
|
330
|
-
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
331
348
|
// Round tens: "twentieth", "thirtieth", etc.
|
|
332
349
|
tensOnesOrdinal = ORDINAL_TENS[tens]
|
|
333
350
|
}
|
|
334
|
-
}
|
|
351
|
+
}
|
|
352
|
+
else if (ones > 0) {
|
|
335
353
|
// Single digit: "first", "second", etc.
|
|
336
354
|
tensOnesOrdinal = ORDINAL_ONES[ones]
|
|
337
355
|
}
|
|
@@ -341,7 +359,8 @@ function buildOrdinalSegment (n) {
|
|
|
341
359
|
if (tensOnesOrdinal) {
|
|
342
360
|
// "one hundred twenty-first"
|
|
343
361
|
return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
|
|
344
|
-
}
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
345
364
|
// "one hundredth", "two hundredth", etc.
|
|
346
365
|
return ONES[hundreds] + ' hundredth'
|
|
347
366
|
}
|
|
@@ -353,11 +372,10 @@ function buildOrdinalSegment (n) {
|
|
|
353
372
|
/**
|
|
354
373
|
* Converts a positive integer to ordinal words.
|
|
355
374
|
* Generates ordinals directly without string manipulation.
|
|
356
|
-
*
|
|
357
375
|
* @param {bigint} n - Positive integer to convert
|
|
358
376
|
* @returns {string} Ordinal English words
|
|
359
377
|
*/
|
|
360
|
-
function integerToOrdinal
|
|
378
|
+
function integerToOrdinal(n) {
|
|
361
379
|
// Fast path: numbers < 1000
|
|
362
380
|
if (n < 1000n) {
|
|
363
381
|
return buildOrdinalSegment(Number(n))
|
|
@@ -385,11 +403,10 @@ function integerToOrdinal (n) {
|
|
|
385
403
|
/**
|
|
386
404
|
* Builds ordinal words for numbers >= 1,000,000.
|
|
387
405
|
* All segments except the final one are cardinal; final segment is ordinal.
|
|
388
|
-
*
|
|
389
406
|
* @param {bigint} n - Number >= 1,000,000
|
|
390
407
|
* @returns {string} Ordinal English words
|
|
391
408
|
*/
|
|
392
|
-
function buildLargeOrdinal
|
|
409
|
+
function buildLargeOrdinal(n) {
|
|
393
410
|
// Extract segments (least-significant first)
|
|
394
411
|
const segments = []
|
|
395
412
|
let temp = n
|
|
@@ -423,11 +440,13 @@ function buildLargeOrdinal (n) {
|
|
|
423
440
|
if (i === 0) {
|
|
424
441
|
// Units position: use ordinal segment
|
|
425
442
|
result += buildOrdinalSegment(segment)
|
|
426
|
-
}
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
427
445
|
// Scale position with no remainder below: "one millionth"
|
|
428
446
|
result += buildSegment(segment, false).word + ' ' + SCALES[i - 1] + 'th'
|
|
429
447
|
}
|
|
430
|
-
}
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
431
450
|
// Non-final segments are cardinal
|
|
432
451
|
result += buildSegment(segment, false).word
|
|
433
452
|
if (i > 0) {
|
|
@@ -441,12 +460,10 @@ function buildLargeOrdinal (n) {
|
|
|
441
460
|
|
|
442
461
|
/**
|
|
443
462
|
* Converts a numeric value to American English ordinal words.
|
|
444
|
-
*
|
|
445
463
|
* @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
|
|
446
464
|
* @returns {string} The number as ordinal words (e.g., "first", "forty-second")
|
|
447
465
|
* @throws {TypeError} If value is not a valid numeric type
|
|
448
466
|
* @throws {RangeError} If value is negative, zero, or has a decimal part
|
|
449
|
-
*
|
|
450
467
|
* @example
|
|
451
468
|
* toOrdinal(1) // 'first'
|
|
452
469
|
* toOrdinal(2) // 'second'
|
|
@@ -457,8 +474,9 @@ function buildLargeOrdinal (n) {
|
|
|
457
474
|
* toOrdinal(101) // 'one hundred first'
|
|
458
475
|
* toOrdinal(1000) // 'one thousandth'
|
|
459
476
|
*/
|
|
460
|
-
function toOrdinal
|
|
477
|
+
function toOrdinal(value) {
|
|
461
478
|
const integerPart = parseOrdinalValue(value)
|
|
479
|
+
checkMax(integerPart, ordinalMax)
|
|
462
480
|
return integerToOrdinal(integerPart)
|
|
463
481
|
}
|
|
464
482
|
|
|
@@ -466,26 +484,31 @@ function toOrdinal (value) {
|
|
|
466
484
|
// CURRENCY: toCurrency(value, options?)
|
|
467
485
|
// ============================================================================
|
|
468
486
|
|
|
487
|
+
/**
|
|
488
|
+
* @typedef {object} CurrencyOptions
|
|
489
|
+
* @property {boolean} [and] - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
|
|
490
|
+
*/
|
|
491
|
+
|
|
492
|
+
/** @type {Required<CurrencyOptions>} */
|
|
493
|
+
export const currencyDefaults = { and: true }
|
|
494
|
+
|
|
469
495
|
/**
|
|
470
496
|
* Converts a numeric value to American English currency words.
|
|
471
|
-
*
|
|
472
497
|
* @param {number | string | bigint} value - The currency amount to convert
|
|
473
|
-
* @param {
|
|
474
|
-
* @param {boolean} [options.and=true] - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
|
|
498
|
+
* @param {CurrencyOptions} [options] - Optional configuration
|
|
475
499
|
* @returns {string} The amount in American English currency words
|
|
476
500
|
* @throws {TypeError} If value is not a valid numeric type
|
|
477
501
|
* @throws {Error} If value is not a valid number format
|
|
478
|
-
*
|
|
479
502
|
* @example
|
|
480
503
|
* toCurrency(42.50) // 'forty-two dollars and fifty cents'
|
|
481
504
|
* toCurrency(1) // 'one dollar'
|
|
482
505
|
* toCurrency(0.99) // 'ninety-nine cents'
|
|
483
506
|
* toCurrency(42.50, { and: false }) // 'forty-two dollars fifty cents'
|
|
484
507
|
*/
|
|
485
|
-
function toCurrency
|
|
486
|
-
options = validateOptions(options)
|
|
508
|
+
function toCurrency(value, options) {
|
|
487
509
|
const { isNegative, dollars, cents } = parseCurrencyValue(value)
|
|
488
|
-
|
|
510
|
+
checkMax(dollars, currencyMax)
|
|
511
|
+
const { and: useAnd } = resolveOptions(options, currencyDefaults)
|
|
489
512
|
|
|
490
513
|
// Build result
|
|
491
514
|
let result = ''
|