n20-common-lib 3.3.14 → 3.3.16
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/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-descriptions class="n20-descriptions" :column="
|
|
2
|
+
<el-descriptions class="n20-descriptions" :column="currentColumn" size="default" border>
|
|
3
3
|
<slot></slot>
|
|
4
|
-
<template v-if="this.$slots.default.length === 1">
|
|
5
|
-
<el-descriptions-item v-for="(item, index) in
|
|
4
|
+
<template v-if="this.$slots.default && this.$slots.default.length === 1">
|
|
5
|
+
<el-descriptions-item v-for="(item, index) in currentColumn - 1" :key="index" />
|
|
6
6
|
</template>
|
|
7
7
|
</el-descriptions>
|
|
8
8
|
</template>
|
|
@@ -20,28 +20,48 @@ export default {
|
|
|
20
20
|
default: true
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
|
|
23
|
+
data() {
|
|
24
|
+
return {
|
|
25
|
+
currentColumn: this.column
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
watch: {
|
|
29
|
+
column(val) {
|
|
30
|
+
this.currentColumn = val
|
|
31
|
+
},
|
|
32
|
+
autoResize(val) {
|
|
33
|
+
if (val) {
|
|
34
|
+
this.createResize()
|
|
35
|
+
} else {
|
|
36
|
+
this.currentColumn = this.column
|
|
37
|
+
this.destroyResize()
|
|
38
|
+
}
|
|
27
39
|
}
|
|
28
40
|
},
|
|
41
|
+
created() {
|
|
42
|
+
if (!this.autoResize) return
|
|
43
|
+
this.createResize()
|
|
44
|
+
},
|
|
29
45
|
beforeDestroy() {
|
|
30
|
-
|
|
46
|
+
this.destroyResize()
|
|
31
47
|
},
|
|
32
48
|
methods: {
|
|
33
49
|
resize() {
|
|
34
50
|
let wW = document.documentElement.clientWidth
|
|
35
51
|
if (wW <= 1024) {
|
|
36
|
-
this.
|
|
52
|
+
this.currentColumn = 1
|
|
37
53
|
} else if (wW > 1024 && wW <= 1600) {
|
|
38
|
-
this.
|
|
54
|
+
this.currentColumn = 2
|
|
39
55
|
} else if (wW > 1600 && wW <= 1920) {
|
|
40
|
-
this.
|
|
56
|
+
this.currentColumn = 3
|
|
41
57
|
} else if (wW > 1920) {
|
|
42
|
-
this.
|
|
58
|
+
this.currentColumn = 4
|
|
43
59
|
}
|
|
44
60
|
},
|
|
61
|
+
createResize() {
|
|
62
|
+
this.resize()
|
|
63
|
+
window.addEventListener('resize', this.resize)
|
|
64
|
+
},
|
|
45
65
|
destroyResize() {
|
|
46
66
|
window.removeEventListener('resize', this.resize)
|
|
47
67
|
}
|
|
@@ -31,10 +31,11 @@
|
|
|
31
31
|
| disabled | Boolean | undefined | 是否禁用 |
|
|
32
32
|
| isClearable | Boolean | false | 是否可清空 |
|
|
33
33
|
| placeholder | String | '请输入' | 占位提示文本 |
|
|
34
|
-
| dNum | Number | undefined | 小数位数,优先级低于format
|
|
35
|
-
| format | String | undefined | 格式化字符串,如'0.00'表示保留2
|
|
34
|
+
| dNum | Number | undefined | 小数位数,优先级低于format;超过15位时以字符串保持精度;与format、rangeAuto同时使用时表示最大小数位数 |
|
|
35
|
+
| format | String | undefined | 格式化字符串,如'0.00'表示保留2位小数;与dNum、rangeAuto同时使用时表示最少展示的小数位数 |
|
|
36
36
|
| suffix | String | '%' | 后缀文本,仅在type='rate'时有效 |
|
|
37
37
|
| rangeAuto | Boolean | false | 是否自动处理范围值 |
|
|
38
|
+
| isString | Boolean | false | 是否始终以字符串回传 input/change 值;小数位超过15位时自动按 true 处理 |
|
|
38
39
|
|
|
39
40
|
## 类型说明
|
|
40
41
|
|
|
@@ -60,6 +61,10 @@
|
|
|
60
61
|
|
|
61
62
|
## 事件
|
|
62
63
|
|
|
64
|
+
当 `dNum` 或 `format` 设置为超过 15 位小数时,组件会强制按 `isString=true` 处理,即使未显式配置 `isString`,`input` 和 `change` 也会回传字符串,避免 JavaScript `Number` 丢失精度;失焦时会按最大位数四舍五入并移除尾部 `0`,例如 `0.200000` 会回传 `"0.2"`。设置 `isString` 为 `true` 后,无论小数位数多少,`input` 和 `change` 都回传字符串;清空时回传空字符串。
|
|
65
|
+
|
|
66
|
+
当 `rangeAuto=true` 且同时配置 `format` 和 `dNum` 时,`format` 表示最少展示的小数位数,`dNum` 表示最多保留的小数位数。例如 `format="0.000000"`、`dNum=20` 时,输入 `0.2` 显示为 `0.200000`,输入 7 至 20 位小数按实际位数显示,超过 20 位时按 20 位四舍五入。
|
|
67
|
+
|
|
63
68
|
| 事件名 | 说明 | 回调参数 |
|
|
64
69
|
| ------ | ----------- | ---- |
|
|
65
70
|
| input | 值改变时触发 | 当前值 |
|
|
@@ -109,4 +114,3 @@
|
|
|
109
114
|
<!-- 启用步进严格模式 -->
|
|
110
115
|
<InputNumber v-model="value" type="number" :step="0.5" :stepStrictly="true" />
|
|
111
116
|
```
|
|
112
|
-
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
<script>
|
|
34
34
|
import emitter from '../../utils/element-ui-emitter'
|
|
35
35
|
import { $lc } from '../../utils/i18n/index'
|
|
36
|
-
import N from '../../utils/numberPor'
|
|
36
|
+
import N, { addDecimal, compareDecimal, decimalToFixed, normalizeDecimal } from '../../utils/numberPor'
|
|
37
|
+
|
|
38
|
+
const MAX_SAFE_DECIMAL_PLACES = 15
|
|
37
39
|
|
|
38
40
|
export default {
|
|
39
41
|
name: 'InputNumber',
|
|
@@ -103,6 +105,10 @@ export default {
|
|
|
103
105
|
rangeAuto: {
|
|
104
106
|
type: Boolean,
|
|
105
107
|
default: false
|
|
108
|
+
},
|
|
109
|
+
isString: {
|
|
110
|
+
type: Boolean,
|
|
111
|
+
default: false
|
|
106
112
|
}
|
|
107
113
|
},
|
|
108
114
|
data() {
|
|
@@ -155,6 +161,20 @@ export default {
|
|
|
155
161
|
watch: {
|
|
156
162
|
value: {
|
|
157
163
|
handler(val) {
|
|
164
|
+
if (this.isHighPrecision()) {
|
|
165
|
+
if (val === undefined || val === null || val === '') {
|
|
166
|
+
this.valueStr = ''
|
|
167
|
+
} else {
|
|
168
|
+
const normalized = normalizeDecimal(val)
|
|
169
|
+
if (!normalized) {
|
|
170
|
+
this.valueStr = ''
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
const formatted = this.normalizeHighPrecisionValue(val)
|
|
174
|
+
this.valueStr = this.type === 'number' ? formatted : N.addThousands(formatted)
|
|
175
|
+
}
|
|
176
|
+
return
|
|
177
|
+
}
|
|
158
178
|
// number类型不启用千分位格式化,但应用小数位限制
|
|
159
179
|
if (this.type === 'number') {
|
|
160
180
|
if (val === undefined || val === null) {
|
|
@@ -189,6 +209,45 @@ export default {
|
|
|
189
209
|
}
|
|
190
210
|
},
|
|
191
211
|
methods: {
|
|
212
|
+
isAdaptivePrecision() {
|
|
213
|
+
return this.rangeAuto && this.format && (this.dNum || this.dNum === 0)
|
|
214
|
+
},
|
|
215
|
+
maxDecimalPlaces() {
|
|
216
|
+
return this.isAdaptivePrecision()
|
|
217
|
+
? Math.max(this.fNum, this.dNum)
|
|
218
|
+
: this.fNum
|
|
219
|
+
},
|
|
220
|
+
isHighPrecision() {
|
|
221
|
+
return this.maxDecimalPlaces() > MAX_SAFE_DECIMAL_PLACES
|
|
222
|
+
},
|
|
223
|
+
// 高精度小数会强制使用字符串回传,即使 isString 未开启。
|
|
224
|
+
shouldEmitString() {
|
|
225
|
+
return this.isString || this.isHighPrecision()
|
|
226
|
+
},
|
|
227
|
+
normalizeHighPrecisionValue(value) {
|
|
228
|
+
const normalized = normalizeDecimal(value)
|
|
229
|
+
if (!normalized) return ''
|
|
230
|
+
|
|
231
|
+
const decimalPart = normalized.split('.')[1] || ''
|
|
232
|
+
if (this.isAdaptivePrecision()) {
|
|
233
|
+
const originalValue = String(value).replace(/,/g, '').trim()
|
|
234
|
+
const originalFraction = originalValue.match(/\.(\d*)$/)
|
|
235
|
+
const decimalPlaces = originalFraction
|
|
236
|
+
? originalFraction[1].length
|
|
237
|
+
: decimalPart.length
|
|
238
|
+
if (decimalPlaces < this.fNum) {
|
|
239
|
+
return decimalToFixed(normalized, this.fNum)
|
|
240
|
+
}
|
|
241
|
+
if (decimalPlaces > this.maxDecimalPlaces()) {
|
|
242
|
+
return decimalToFixed(normalized, this.maxDecimalPlaces())
|
|
243
|
+
}
|
|
244
|
+
return decimalToFixed(normalized, decimalPlaces)
|
|
245
|
+
}
|
|
246
|
+
const roundedValue = this.rangeAuto && decimalPart.length >= this.fNum
|
|
247
|
+
? normalized
|
|
248
|
+
: decimalToFixed(normalized, this.fNum)
|
|
249
|
+
return normalizeDecimal(roundedValue)
|
|
250
|
+
},
|
|
192
251
|
focusFn() {
|
|
193
252
|
this.isFocus = true
|
|
194
253
|
if (!this.disabled && this.valueStr) {
|
|
@@ -210,6 +269,24 @@ export default {
|
|
|
210
269
|
},
|
|
211
270
|
stepFn(ev) {
|
|
212
271
|
if ((ev.code === 'ArrowUp' || ev.code === 'ArrowDown') && !this.disabled) {
|
|
272
|
+
if (this.isHighPrecision()) {
|
|
273
|
+
const currentValue = normalizeDecimal(this.valueStr)
|
|
274
|
+
if (!currentValue) return
|
|
275
|
+
|
|
276
|
+
ev.preventDefault()
|
|
277
|
+
const stepValue = N.toString(this.step)
|
|
278
|
+
let nextValue = addDecimal(currentValue, ev.code === 'ArrowUp' ? stepValue : '-' + stepValue)
|
|
279
|
+
if (compareDecimal(nextValue, this.min) < 0) {
|
|
280
|
+
nextValue = normalizeDecimal(this.min)
|
|
281
|
+
} else if (compareDecimal(nextValue, this.max) > 0) {
|
|
282
|
+
nextValue = normalizeDecimal(this.max)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const formatted = this.normalizeHighPrecisionValue(nextValue)
|
|
286
|
+
this.valueStr = this.type === 'number' ? formatted : N.addThousands(formatted)
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
213
290
|
let val = N(this.valueStr)
|
|
214
291
|
if (!isNaN(val)) {
|
|
215
292
|
ev.preventDefault()
|
|
@@ -243,14 +320,53 @@ export default {
|
|
|
243
320
|
this.changeIng = false
|
|
244
321
|
})
|
|
245
322
|
|
|
323
|
+
if (this.isHighPrecision()) {
|
|
324
|
+
const normalized = normalizeDecimal(valStr)
|
|
325
|
+
if (!normalized) {
|
|
326
|
+
this.valueStr = ''
|
|
327
|
+
const oldValue = this.value
|
|
328
|
+
if (oldValue || oldValue === 0 || oldValue === '0') {
|
|
329
|
+
const emptyValue = this.shouldEmitString() ? '' : undefined
|
|
330
|
+
this.$emit('input', emptyValue)
|
|
331
|
+
this.$emit('change', emptyValue)
|
|
332
|
+
this.dispatch('ElFormItem', 'el.form.change', [emptyValue])
|
|
333
|
+
}
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let nextValue = this.normalizeHighPrecisionValue(valStr)
|
|
338
|
+
|
|
339
|
+
if (compareDecimal(nextValue, this.min) < 0) {
|
|
340
|
+
nextValue = normalizeDecimal(this.min)
|
|
341
|
+
} else if (compareDecimal(nextValue, this.max) > 0) {
|
|
342
|
+
nextValue = normalizeDecimal(this.max)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
nextValue = this.normalizeHighPrecisionValue(nextValue)
|
|
346
|
+
|
|
347
|
+
const formatted = this.type === 'number' ? nextValue : N.addThousands(nextValue)
|
|
348
|
+
this.valueStr = formatted
|
|
349
|
+
this.$nextTick(() => {
|
|
350
|
+
const oldValue = normalizeDecimal(this.value)
|
|
351
|
+
const normalizedNextValue = normalizeDecimal(nextValue)
|
|
352
|
+
if (oldValue !== normalizedNextValue) {
|
|
353
|
+
this.$emit('input', nextValue)
|
|
354
|
+
this.$emit('change', nextValue)
|
|
355
|
+
this.dispatch('ElFormItem', 'el.form.change', [nextValue])
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
246
361
|
let val = N(valStr)
|
|
247
362
|
if (isNaN(val)) {
|
|
248
363
|
this.valueStr = ''
|
|
249
364
|
let oVal = this.value
|
|
250
365
|
if (oVal || oVal === 0) {
|
|
251
|
-
this
|
|
252
|
-
this.$emit('
|
|
253
|
-
this
|
|
366
|
+
const emptyValue = this.shouldEmitString() ? '' : undefined
|
|
367
|
+
this.$emit('input', emptyValue)
|
|
368
|
+
this.$emit('change', emptyValue)
|
|
369
|
+
this.dispatch('ElFormItem', 'el.form.change', [emptyValue])
|
|
254
370
|
}
|
|
255
371
|
return
|
|
256
372
|
}
|
|
@@ -294,10 +410,12 @@ export default {
|
|
|
294
410
|
this.$nextTick(() => {
|
|
295
411
|
let nVar = Number(nStr)
|
|
296
412
|
let oVal = this.value
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this
|
|
413
|
+
const emittedValue = this.shouldEmitString() ? nStr : nVar
|
|
414
|
+
const valueChanged = this.shouldEmitString() ? String(oVal) !== nStr : oVal !== nVar
|
|
415
|
+
if (valueChanged) {
|
|
416
|
+
this.$emit('input', emittedValue)
|
|
417
|
+
this.$emit('change', emittedValue)
|
|
418
|
+
this.dispatch('ElFormItem', 'el.form.change', [emittedValue])
|
|
301
419
|
}
|
|
302
420
|
})
|
|
303
421
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<div v-if="size !== 'mini'" class="flex-box p-t-ss p-b-ss">
|
|
4
4
|
<statisItem
|
|
5
|
-
:type="
|
|
5
|
+
:type="pageTotalLabel"
|
|
6
6
|
:list="pageList"
|
|
7
7
|
:show-w="showW"
|
|
8
8
|
:show-u="showU"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
<div v-else class="n20-statis p-t-ss p-b-ss">
|
|
20
20
|
<span class="m-r">
|
|
21
|
-
{{
|
|
21
|
+
{{ label + ':' }}<span class="n20-primary"> {{ pageSum }}</span> {{ '条' | $lc }}
|
|
22
22
|
</span>
|
|
23
23
|
<span class="m-r">
|
|
24
24
|
{{ '全部:' | $lc }}<span class="n20-primary"> {{ totalSun }}</span> {{ '条' | $lc }}
|
|
@@ -28,8 +28,10 @@
|
|
|
28
28
|
</template>
|
|
29
29
|
|
|
30
30
|
<script>
|
|
31
|
-
import { $lc } from '../../utils/i18n/index'
|
|
32
31
|
import statisItem from './statisItem.vue'
|
|
32
|
+
|
|
33
|
+
import { $lc } from '../../utils/i18n/index'
|
|
34
|
+
|
|
33
35
|
export default {
|
|
34
36
|
name: 'Statis',
|
|
35
37
|
components: {
|
|
@@ -70,10 +72,14 @@ export default {
|
|
|
70
72
|
},
|
|
71
73
|
label: {
|
|
72
74
|
type: String,
|
|
73
|
-
default: $lc(
|
|
75
|
+
default: $lc('已选')
|
|
74
76
|
}
|
|
75
77
|
},
|
|
76
78
|
computed: {
|
|
79
|
+
pageTotalLabel() {
|
|
80
|
+
const separator = ['zh', 'ja', 'th'].includes(this._lang || 'zh') ? '' : ' '
|
|
81
|
+
return this.label + separator + $lc('合计')
|
|
82
|
+
},
|
|
77
83
|
unitAs() {
|
|
78
84
|
if (!this.showU) {
|
|
79
85
|
return undefined
|
package/src/utils/numberPor.js
CHANGED
|
@@ -197,6 +197,198 @@ export const subFixed = function (n, decimal = 6) {
|
|
|
197
197
|
return XEUtils.toFixed(n, decimal)
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
+
|
|
201
|
+
const decimalPattern = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/
|
|
202
|
+
|
|
203
|
+
function padRight(value, length) {
|
|
204
|
+
return value + '0'.repeat(Math.max(0, length - value.length))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function padLeft(value, length) {
|
|
208
|
+
return '0'.repeat(Math.max(0, length - value.length)) + value
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 规范化十进制数字字符串,不经过 Number,避免高精度小数丢失精度。
|
|
213
|
+
* @param {number|string} value
|
|
214
|
+
* @returns {string}
|
|
215
|
+
*/
|
|
216
|
+
export const normalizeDecimal = function (value) {
|
|
217
|
+
if (value === undefined || value === null || value === '') return ''
|
|
218
|
+
|
|
219
|
+
const stringValue = toString(value).replace(/,/g, '').trim()
|
|
220
|
+
if (!decimalPattern.test(stringValue)) return ''
|
|
221
|
+
|
|
222
|
+
const sign = stringValue.charAt(0) === '-' ? '-' : ''
|
|
223
|
+
const unsignedValue = stringValue.replace(/^[+-]/, '')
|
|
224
|
+
let [integerPart = '0', decimalPart = ''] = unsignedValue.split('.')
|
|
225
|
+
integerPart = integerPart.replace(/^0+(?=\d)/, '') || '0'
|
|
226
|
+
decimalPart = decimalPart.replace(/0+$/, '')
|
|
227
|
+
|
|
228
|
+
return sign + integerPart + (decimalPart ? '.' + decimalPart : '')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function compareUnsignedDecimal(a, b) {
|
|
232
|
+
let [aInteger = '0', aDecimal = ''] = a.split('.')
|
|
233
|
+
let [bInteger = '0', bDecimal = ''] = b.split('.')
|
|
234
|
+
aInteger = aInteger.replace(/^0+(?=\d)/, '') || '0'
|
|
235
|
+
bInteger = bInteger.replace(/^0+(?=\d)/, '') || '0'
|
|
236
|
+
|
|
237
|
+
if (aInteger.length !== bInteger.length) {
|
|
238
|
+
return aInteger.length > bInteger.length ? 1 : -1
|
|
239
|
+
}
|
|
240
|
+
if (aInteger !== bInteger) return aInteger > bInteger ? 1 : -1
|
|
241
|
+
|
|
242
|
+
const decimalLength = Math.max(aDecimal.length, bDecimal.length)
|
|
243
|
+
aDecimal = padRight(aDecimal, decimalLength)
|
|
244
|
+
bDecimal = padRight(bDecimal, decimalLength)
|
|
245
|
+
if (aDecimal === bDecimal) return 0
|
|
246
|
+
return aDecimal > bDecimal ? 1 : -1
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 比较两个十进制数字字符串。
|
|
251
|
+
* @param {number|string} left
|
|
252
|
+
* @param {number|string} right
|
|
253
|
+
* @returns {number} -1、0、1
|
|
254
|
+
*/
|
|
255
|
+
export const compareDecimal = function (left, right) {
|
|
256
|
+
const a = normalizeDecimal(left)
|
|
257
|
+
const b = normalizeDecimal(right)
|
|
258
|
+
if (!a || !b) return NaN
|
|
259
|
+
|
|
260
|
+
const aNegative = a.charAt(0) === '-'
|
|
261
|
+
const bNegative = b.charAt(0) === '-'
|
|
262
|
+
const aUnsigned = a.replace(/^-/, '')
|
|
263
|
+
const bUnsigned = b.replace(/^-/, '')
|
|
264
|
+
|
|
265
|
+
if (aNegative !== bNegative) return aNegative ? -1 : 1
|
|
266
|
+
const result = compareUnsignedDecimal(aUnsigned, bUnsigned)
|
|
267
|
+
return aNegative ? -result : result
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function incrementUnsignedDecimal(value) {
|
|
271
|
+
const digits = value.split('')
|
|
272
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
273
|
+
if (digits[i] === '9') {
|
|
274
|
+
digits[i] = '0'
|
|
275
|
+
} else {
|
|
276
|
+
digits[i] = String(Number(digits[i]) + 1)
|
|
277
|
+
return digits.join('')
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return '1' + digits.join('')
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 使用字符串运算按指定小数位四舍五入,不经过 Number。
|
|
285
|
+
* @param {number|string} value
|
|
286
|
+
* @param {number} decimal
|
|
287
|
+
* @returns {string}
|
|
288
|
+
*/
|
|
289
|
+
export const decimalToFixed = function (value, decimal = 0) {
|
|
290
|
+
const normalized = normalizeDecimal(value)
|
|
291
|
+
if (!normalized) return ''
|
|
292
|
+
|
|
293
|
+
const precision = Math.max(0, Number(decimal) || 0)
|
|
294
|
+
const negative = normalized.charAt(0) === '-'
|
|
295
|
+
const unsignedValue = normalized.replace(/^-/, '')
|
|
296
|
+
let [integerPart = '0', decimalPart = ''] = unsignedValue.split('.')
|
|
297
|
+
const shouldRound = decimalPart.length > precision && decimalPart.charAt(precision) >= '5'
|
|
298
|
+
decimalPart = padRight(decimalPart.slice(0, precision), precision)
|
|
299
|
+
|
|
300
|
+
if (shouldRound) {
|
|
301
|
+
const incremented = incrementUnsignedDecimal(integerPart + decimalPart)
|
|
302
|
+
integerPart = incremented.slice(0, Math.max(1, incremented.length - precision))
|
|
303
|
+
decimalPart = precision ? padLeft(incremented.slice(-precision), precision) : ''
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return (negative ? '-' : '') + integerPart + (precision ? '.' + decimalPart : '')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function addUnsignedDecimal(left, right) {
|
|
310
|
+
let [leftInteger = '0', leftDecimal = ''] = left.split('.')
|
|
311
|
+
let [rightInteger = '0', rightDecimal = ''] = right.split('.')
|
|
312
|
+
const decimalLength = Math.max(leftDecimal.length, rightDecimal.length)
|
|
313
|
+
leftDecimal = padRight(leftDecimal, decimalLength)
|
|
314
|
+
rightDecimal = padRight(rightDecimal, decimalLength)
|
|
315
|
+
|
|
316
|
+
let carry = 0
|
|
317
|
+
let result = ''
|
|
318
|
+
const leftDigits = (leftInteger + leftDecimal).split('')
|
|
319
|
+
const rightDigits = (rightInteger + rightDecimal).split('')
|
|
320
|
+
const length = Math.max(leftDigits.length, rightDigits.length)
|
|
321
|
+
for (let i = 0; i < length; i += 1) {
|
|
322
|
+
const leftDigit = Number(leftDigits[leftDigits.length - 1 - i] || 0)
|
|
323
|
+
const rightDigit = Number(rightDigits[rightDigits.length - 1 - i] || 0)
|
|
324
|
+
const sum = leftDigit + rightDigit + carry
|
|
325
|
+
result = String(sum % 10) + result
|
|
326
|
+
carry = Math.floor(sum / 10)
|
|
327
|
+
}
|
|
328
|
+
if (carry) result = String(carry) + result
|
|
329
|
+
|
|
330
|
+
const integerLength = result.length - decimalLength
|
|
331
|
+
const integerPart = result.slice(0, Math.max(1, integerLength))
|
|
332
|
+
const decimalPart = decimalLength ? result.slice(-decimalLength) : ''
|
|
333
|
+
return integerPart + (decimalPart ? '.' + decimalPart : '')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function subtractUnsignedDecimal(left, right) {
|
|
337
|
+
let [leftInteger = '0', leftDecimal = ''] = left.split('.')
|
|
338
|
+
let [rightInteger = '0', rightDecimal = ''] = right.split('.')
|
|
339
|
+
const decimalLength = Math.max(leftDecimal.length, rightDecimal.length)
|
|
340
|
+
leftDecimal = padRight(leftDecimal, decimalLength)
|
|
341
|
+
rightDecimal = padRight(rightDecimal, decimalLength)
|
|
342
|
+
|
|
343
|
+
let borrow = 0
|
|
344
|
+
let result = ''
|
|
345
|
+
const leftDigits = (leftInteger + leftDecimal).split('')
|
|
346
|
+
const rightDigits = (rightInteger + rightDecimal).split('')
|
|
347
|
+
const length = Math.max(leftDigits.length, rightDigits.length)
|
|
348
|
+
for (let i = 0; i < length; i += 1) {
|
|
349
|
+
let leftDigit = Number(leftDigits[leftDigits.length - 1 - i] || 0) - borrow
|
|
350
|
+
const rightDigit = Number(rightDigits[rightDigits.length - 1 - i] || 0)
|
|
351
|
+
if (leftDigit < rightDigit) {
|
|
352
|
+
leftDigit += 10
|
|
353
|
+
borrow = 1
|
|
354
|
+
} else {
|
|
355
|
+
borrow = 0
|
|
356
|
+
}
|
|
357
|
+
result = String(leftDigit - rightDigit) + result
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const integerLength = result.length - decimalLength
|
|
361
|
+
const integerPart = result.slice(0, Math.max(1, integerLength)).replace(/^0+(?=\d)/, '') || '0'
|
|
362
|
+
const decimalPart = decimalLength ? result.slice(-decimalLength) : ''
|
|
363
|
+
return integerPart + (decimalPart ? '.' + decimalPart : '')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 高精度十进制加法,不经过 Number。
|
|
368
|
+
* @param {number|string} left
|
|
369
|
+
* @param {number|string} right
|
|
370
|
+
* @returns {string}
|
|
371
|
+
*/
|
|
372
|
+
export const addDecimal = function (left, right) {
|
|
373
|
+
const a = normalizeDecimal(left)
|
|
374
|
+
const b = normalizeDecimal(right)
|
|
375
|
+
if (!a || !b) return ''
|
|
376
|
+
|
|
377
|
+
const aNegative = a.charAt(0) === '-'
|
|
378
|
+
const bNegative = b.charAt(0) === '-'
|
|
379
|
+
const aUnsigned = a.replace(/^-/, '')
|
|
380
|
+
const bUnsigned = b.replace(/^-/, '')
|
|
381
|
+
|
|
382
|
+
if (aNegative === bNegative) {
|
|
383
|
+
const result = addUnsignedDecimal(aUnsigned, bUnsigned)
|
|
384
|
+
return (aNegative ? '-' : '') + normalizeDecimal(result)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const comparison = compareUnsignedDecimal(aUnsigned, bUnsigned)
|
|
388
|
+
if (comparison === 0) return '0'
|
|
389
|
+
if (comparison > 0) return (aNegative ? '-' : '') + normalizeDecimal(subtractUnsignedDecimal(aUnsigned, bUnsigned))
|
|
390
|
+
return (bNegative ? '-' : '') + normalizeDecimal(subtractUnsignedDecimal(bUnsigned, aUnsigned))
|
|
391
|
+
}
|
|
200
392
|
/**
|
|
201
393
|
* 将数字或字符串格式的数字转换为千位分隔符格式的字符串
|
|
202
394
|
* @param {number|string} number - 待转换的数字或字符串格式的数字
|