reduce-precision 1.0.0 → 1.0.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.
@@ -0,0 +1,47 @@
1
+ declare type Template = 'number' | 'usd' | 'irt' | 'irr' | 'percent';
2
+ declare type Precision = 'auto' | 'high' | 'medium' | 'low';
3
+ declare type Language = 'en' | 'fa';
4
+ declare type OutputFormat = 'plain' | 'html' | 'markdown';
5
+ interface Options {
6
+ precision?: Precision;
7
+ template?: Template;
8
+ language?: Language;
9
+ outputFormat?: OutputFormat;
10
+ prefixMarker?: string;
11
+ postfixMarker?: string;
12
+ prefix?: string;
13
+ postfix?: string;
14
+ }
15
+ interface FormattedObject {
16
+ value?: string;
17
+ prefix: string;
18
+ postfix: string;
19
+ sign: string;
20
+ wholeNumber: string;
21
+ fractionalPart: string;
22
+ fractionalNonZeros: string;
23
+ fractionalZerosCount: number;
24
+ unit?: string;
25
+ }
26
+ interface LanguageConfig {
27
+ prefixMarker?: string;
28
+ postfixMarker?: string;
29
+ prefix?: string;
30
+ postfix?: string;
31
+ }
32
+ declare class NumberFormatter {
33
+ private options;
34
+ constructor(options?: Options);
35
+ setLanguage(lang: Language, config?: LanguageConfig): NumberFormatter;
36
+ setTemplate(template: Template, precision: Precision): NumberFormatter;
37
+ toJson(input: string | number): FormattedObject;
38
+ toString(input: string | number): string;
39
+ toPlainString(input: string | number): string;
40
+ toHtmlString(input: string | number): string;
41
+ toMdString(input: string | number): string;
42
+ private isENotation;
43
+ private format;
44
+ private convertENotationToRegularNumber;
45
+ private reducePrecision;
46
+ }
47
+ export default NumberFormatter;
@@ -0,0 +1,458 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class NumberFormatter {
4
+ constructor(options = {}) {
5
+ this.options = {
6
+ language: 'en',
7
+ template: 'number',
8
+ precision: 'high',
9
+ outputFormat: 'plain',
10
+ prefixMarker: 'i',
11
+ postfixMarker: 'i',
12
+ prefix: '',
13
+ postfix: '',
14
+ };
15
+ this.options = Object.assign(Object.assign({}, this.options), options);
16
+ }
17
+ setLanguage(lang, config = {}) {
18
+ this.options.language = lang;
19
+ this.options.prefixMarker =
20
+ config.prefixMarker || this.options.prefixMarker;
21
+ this.options.postfixMarker =
22
+ config.postfixMarker || this.options.postfixMarker;
23
+ this.options.prefix = config.prefix || this.options.prefix;
24
+ this.options.postfix = config.postfix || this.options.postfix;
25
+ return this;
26
+ }
27
+ setTemplate(template, precision) {
28
+ this.options.template = template;
29
+ this.options.precision = precision;
30
+ return this;
31
+ }
32
+ toJson(input) {
33
+ const formattedObject = this.format(input);
34
+ delete formattedObject.value;
35
+ return formattedObject;
36
+ }
37
+ toString(input) {
38
+ const formattedObject = this.format(input);
39
+ return formattedObject.value || '';
40
+ }
41
+ toPlainString(input) {
42
+ this.options.outputFormat = 'plain';
43
+ const formattedObject = this.format(input);
44
+ return formattedObject.value || '';
45
+ }
46
+ toHtmlString(input) {
47
+ this.options.outputFormat = 'html';
48
+ const formattedObject = this.format(input);
49
+ return formattedObject.value || '';
50
+ }
51
+ toMdString(input) {
52
+ this.options.outputFormat = 'markdown';
53
+ const formattedObject = this.format(input);
54
+ return formattedObject.value || '';
55
+ }
56
+ // Private methods...
57
+ isENotation(input) {
58
+ return /^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)$/.test(input);
59
+ }
60
+ format(input) {
61
+ let { precision, template } = this.options;
62
+ const { language, outputFormat, prefixMarker, postfixMarker, prefix, postfix, } = this.options;
63
+ if (!input)
64
+ return {};
65
+ if (!(template === null || template === void 0 ? void 0 : template.match(/^(number|usd|irt|irr|percent)$/g)))
66
+ template = 'number';
67
+ if (this.isENotation(input.toString())) {
68
+ input = this.convertENotationToRegularNumber(Number(input));
69
+ }
70
+ // Replace each Persian/Arabic numeral in the string with its English counterpart and strip all non-numeric chars
71
+ let numberString = input
72
+ .toString()
73
+ .replace(/[\u0660-\u0669\u06F0-\u06F9]/g, function (match) {
74
+ return String(match.charCodeAt(0) & 0xf);
75
+ })
76
+ .replace(/[^\d.-]/g, '');
77
+ // Stripping leading zeros and trailing zeros after a decimal point
78
+ numberString = numberString
79
+ .replace(/^0+(?=\d)/g, '')
80
+ .replace(/(?<=\.\d*)0+$|(?<=\.\d)0+\b/g, '');
81
+ const number = Math.abs(Number(numberString));
82
+ let p, d, r, c;
83
+ let f = 0;
84
+ // Auto precision selection
85
+ if (precision === 'auto') {
86
+ if (template.match(/^(usd|irt|irr)$/g)) {
87
+ if (number >= 0.0001 && number < 100000000000) {
88
+ precision = 'high';
89
+ }
90
+ else {
91
+ precision = 'medium';
92
+ }
93
+ }
94
+ else if (template === 'number') {
95
+ precision = 'medium';
96
+ }
97
+ else if (template === 'percent') {
98
+ precision = 'low';
99
+ }
100
+ }
101
+ if (precision === 'medium') {
102
+ if (number >= 0 && number < 0.0001) {
103
+ p = 33;
104
+ d = 4;
105
+ r = false;
106
+ c = true;
107
+ }
108
+ else if (number >= 0.0001 && number < 0.001) {
109
+ p = 7;
110
+ d = 4;
111
+ r = false;
112
+ c = false;
113
+ }
114
+ else if (number >= 0.001 && number < 0.01) {
115
+ p = 5;
116
+ d = 3;
117
+ r = false;
118
+ c = false;
119
+ }
120
+ else if (number >= 0.001 && number < 0.1) {
121
+ p = 3;
122
+ d = 2;
123
+ r = false;
124
+ c = false;
125
+ }
126
+ else if (number >= 0.1 && number < 1) {
127
+ p = 1;
128
+ d = 1;
129
+ r = false;
130
+ c = false;
131
+ }
132
+ else if (number >= 1 && number < 10) {
133
+ p = 3;
134
+ d = 3;
135
+ r = false;
136
+ c = false;
137
+ }
138
+ else if (number >= 10 && number < 100) {
139
+ p = 2;
140
+ d = 2;
141
+ r = false;
142
+ c = false;
143
+ }
144
+ else if (number >= 100 && number < 1000) {
145
+ p = 1;
146
+ d = 1;
147
+ r = false;
148
+ c = false;
149
+ }
150
+ else if (number >= 1000) {
151
+ const x = Math.floor(Math.log10(number)) % 3;
152
+ p = 2 - x;
153
+ d = 2 - x;
154
+ r = true;
155
+ c = true;
156
+ }
157
+ else {
158
+ p = 0;
159
+ d = 0;
160
+ r = true;
161
+ c = true;
162
+ }
163
+ }
164
+ else if (precision === 'low') {
165
+ if (number >= 0 && number < 0.01) {
166
+ p = 2;
167
+ d = 0;
168
+ r = true;
169
+ c = false;
170
+ f = 2;
171
+ }
172
+ else if (number >= 0.01 && number < 0.1) {
173
+ p = 2;
174
+ d = 1;
175
+ r = true;
176
+ c = false;
177
+ }
178
+ else if (number >= 0.1 && number < 1) {
179
+ p = 2;
180
+ d = 2;
181
+ r = true;
182
+ c = false;
183
+ }
184
+ else if (number >= 1 && number < 10) {
185
+ p = 2;
186
+ d = 2;
187
+ r = true;
188
+ c = false;
189
+ f = 2;
190
+ }
191
+ else if (number >= 10 && number < 100) {
192
+ p = 1;
193
+ d = 1;
194
+ r = true;
195
+ c = false;
196
+ f = 1;
197
+ }
198
+ else if (number >= 100 && number < 1000) {
199
+ p = 0;
200
+ d = 0;
201
+ r = true;
202
+ c = false;
203
+ }
204
+ else if (number >= 1000) {
205
+ const x = Math.floor(Math.log10(number)) % 3;
206
+ p = 1 - x;
207
+ d = 1 - x;
208
+ r = true;
209
+ c = true;
210
+ }
211
+ else {
212
+ p = 0;
213
+ d = 0;
214
+ r = true;
215
+ c = true;
216
+ f = 2;
217
+ }
218
+ }
219
+ else {
220
+ // precision === "high"
221
+ if (number >= 0 && number < 1) {
222
+ p = 33;
223
+ d = 4;
224
+ r = false;
225
+ c = false;
226
+ }
227
+ else if (number >= 1 && number < 10) {
228
+ p = 3;
229
+ d = 3;
230
+ r = true;
231
+ c = false;
232
+ }
233
+ else if (number >= 10 && number < 100) {
234
+ p = 2;
235
+ d = 2;
236
+ r = true;
237
+ c = false;
238
+ }
239
+ else if (number >= 100 && number < 1000) {
240
+ p = 2;
241
+ d = 2;
242
+ r = true;
243
+ c = false;
244
+ }
245
+ else if (number >= 1000 && number < 10000) {
246
+ p = 1;
247
+ d = 1;
248
+ r = true;
249
+ c = false;
250
+ }
251
+ else {
252
+ p = 0;
253
+ d = 0;
254
+ r = true;
255
+ c = false;
256
+ }
257
+ }
258
+ return this.reducePrecision(numberString, p, d, r, c, f, template, language, outputFormat, prefixMarker, postfixMarker, prefix, postfix);
259
+ }
260
+ convertENotationToRegularNumber(eNotation) {
261
+ const [coefficientStr, exponentStr] = eNotation.toString().split('e');
262
+ const coefficientLength = coefficientStr
263
+ .replace('.', '')
264
+ .replace('-', '').length;
265
+ const exponent = parseFloat(exponentStr);
266
+ const precision = Math.max(coefficientLength - exponent, 1);
267
+ return eNotation.toFixed(precision);
268
+ }
269
+ reducePrecision(numberString, precision = 30, nonZeroDigits = 4, round = false, compress = false, fixedDecimalZeros = 0, template = 'number', language = 'en', outputFormat = 'plain', prefixMarker = 'span', postfixMarker = 'span', prefix = '', postfix = '') {
270
+ if (!numberString) {
271
+ return {};
272
+ }
273
+ numberString = numberString.toString();
274
+ const maxPrecision = 30;
275
+ const maxIntegerDigits = 21;
276
+ const scaleUnits = template.match(/^(number|percent)$/g)
277
+ ? {
278
+ '': '',
279
+ K: ' هزار',
280
+ M: ' میلیون',
281
+ B: ' میلیارد',
282
+ T: ' تریلیون',
283
+ Qd: ' کادریلیون',
284
+ Qt: ' کنتیلیون',
285
+ }
286
+ : {
287
+ '': '',
288
+ K: ' هزار ت',
289
+ M: ' میلیون ت',
290
+ B: ' میلیارد ت',
291
+ T: ' همت',
292
+ Qd: ' هزار همت',
293
+ Qt: ' میلیون همت',
294
+ };
295
+ let parts = /^(-)?(\d+)\.?([0]*)(\d*)$/g.exec(numberString);
296
+ if (!parts) {
297
+ return {};
298
+ }
299
+ const sign = parts[1] || '';
300
+ let wholeNumberStr = parts[2];
301
+ let fractionalZeroStr = parts[3];
302
+ let fractionalNonZeroStr = parts[4];
303
+ let unitPrefix = '';
304
+ let unitPostfix = '';
305
+ if (fractionalZeroStr.length >= maxPrecision) {
306
+ // Number is smaller than maximum precision
307
+ fractionalZeroStr = '0'.padEnd(maxPrecision - 1, '0');
308
+ fractionalNonZeroStr = '1';
309
+ }
310
+ else if (fractionalZeroStr.length + nonZeroDigits > precision) {
311
+ // decrease non-zero digits
312
+ nonZeroDigits = precision - fractionalZeroStr.length;
313
+ if (nonZeroDigits < 1)
314
+ nonZeroDigits = 1;
315
+ }
316
+ else if (wholeNumberStr.length > maxIntegerDigits) {
317
+ wholeNumberStr = '0';
318
+ fractionalZeroStr = '';
319
+ fractionalNonZeroStr = '';
320
+ }
321
+ // compress large numbers
322
+ if (compress && wholeNumberStr.length >= 4) {
323
+ const scaleUnitKeys = Object.keys(scaleUnits);
324
+ let scaledWholeNumber = wholeNumberStr;
325
+ let unitIndex = 0;
326
+ while (+scaledWholeNumber > 999 && unitIndex < scaleUnitKeys.length - 1) {
327
+ scaledWholeNumber = (+scaledWholeNumber / 1000).toFixed(2);
328
+ unitIndex++;
329
+ }
330
+ unitPostfix = scaleUnitKeys[unitIndex];
331
+ parts = /^(-)?(\d+)\.?([0]*)(\d*)$/g.exec(scaledWholeNumber.toString());
332
+ if (!parts) {
333
+ return {};
334
+ }
335
+ // sign = parts[1] || "";
336
+ wholeNumberStr = parts[2];
337
+ fractionalZeroStr = parts[3];
338
+ fractionalNonZeroStr = parts[4];
339
+ }
340
+ // Truncate the fractional part or round it
341
+ // if (precision > 0 && nonZeroDigits > 0 && fractionalNonZeroStr.length > nonZeroDigits) {
342
+ if (fractionalNonZeroStr.length > nonZeroDigits) {
343
+ if (!round) {
344
+ fractionalNonZeroStr = fractionalNonZeroStr.substring(0, nonZeroDigits);
345
+ }
346
+ else {
347
+ if (parseInt(fractionalNonZeroStr[nonZeroDigits]) < 5) {
348
+ fractionalNonZeroStr = fractionalNonZeroStr.substring(0, nonZeroDigits);
349
+ }
350
+ else {
351
+ fractionalNonZeroStr = (parseInt(fractionalNonZeroStr.substring(0, nonZeroDigits)) + 1).toString();
352
+ // If overflow occurs (e.g., 999 + 1 = 1000), adjust the substring length
353
+ if (fractionalNonZeroStr.length > nonZeroDigits) {
354
+ if (fractionalZeroStr.length > 0) {
355
+ fractionalZeroStr = fractionalZeroStr.substring(0, fractionalZeroStr.length - 1);
356
+ }
357
+ else {
358
+ wholeNumberStr = (Number(wholeNumberStr) + 1).toString();
359
+ fractionalNonZeroStr = fractionalNonZeroStr.substring(1);
360
+ }
361
+ }
362
+ }
363
+ }
364
+ }
365
+ const orginalFractionalZeroStr = fractionalZeroStr;
366
+ // Using dex style
367
+ if (compress && fractionalZeroStr !== '' && unitPostfix === '') {
368
+ fractionalZeroStr =
369
+ '0' +
370
+ fractionalZeroStr.length.toString().replace(/\d/g, function (match) {
371
+ return [
372
+ '₀',
373
+ '₁',
374
+ '₂',
375
+ '₃',
376
+ '₄',
377
+ '₅',
378
+ '₆',
379
+ '₇',
380
+ '₈',
381
+ '₉',
382
+ ][parseInt(match, 10)];
383
+ });
384
+ }
385
+ let fractionalPartStr = `${fractionalZeroStr}${fractionalNonZeroStr}`;
386
+ fractionalPartStr = fractionalPartStr.substring(0, precision);
387
+ fractionalPartStr = fractionalPartStr.replace(/^(\d*[1-9])0+$/g, '$1');
388
+ // Output Formating, Prefix, Postfix
389
+ if (template === 'usd') {
390
+ unitPrefix = language === 'en' ? '$' : '';
391
+ }
392
+ else if (template === 'irr') {
393
+ if (!unitPostfix)
394
+ unitPostfix = language === 'fa' ? ' ر' : ' R';
395
+ }
396
+ else if (template === 'irt') {
397
+ if (!unitPostfix)
398
+ unitPostfix = language === 'fa' ? ' ت' : ' T';
399
+ }
400
+ else if (template === 'percent') {
401
+ if (language === 'en') {
402
+ unitPostfix += '%';
403
+ }
404
+ else {
405
+ unitPostfix += !unitPostfix ? '٪' : ' درصد';
406
+ }
407
+ }
408
+ unitPrefix = prefix + unitPrefix;
409
+ unitPostfix += postfix;
410
+ if (outputFormat === 'html') {
411
+ if (unitPrefix)
412
+ unitPrefix = `<${prefixMarker}>${unitPrefix}</${prefixMarker}>`;
413
+ if (unitPostfix)
414
+ unitPostfix = `<${postfixMarker}>${unitPostfix}</${postfixMarker}>`;
415
+ }
416
+ else if (outputFormat === 'markdown') {
417
+ if (unitPrefix)
418
+ unitPrefix = `${prefixMarker}${unitPrefix}${prefixMarker}`;
419
+ if (unitPostfix)
420
+ unitPostfix = `${postfixMarker}${unitPostfix}${postfixMarker}`;
421
+ }
422
+ const thousandSeparatorRegex = /\B(?=(\d{3})+(?!\d))/g;
423
+ const fixedDecimalZeroStr = fixedDecimalZeros
424
+ ? '.'.padEnd(fixedDecimalZeros + 1, '0')
425
+ : '';
426
+ let out = '';
427
+ if (precision <= 0 || nonZeroDigits <= 0 || !fractionalNonZeroStr) {
428
+ out = `${sign}${unitPrefix}${wholeNumberStr.replace(thousandSeparatorRegex, ',')}${fixedDecimalZeroStr}${unitPostfix}`;
429
+ }
430
+ else {
431
+ out = `${sign}${unitPrefix}${wholeNumberStr.replace(thousandSeparatorRegex, ',')}.${fractionalPartStr}${unitPostfix}`;
432
+ }
433
+ // Convert output to Persian numerals if language is "fa"
434
+ if (language === 'fa') {
435
+ out = out
436
+ .replace(/[0-9]/g, c => String.fromCharCode(c.charCodeAt(0) + 1728))
437
+ .replace(/,/g, '٬')
438
+ .replace(/\./g, '٫')
439
+ .replace(/(K|M|B|T|Qt|Qd)/g, function (c) {
440
+ return String(scaleUnits[c]);
441
+ });
442
+ }
443
+ const formattedObject = {
444
+ value: out,
445
+ prefix: unitPrefix,
446
+ postfix: unitPostfix.replace(/(K|M|B|T|Qt|Qd)/g, function (c) {
447
+ return String(scaleUnits[c]);
448
+ }),
449
+ sign: sign,
450
+ wholeNumber: wholeNumberStr.replace(thousandSeparatorRegex, ','),
451
+ fractionalPart: fractionalPartStr,
452
+ fractionalNonZeros: fractionalNonZeroStr,
453
+ fractionalZerosCount: orginalFractionalZeroStr.length,
454
+ };
455
+ return formattedObject;
456
+ }
457
+ }
458
+ exports.default = NumberFormatter;
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import NumberFormatter from './format';
2
+ export { NumberFormatter };
package/lib/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NumberFormatter = void 0;
7
+ const format_1 = __importDefault(require("./format"));
8
+ exports.NumberFormatter = format_1.default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reduce-precision",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "",
5
5
  "main": "./lib/index.js",
6
6
  "files": [