smart-unit 1.0.4 → 2.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.
- package/README.md +48 -151
- package/README.zh-CN.md +37 -140
- package/dist/SmartUnit.d.ts +60 -0
- package/dist/SmartUnitBase.d.ts +77 -0
- package/dist/SmartUnitPrecision.d.ts +82 -0
- package/dist/index.cjs +166 -0
- package/dist/index.d.ts +3 -104
- package/dist/index.mjs +160 -0
- package/dist/precision.cjs +195 -0
- package/dist/precision.d.ts +9 -0
- package/dist/precision.mjs +189 -0
- package/dist/utils-C1byK7Uk.js +146 -0
- package/dist/utils-DqEXot5a.js +149 -0
- package/dist/utils.d.ts +32 -0
- package/package.json +36 -30
- package/dist/browser.umd.js +0 -14
- package/dist/index.esm.js +0 -228
- package/dist/index.js +0 -235
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var DecimalConstructor = require('decimal.js');
|
|
6
|
+
var utils = require('./utils-DqEXot5a.js');
|
|
7
|
+
|
|
8
|
+
class SmartUnitPrecision extends utils.SmartUnitBase {
|
|
9
|
+
constructor(units, option = {}) {
|
|
10
|
+
super(units, option);
|
|
11
|
+
this.DecimalClass = DecimalConstructor;
|
|
12
|
+
if (option.decimalOptions) {
|
|
13
|
+
this.DecimalClass = DecimalConstructor.clone(option.decimalOptions);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// 根据输入数值获取单位和调整后的值
|
|
17
|
+
/**
|
|
18
|
+
* Gets the appropriate unit and adjusted value for the input number
|
|
19
|
+
*
|
|
20
|
+
* @param num - The input NumPrecision to determine the unit for
|
|
21
|
+
* @param fractionDigits - Decimal precision configuration
|
|
22
|
+
* @returns The FormattedValue object containing the number, unit, and decimal instance
|
|
23
|
+
*/
|
|
24
|
+
getUnit(num) {
|
|
25
|
+
if (Number.isNaN(num))
|
|
26
|
+
throw new Error(utils.ERROR_NAN_INPUT);
|
|
27
|
+
const unitDigitsLen = this.unitDigits.length;
|
|
28
|
+
const dn = new this.DecimalClass(typeof num === 'bigint' ? num.toString() : num);
|
|
29
|
+
const isNegative = dn.isNegative();
|
|
30
|
+
let absDn = isNegative ? dn.abs() : dn;
|
|
31
|
+
let i = 0;
|
|
32
|
+
while (i < unitDigitsLen) {
|
|
33
|
+
const digit = this.unitDigits[i];
|
|
34
|
+
if (absDn.lt(digit * this.threshold)) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
absDn = absDn.dividedBy(digit);
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
const result = isNegative ? absDn.neg() : absDn;
|
|
41
|
+
const n = result.toNumber();
|
|
42
|
+
return {
|
|
43
|
+
num: n,
|
|
44
|
+
decimal: result,
|
|
45
|
+
unit: this.unitNames[i],
|
|
46
|
+
numStr: this.formatNumber(result, this.fractionDigits),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// 将数字转换为字符串表示形式,并可选择配置小数位数
|
|
50
|
+
/**
|
|
51
|
+
* Formats a number as a string with unit and optional decimal place configuration
|
|
52
|
+
*
|
|
53
|
+
* @param num - The input number to format
|
|
54
|
+
* @param fractionDigits - Decimal precision configuration
|
|
55
|
+
* @returns The formatted string with number and unit
|
|
56
|
+
*/
|
|
57
|
+
format(num, fractionDigits = this.fractionDigits) {
|
|
58
|
+
const { decimal, unit } = this.getUnit(num);
|
|
59
|
+
const formatted = this.formatNumber(decimal, fractionDigits);
|
|
60
|
+
return `${formatted}${this.convert ? this.convert(unit) : unit}`;
|
|
61
|
+
}
|
|
62
|
+
// 根据输入数值获取链式单位数组
|
|
63
|
+
/**
|
|
64
|
+
* Gets the chain of units for the input number
|
|
65
|
+
*
|
|
66
|
+
* @param num - The input number to determine the chain for
|
|
67
|
+
* @returns An array of FormattedValue objects representing the chain of units
|
|
68
|
+
*/
|
|
69
|
+
getChainUnit(num) {
|
|
70
|
+
if (Number.isNaN(num))
|
|
71
|
+
throw new Error(utils.ERROR_NAN_INPUT);
|
|
72
|
+
const result = [];
|
|
73
|
+
if (num === 0) {
|
|
74
|
+
return [{ num: 0, unit: this.unitNames[0], numStr: '0', decimal: new this.DecimalClass(0) }];
|
|
75
|
+
}
|
|
76
|
+
const dn = new this.DecimalClass(typeof num === 'bigint' ? num.toString() : num);
|
|
77
|
+
const isNegative = dn.isNegative();
|
|
78
|
+
let absDn = isNegative ? dn.abs() : dn;
|
|
79
|
+
for (let i = this.accumulatedDigits.length - 1; i >= 0; i--) {
|
|
80
|
+
const accDigit = this.accumulatedDigits[i];
|
|
81
|
+
if (absDn.gte(accDigit)) {
|
|
82
|
+
const res = absDn.dividedToIntegerBy(accDigit);
|
|
83
|
+
const val = isNegative ? res.neg() : res;
|
|
84
|
+
result.push({
|
|
85
|
+
num: val.toNumber(),
|
|
86
|
+
decimal: new this.DecimalClass(val),
|
|
87
|
+
unit: this.unitNames[i + 1],
|
|
88
|
+
numStr: this.formatNumber(val, this.fractionDigits),
|
|
89
|
+
});
|
|
90
|
+
absDn = absDn.minus(new this.DecimalClass(val).times(accDigit));
|
|
91
|
+
if (absDn.isZero())
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!absDn.isZero()) {
|
|
96
|
+
const val = isNegative ? absDn.neg() : absDn;
|
|
97
|
+
result.push({
|
|
98
|
+
num: val.toNumber(),
|
|
99
|
+
decimal: val,
|
|
100
|
+
unit: this.unitNames[0],
|
|
101
|
+
numStr: this.formatNumber(val, this.fractionDigits),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
// 将数字格式化为链式单位字符串
|
|
107
|
+
/**
|
|
108
|
+
* Formats a number as a chain of units string
|
|
109
|
+
*
|
|
110
|
+
* @param num - The input number to format
|
|
111
|
+
* @returns The formatted chain string
|
|
112
|
+
*/
|
|
113
|
+
formatChain(num, separator = this.separator) {
|
|
114
|
+
const chain = this.getChainUnit(num);
|
|
115
|
+
return chain
|
|
116
|
+
.map(({ numStr, unit }) => `${numStr}${this.convert ? this.convert(unit) : unit}`)
|
|
117
|
+
.join(separator);
|
|
118
|
+
}
|
|
119
|
+
// 将给定数值从指定单位转换为基本单位
|
|
120
|
+
/**
|
|
121
|
+
* Converts a value from the specified unit to the base unit
|
|
122
|
+
*
|
|
123
|
+
* @param num - The number to convert
|
|
124
|
+
* @param unit - The original unit of the number
|
|
125
|
+
* @returns The converted value in base unit
|
|
126
|
+
*/
|
|
127
|
+
toBase(num, unit) {
|
|
128
|
+
if (Number.isNaN(num))
|
|
129
|
+
throw new Error(utils.ERROR_NAN_INPUT);
|
|
130
|
+
const unitDigitsLen = this.unitDigits.length;
|
|
131
|
+
let dn = new this.DecimalClass(typeof num === 'bigint' ? num.toString() : num);
|
|
132
|
+
for (let i = 0; i < unitDigitsLen; i++) {
|
|
133
|
+
const digit = this.unitDigits[i];
|
|
134
|
+
if (this.unitNames[i] === unit) {
|
|
135
|
+
return dn;
|
|
136
|
+
}
|
|
137
|
+
dn = dn.times(digit);
|
|
138
|
+
}
|
|
139
|
+
if (unit === this.unitNames.at(-1)) {
|
|
140
|
+
return dn;
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`Undefined unit: "${unit}".`);
|
|
143
|
+
}
|
|
144
|
+
// 从给定字符串中分离出数字部分和单位
|
|
145
|
+
/**
|
|
146
|
+
* Splits a string into its numeric part and unit
|
|
147
|
+
*
|
|
148
|
+
* @param str - Input string containing a number followed by a unit
|
|
149
|
+
* @returns An object containing the numeric value, unit, and Decimal instance
|
|
150
|
+
* @throws An error if no predefined unit is matched
|
|
151
|
+
*/
|
|
152
|
+
splitUnit(str) {
|
|
153
|
+
const { num, unit, numStr } = super.splitUnit(str);
|
|
154
|
+
return {
|
|
155
|
+
num,
|
|
156
|
+
decimal: new this.DecimalClass(numStr),
|
|
157
|
+
unit,
|
|
158
|
+
numStr,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// 将带单位的值转换为基础单位的数值
|
|
162
|
+
/**
|
|
163
|
+
* Parses a string with unit into a base unit numeric value
|
|
164
|
+
*
|
|
165
|
+
* @param str - Input string containing a number and unit
|
|
166
|
+
* @returns The value converted to base unit
|
|
167
|
+
*/
|
|
168
|
+
parse(str) {
|
|
169
|
+
const { decimal, unit } = this.splitUnit(str);
|
|
170
|
+
return this.toBase(decimal, unit);
|
|
171
|
+
}
|
|
172
|
+
// 将给定数值从原单位转换为最佳单位,并可指定小数精度
|
|
173
|
+
/**
|
|
174
|
+
* Converts a value from the original unit to the optimal unit with optional decimal precision
|
|
175
|
+
*
|
|
176
|
+
* @param num - The number to convert
|
|
177
|
+
* @param unit - The original unit
|
|
178
|
+
* @param fractionDigits - Optional decimal places for formatting output
|
|
179
|
+
* @returns The converted number as a formatted string
|
|
180
|
+
*/
|
|
181
|
+
fromUnitFormat(num, unit, fractionDigits) {
|
|
182
|
+
const nnum = this.toBase(num, unit);
|
|
183
|
+
return this.format(nnum, fractionDigits);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/*!
|
|
188
|
+
* smart-unit
|
|
189
|
+
* Copyright (c) [2026] [flycran]
|
|
190
|
+
* MIT License. See LICENSE for details.
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
exports.ERROR_NAN_INPUT = utils.ERROR_NAN_INPUT;
|
|
194
|
+
exports.SmartUnitPrecision = SmartUnitPrecision;
|
|
195
|
+
exports.default = SmartUnitPrecision;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* smart-unit
|
|
3
|
+
* Copyright (c) [2026] [flycran]
|
|
4
|
+
* MIT License. See LICENSE for details.
|
|
5
|
+
*/
|
|
6
|
+
export * from './SmartUnitPrecision';
|
|
7
|
+
export * from './utils';
|
|
8
|
+
import { SmartUnitPrecision } from './SmartUnitPrecision';
|
|
9
|
+
export default SmartUnitPrecision;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import DecimalConstructor from 'decimal.js';
|
|
2
|
+
import { S as SmartUnitBase, E as ERROR_NAN_INPUT } from './utils-C1byK7Uk.js';
|
|
3
|
+
|
|
4
|
+
class SmartUnitPrecision extends SmartUnitBase {
|
|
5
|
+
constructor(units, option = {}) {
|
|
6
|
+
super(units, option);
|
|
7
|
+
this.DecimalClass = DecimalConstructor;
|
|
8
|
+
if (option.decimalOptions) {
|
|
9
|
+
this.DecimalClass = DecimalConstructor.clone(option.decimalOptions);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
// 根据输入数值获取单位和调整后的值
|
|
13
|
+
/**
|
|
14
|
+
* Gets the appropriate unit and adjusted value for the input number
|
|
15
|
+
*
|
|
16
|
+
* @param num - The input NumPrecision to determine the unit for
|
|
17
|
+
* @param fractionDigits - Decimal precision configuration
|
|
18
|
+
* @returns The FormattedValue object containing the number, unit, and decimal instance
|
|
19
|
+
*/
|
|
20
|
+
getUnit(num) {
|
|
21
|
+
if (Number.isNaN(num))
|
|
22
|
+
throw new Error(ERROR_NAN_INPUT);
|
|
23
|
+
const unitDigitsLen = this.unitDigits.length;
|
|
24
|
+
const dn = new this.DecimalClass(typeof num === 'bigint' ? num.toString() : num);
|
|
25
|
+
const isNegative = dn.isNegative();
|
|
26
|
+
let absDn = isNegative ? dn.abs() : dn;
|
|
27
|
+
let i = 0;
|
|
28
|
+
while (i < unitDigitsLen) {
|
|
29
|
+
const digit = this.unitDigits[i];
|
|
30
|
+
if (absDn.lt(digit * this.threshold)) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
absDn = absDn.dividedBy(digit);
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
const result = isNegative ? absDn.neg() : absDn;
|
|
37
|
+
const n = result.toNumber();
|
|
38
|
+
return {
|
|
39
|
+
num: n,
|
|
40
|
+
decimal: result,
|
|
41
|
+
unit: this.unitNames[i],
|
|
42
|
+
numStr: this.formatNumber(result, this.fractionDigits),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// 将数字转换为字符串表示形式,并可选择配置小数位数
|
|
46
|
+
/**
|
|
47
|
+
* Formats a number as a string with unit and optional decimal place configuration
|
|
48
|
+
*
|
|
49
|
+
* @param num - The input number to format
|
|
50
|
+
* @param fractionDigits - Decimal precision configuration
|
|
51
|
+
* @returns The formatted string with number and unit
|
|
52
|
+
*/
|
|
53
|
+
format(num, fractionDigits = this.fractionDigits) {
|
|
54
|
+
const { decimal, unit } = this.getUnit(num);
|
|
55
|
+
const formatted = this.formatNumber(decimal, fractionDigits);
|
|
56
|
+
return `${formatted}${this.convert ? this.convert(unit) : unit}`;
|
|
57
|
+
}
|
|
58
|
+
// 根据输入数值获取链式单位数组
|
|
59
|
+
/**
|
|
60
|
+
* Gets the chain of units for the input number
|
|
61
|
+
*
|
|
62
|
+
* @param num - The input number to determine the chain for
|
|
63
|
+
* @returns An array of FormattedValue objects representing the chain of units
|
|
64
|
+
*/
|
|
65
|
+
getChainUnit(num) {
|
|
66
|
+
if (Number.isNaN(num))
|
|
67
|
+
throw new Error(ERROR_NAN_INPUT);
|
|
68
|
+
const result = [];
|
|
69
|
+
if (num === 0) {
|
|
70
|
+
return [{ num: 0, unit: this.unitNames[0], numStr: '0', decimal: new this.DecimalClass(0) }];
|
|
71
|
+
}
|
|
72
|
+
const dn = new this.DecimalClass(typeof num === 'bigint' ? num.toString() : num);
|
|
73
|
+
const isNegative = dn.isNegative();
|
|
74
|
+
let absDn = isNegative ? dn.abs() : dn;
|
|
75
|
+
for (let i = this.accumulatedDigits.length - 1; i >= 0; i--) {
|
|
76
|
+
const accDigit = this.accumulatedDigits[i];
|
|
77
|
+
if (absDn.gte(accDigit)) {
|
|
78
|
+
const res = absDn.dividedToIntegerBy(accDigit);
|
|
79
|
+
const val = isNegative ? res.neg() : res;
|
|
80
|
+
result.push({
|
|
81
|
+
num: val.toNumber(),
|
|
82
|
+
decimal: new this.DecimalClass(val),
|
|
83
|
+
unit: this.unitNames[i + 1],
|
|
84
|
+
numStr: this.formatNumber(val, this.fractionDigits),
|
|
85
|
+
});
|
|
86
|
+
absDn = absDn.minus(new this.DecimalClass(val).times(accDigit));
|
|
87
|
+
if (absDn.isZero())
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!absDn.isZero()) {
|
|
92
|
+
const val = isNegative ? absDn.neg() : absDn;
|
|
93
|
+
result.push({
|
|
94
|
+
num: val.toNumber(),
|
|
95
|
+
decimal: val,
|
|
96
|
+
unit: this.unitNames[0],
|
|
97
|
+
numStr: this.formatNumber(val, this.fractionDigits),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
// 将数字格式化为链式单位字符串
|
|
103
|
+
/**
|
|
104
|
+
* Formats a number as a chain of units string
|
|
105
|
+
*
|
|
106
|
+
* @param num - The input number to format
|
|
107
|
+
* @returns The formatted chain string
|
|
108
|
+
*/
|
|
109
|
+
formatChain(num, separator = this.separator) {
|
|
110
|
+
const chain = this.getChainUnit(num);
|
|
111
|
+
return chain
|
|
112
|
+
.map(({ numStr, unit }) => `${numStr}${this.convert ? this.convert(unit) : unit}`)
|
|
113
|
+
.join(separator);
|
|
114
|
+
}
|
|
115
|
+
// 将给定数值从指定单位转换为基本单位
|
|
116
|
+
/**
|
|
117
|
+
* Converts a value from the specified unit to the base unit
|
|
118
|
+
*
|
|
119
|
+
* @param num - The number to convert
|
|
120
|
+
* @param unit - The original unit of the number
|
|
121
|
+
* @returns The converted value in base unit
|
|
122
|
+
*/
|
|
123
|
+
toBase(num, unit) {
|
|
124
|
+
if (Number.isNaN(num))
|
|
125
|
+
throw new Error(ERROR_NAN_INPUT);
|
|
126
|
+
const unitDigitsLen = this.unitDigits.length;
|
|
127
|
+
let dn = new this.DecimalClass(typeof num === 'bigint' ? num.toString() : num);
|
|
128
|
+
for (let i = 0; i < unitDigitsLen; i++) {
|
|
129
|
+
const digit = this.unitDigits[i];
|
|
130
|
+
if (this.unitNames[i] === unit) {
|
|
131
|
+
return dn;
|
|
132
|
+
}
|
|
133
|
+
dn = dn.times(digit);
|
|
134
|
+
}
|
|
135
|
+
if (unit === this.unitNames.at(-1)) {
|
|
136
|
+
return dn;
|
|
137
|
+
}
|
|
138
|
+
throw new Error(`Undefined unit: "${unit}".`);
|
|
139
|
+
}
|
|
140
|
+
// 从给定字符串中分离出数字部分和单位
|
|
141
|
+
/**
|
|
142
|
+
* Splits a string into its numeric part and unit
|
|
143
|
+
*
|
|
144
|
+
* @param str - Input string containing a number followed by a unit
|
|
145
|
+
* @returns An object containing the numeric value, unit, and Decimal instance
|
|
146
|
+
* @throws An error if no predefined unit is matched
|
|
147
|
+
*/
|
|
148
|
+
splitUnit(str) {
|
|
149
|
+
const { num, unit, numStr } = super.splitUnit(str);
|
|
150
|
+
return {
|
|
151
|
+
num,
|
|
152
|
+
decimal: new this.DecimalClass(numStr),
|
|
153
|
+
unit,
|
|
154
|
+
numStr,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// 将带单位的值转换为基础单位的数值
|
|
158
|
+
/**
|
|
159
|
+
* Parses a string with unit into a base unit numeric value
|
|
160
|
+
*
|
|
161
|
+
* @param str - Input string containing a number and unit
|
|
162
|
+
* @returns The value converted to base unit
|
|
163
|
+
*/
|
|
164
|
+
parse(str) {
|
|
165
|
+
const { decimal, unit } = this.splitUnit(str);
|
|
166
|
+
return this.toBase(decimal, unit);
|
|
167
|
+
}
|
|
168
|
+
// 将给定数值从原单位转换为最佳单位,并可指定小数精度
|
|
169
|
+
/**
|
|
170
|
+
* Converts a value from the original unit to the optimal unit with optional decimal precision
|
|
171
|
+
*
|
|
172
|
+
* @param num - The number to convert
|
|
173
|
+
* @param unit - The original unit
|
|
174
|
+
* @param fractionDigits - Optional decimal places for formatting output
|
|
175
|
+
* @returns The converted number as a formatted string
|
|
176
|
+
*/
|
|
177
|
+
fromUnitFormat(num, unit, fractionDigits) {
|
|
178
|
+
const nnum = this.toBase(num, unit);
|
|
179
|
+
return this.format(nnum, fractionDigits);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/*!
|
|
184
|
+
* smart-unit
|
|
185
|
+
* Copyright (c) [2026] [flycran]
|
|
186
|
+
* MIT License. See LICENSE for details.
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
export { ERROR_NAN_INPUT, SmartUnitPrecision, SmartUnitPrecision as default };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
class SmartUnitBase {
|
|
2
|
+
/** Accumulated digits for unit conversion */
|
|
3
|
+
get accumulatedDigits() {
|
|
4
|
+
if (!this._accumulatedDigits)
|
|
5
|
+
this.createAccumulatedDigits();
|
|
6
|
+
return this._accumulatedDigits;
|
|
7
|
+
}
|
|
8
|
+
/** Sorted unit names by length for efficient lookup */
|
|
9
|
+
get sortedUnitNames() {
|
|
10
|
+
if (!this._sortedUnitNames)
|
|
11
|
+
this.createSortedUnitNames();
|
|
12
|
+
return this._sortedUnitNames;
|
|
13
|
+
}
|
|
14
|
+
constructor(units, option = {}) {
|
|
15
|
+
this.unitNames = [];
|
|
16
|
+
this.unitDigits = [];
|
|
17
|
+
// Initialize
|
|
18
|
+
if (!units.length)
|
|
19
|
+
throw new Error('units is empty.');
|
|
20
|
+
this.threshold = option.threshold || 1;
|
|
21
|
+
this.fractionDigits = option.fractionDigits;
|
|
22
|
+
this.separator = option.separator || '';
|
|
23
|
+
if (option.baseDigit) {
|
|
24
|
+
for (let i = 0; i < units.length; i++) {
|
|
25
|
+
const name = units[i];
|
|
26
|
+
if (typeof name !== 'string')
|
|
27
|
+
throw new Error(`The unit setting is incorrect; the element at index [${i}] should be of string type.`);
|
|
28
|
+
}
|
|
29
|
+
this.unitNames = units;
|
|
30
|
+
this.unitDigits = Array(units.length - 1).fill(option.baseDigit);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
for (let i = 1; i < units.length; i += 2) {
|
|
34
|
+
const digit = units[i];
|
|
35
|
+
if (typeof digit !== 'number')
|
|
36
|
+
throw new Error(`The unit setting is incorrect; the element at index [${i}] should be of numeric type.`);
|
|
37
|
+
this.unitDigits.push(units[i]);
|
|
38
|
+
const name = units[i - 1];
|
|
39
|
+
if (typeof name !== 'string')
|
|
40
|
+
throw new Error(`The unit setting is incorrect; the element at index [${i - 1}] should be of string type.`);
|
|
41
|
+
this.unitNames.push(name);
|
|
42
|
+
}
|
|
43
|
+
this.unitNames.push(units[units.length - 1]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
withConvert(convert) {
|
|
47
|
+
const su = Object.create(this);
|
|
48
|
+
su.convert = convert;
|
|
49
|
+
return su;
|
|
50
|
+
}
|
|
51
|
+
// 累积比例
|
|
52
|
+
createAccumulatedDigits() {
|
|
53
|
+
const accumulatedDigits = [];
|
|
54
|
+
this.unitDigits.forEach((digit, index) => {
|
|
55
|
+
if (index === 0) {
|
|
56
|
+
accumulatedDigits.push(digit);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
accumulatedDigits.push(accumulatedDigits[index - 1] * digit);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
this._accumulatedDigits = accumulatedDigits;
|
|
63
|
+
}
|
|
64
|
+
// 长度排序后的单位名称
|
|
65
|
+
createSortedUnitNames() {
|
|
66
|
+
const sortedUnits = [...this.unitNames].sort((a, b) => b.length - a.length);
|
|
67
|
+
this._sortedUnitNames = sortedUnits;
|
|
68
|
+
}
|
|
69
|
+
// 格式化数字的小数位数
|
|
70
|
+
/**
|
|
71
|
+
* Formats the decimal places of a number
|
|
72
|
+
*
|
|
73
|
+
* @param value - The value to format
|
|
74
|
+
* @param fractionDigits - Decimal precision configuration
|
|
75
|
+
* @returns The formatted string representation of the number
|
|
76
|
+
*/
|
|
77
|
+
formatNumber(value, fractionDigits = this.fractionDigits) {
|
|
78
|
+
if (typeof fractionDigits === 'number') {
|
|
79
|
+
return value.toFixed(fractionDigits);
|
|
80
|
+
}
|
|
81
|
+
else if (typeof fractionDigits === 'string') {
|
|
82
|
+
const [dp1, dp2] = fractionDigits.split('-');
|
|
83
|
+
const ndp = (value.toString().split('.')[1] || '').length;
|
|
84
|
+
const minDp = dp1 ? +dp1 : -Infinity;
|
|
85
|
+
const maxDp = dp2 ? +dp2 : Infinity;
|
|
86
|
+
if (ndp < minDp) {
|
|
87
|
+
return value.toFixed(minDp);
|
|
88
|
+
}
|
|
89
|
+
else if (ndp > maxDp) {
|
|
90
|
+
return value.toFixed(maxDp);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
return value.toString();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
return value.toString();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// format抽象方法
|
|
101
|
+
_format(numStr, unit) {
|
|
102
|
+
return `${numStr}${this.convert ? this.convert(unit) : unit}`;
|
|
103
|
+
}
|
|
104
|
+
_formatChain(chain, separator = this.separator) {
|
|
105
|
+
return chain.map(({ numStr, unit }) => this._format(numStr, unit)).join(separator);
|
|
106
|
+
}
|
|
107
|
+
// 将数字格式化为链式单位字符串
|
|
108
|
+
/**
|
|
109
|
+
* Formats a number as a chain of units string
|
|
110
|
+
* @param num - The input number to format
|
|
111
|
+
* @returns The formatted chain string
|
|
112
|
+
*/
|
|
113
|
+
formatChain(num, separator = this.separator) {
|
|
114
|
+
const chain = this.getChainUnit(num);
|
|
115
|
+
return this._formatChain(chain, separator);
|
|
116
|
+
}
|
|
117
|
+
// 从给定字符串中分离出数字部分和单位
|
|
118
|
+
/**
|
|
119
|
+
* Splits a string into its numeric part and unit
|
|
120
|
+
*
|
|
121
|
+
* @param str - Input string containing a number followed by a unit
|
|
122
|
+
* @returns An object containing the numeric value, unit, and Decimal instance
|
|
123
|
+
* @throws An error if no predefined unit is matched
|
|
124
|
+
*/
|
|
125
|
+
splitUnit(str) {
|
|
126
|
+
const sortedUnits = this.sortedUnitNames;
|
|
127
|
+
for (const unit of sortedUnits) {
|
|
128
|
+
if (str.endsWith(unit)) {
|
|
129
|
+
const numStr = str.slice(0, -unit.length);
|
|
130
|
+
const num = +numStr;
|
|
131
|
+
if (Number.isNaN(num))
|
|
132
|
+
throw new Error(`Invalid number: "${numStr}".`);
|
|
133
|
+
return {
|
|
134
|
+
num,
|
|
135
|
+
unit,
|
|
136
|
+
numStr,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`Undefined unit: "${str}".`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const ERROR_NAN_INPUT = 'Accepting NaN as an argument may be unintentional and could lead to invalid results.';
|
|
145
|
+
|
|
146
|
+
export { ERROR_NAN_INPUT as E, SmartUnitBase as S };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class SmartUnitBase {
|
|
4
|
+
/** Accumulated digits for unit conversion */
|
|
5
|
+
get accumulatedDigits() {
|
|
6
|
+
if (!this._accumulatedDigits)
|
|
7
|
+
this.createAccumulatedDigits();
|
|
8
|
+
return this._accumulatedDigits;
|
|
9
|
+
}
|
|
10
|
+
/** Sorted unit names by length for efficient lookup */
|
|
11
|
+
get sortedUnitNames() {
|
|
12
|
+
if (!this._sortedUnitNames)
|
|
13
|
+
this.createSortedUnitNames();
|
|
14
|
+
return this._sortedUnitNames;
|
|
15
|
+
}
|
|
16
|
+
constructor(units, option = {}) {
|
|
17
|
+
this.unitNames = [];
|
|
18
|
+
this.unitDigits = [];
|
|
19
|
+
// Initialize
|
|
20
|
+
if (!units.length)
|
|
21
|
+
throw new Error('units is empty.');
|
|
22
|
+
this.threshold = option.threshold || 1;
|
|
23
|
+
this.fractionDigits = option.fractionDigits;
|
|
24
|
+
this.separator = option.separator || '';
|
|
25
|
+
if (option.baseDigit) {
|
|
26
|
+
for (let i = 0; i < units.length; i++) {
|
|
27
|
+
const name = units[i];
|
|
28
|
+
if (typeof name !== 'string')
|
|
29
|
+
throw new Error(`The unit setting is incorrect; the element at index [${i}] should be of string type.`);
|
|
30
|
+
}
|
|
31
|
+
this.unitNames = units;
|
|
32
|
+
this.unitDigits = Array(units.length - 1).fill(option.baseDigit);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
for (let i = 1; i < units.length; i += 2) {
|
|
36
|
+
const digit = units[i];
|
|
37
|
+
if (typeof digit !== 'number')
|
|
38
|
+
throw new Error(`The unit setting is incorrect; the element at index [${i}] should be of numeric type.`);
|
|
39
|
+
this.unitDigits.push(units[i]);
|
|
40
|
+
const name = units[i - 1];
|
|
41
|
+
if (typeof name !== 'string')
|
|
42
|
+
throw new Error(`The unit setting is incorrect; the element at index [${i - 1}] should be of string type.`);
|
|
43
|
+
this.unitNames.push(name);
|
|
44
|
+
}
|
|
45
|
+
this.unitNames.push(units[units.length - 1]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
withConvert(convert) {
|
|
49
|
+
const su = Object.create(this);
|
|
50
|
+
su.convert = convert;
|
|
51
|
+
return su;
|
|
52
|
+
}
|
|
53
|
+
// 累积比例
|
|
54
|
+
createAccumulatedDigits() {
|
|
55
|
+
const accumulatedDigits = [];
|
|
56
|
+
this.unitDigits.forEach((digit, index) => {
|
|
57
|
+
if (index === 0) {
|
|
58
|
+
accumulatedDigits.push(digit);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
accumulatedDigits.push(accumulatedDigits[index - 1] * digit);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
this._accumulatedDigits = accumulatedDigits;
|
|
65
|
+
}
|
|
66
|
+
// 长度排序后的单位名称
|
|
67
|
+
createSortedUnitNames() {
|
|
68
|
+
const sortedUnits = [...this.unitNames].sort((a, b) => b.length - a.length);
|
|
69
|
+
this._sortedUnitNames = sortedUnits;
|
|
70
|
+
}
|
|
71
|
+
// 格式化数字的小数位数
|
|
72
|
+
/**
|
|
73
|
+
* Formats the decimal places of a number
|
|
74
|
+
*
|
|
75
|
+
* @param value - The value to format
|
|
76
|
+
* @param fractionDigits - Decimal precision configuration
|
|
77
|
+
* @returns The formatted string representation of the number
|
|
78
|
+
*/
|
|
79
|
+
formatNumber(value, fractionDigits = this.fractionDigits) {
|
|
80
|
+
if (typeof fractionDigits === 'number') {
|
|
81
|
+
return value.toFixed(fractionDigits);
|
|
82
|
+
}
|
|
83
|
+
else if (typeof fractionDigits === 'string') {
|
|
84
|
+
const [dp1, dp2] = fractionDigits.split('-');
|
|
85
|
+
const ndp = (value.toString().split('.')[1] || '').length;
|
|
86
|
+
const minDp = dp1 ? +dp1 : -Infinity;
|
|
87
|
+
const maxDp = dp2 ? +dp2 : Infinity;
|
|
88
|
+
if (ndp < minDp) {
|
|
89
|
+
return value.toFixed(minDp);
|
|
90
|
+
}
|
|
91
|
+
else if (ndp > maxDp) {
|
|
92
|
+
return value.toFixed(maxDp);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
return value.toString();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
return value.toString();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// format抽象方法
|
|
103
|
+
_format(numStr, unit) {
|
|
104
|
+
return `${numStr}${this.convert ? this.convert(unit) : unit}`;
|
|
105
|
+
}
|
|
106
|
+
_formatChain(chain, separator = this.separator) {
|
|
107
|
+
return chain.map(({ numStr, unit }) => this._format(numStr, unit)).join(separator);
|
|
108
|
+
}
|
|
109
|
+
// 将数字格式化为链式单位字符串
|
|
110
|
+
/**
|
|
111
|
+
* Formats a number as a chain of units string
|
|
112
|
+
* @param num - The input number to format
|
|
113
|
+
* @returns The formatted chain string
|
|
114
|
+
*/
|
|
115
|
+
formatChain(num, separator = this.separator) {
|
|
116
|
+
const chain = this.getChainUnit(num);
|
|
117
|
+
return this._formatChain(chain, separator);
|
|
118
|
+
}
|
|
119
|
+
// 从给定字符串中分离出数字部分和单位
|
|
120
|
+
/**
|
|
121
|
+
* Splits a string into its numeric part and unit
|
|
122
|
+
*
|
|
123
|
+
* @param str - Input string containing a number followed by a unit
|
|
124
|
+
* @returns An object containing the numeric value, unit, and Decimal instance
|
|
125
|
+
* @throws An error if no predefined unit is matched
|
|
126
|
+
*/
|
|
127
|
+
splitUnit(str) {
|
|
128
|
+
const sortedUnits = this.sortedUnitNames;
|
|
129
|
+
for (const unit of sortedUnits) {
|
|
130
|
+
if (str.endsWith(unit)) {
|
|
131
|
+
const numStr = str.slice(0, -unit.length);
|
|
132
|
+
const num = +numStr;
|
|
133
|
+
if (Number.isNaN(num))
|
|
134
|
+
throw new Error(`Invalid number: "${numStr}".`);
|
|
135
|
+
return {
|
|
136
|
+
num,
|
|
137
|
+
unit,
|
|
138
|
+
numStr,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`Undefined unit: "${str}".`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ERROR_NAN_INPUT = 'Accepting NaN as an argument may be unintentional and could lead to invalid results.';
|
|
147
|
+
|
|
148
|
+
exports.ERROR_NAN_INPUT = ERROR_NAN_INPUT;
|
|
149
|
+
exports.SmartUnitBase = SmartUnitBase;
|