@nstc-business/imes 1.0.35 → 1.0.36

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@nstc-business/imes",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
4
4
  "private": false,
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -85,8 +85,9 @@
85
85
 
86
86
  <script>
87
87
  import { InputNumber } from './ui'
88
- import { toFixedTrunc, addThousands } from '../common'
88
+ import { addThousands } from '../common'
89
89
  import { formatScoreText } from './helpers'
90
+ import { formatFixedDecimal } from './ui/numberFormat'
90
91
 
91
92
  export default {
92
93
  name: 'MeasureStatCell',
@@ -120,9 +121,9 @@ export default {
120
121
  originalValue(cell) {
121
122
  const val = cell.indicatorResult
122
123
  if (val === '' || val == null) return ''
123
- let text = Number(val).toFixed(this.digits)
124
+ let text = formatFixedDecimal(val, this.digits)
124
125
  if (cell.showStyle === 2) {
125
- text = addThousands(toFixedTrunc(val, this.digits))
126
+ text = addThousands(text)
126
127
  }
127
128
  const suffix = cell.displayStyle === 1 && (val || val === 0) ? '%' : ''
128
129
  return text + suffix
@@ -144,9 +145,9 @@ export default {
144
145
  const val = cell.value
145
146
  if (val === '' || val == null) return ''
146
147
  if (typeof val === 'object' && val.scoreText) return val.scoreText
147
- let text = val || val === 0 ? Number(val).toFixed(this.digits) : ''
148
+ let text = val || val === 0 ? formatFixedDecimal(val, this.digits) : ''
148
149
  if (cell.showStyle === 2 && text) {
149
- text = addThousands(toFixedTrunc(val, this.digits))
150
+ text = addThousands(text)
150
151
  }
151
152
  const suffix = cell.displayStyle === 1 && (val || val === 0) ? '%' : ''
152
153
  return text + suffix
@@ -11,6 +11,7 @@
11
11
  import { N } from 'n20-common-lib'
12
12
  import { addThousands } from '../common'
13
13
  import { resolveManualFillValue, statItemChildren } from './statModelHelper'
14
+ import { formatFixedDecimal } from './ui/numberFormat'
14
15
 
15
16
  export const ITEM_TYPE = {
16
17
  CALC_MODEL: 11,
@@ -239,7 +240,7 @@ export function formatModelResultText(val, modelWrap, options = {}) {
239
240
  if (val === '' || val == null) return ''
240
241
  const num = Number(val)
241
242
  if (Number.isNaN(num)) return String(val)
242
- let text = num.toFixed(modelResultDigits(modelWrap))
243
+ let text = formatFixedDecimal(num, modelResultDigits(modelWrap))
243
244
  if (options.thousands) {
244
245
  text = addThousands(text)
245
246
  }
@@ -255,7 +256,7 @@ export function formatScoreText(val, options = {}) {
255
256
  if (val === '' || val == null) return ''
256
257
  const num = Number(val)
257
258
  if (Number.isNaN(num)) return String(val)
258
- let text = num.toFixed(SCORE_DIGITS)
259
+ let text = formatFixedDecimal(num, SCORE_DIGITS)
259
260
  if (options.thousands) {
260
261
  text = addThousands(text)
261
262
  }
@@ -396,7 +397,7 @@ export function indicatorResultText(row, modelWrap) {
396
397
  const suffix =
397
398
  row.displayStyle === 1 && (raw || raw === 0) ? '%' : ''
398
399
  if (raw || raw === 0) {
399
- value = Number(raw).toFixed(digitsCalc(row, modelWrap))
400
+ value = formatFixedDecimal(raw, digitsCalc(row, modelWrap))
400
401
  if (row.showStyle === 2) {
401
402
  value = addThousands(value)
402
403
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * 测算数字展示格式化
3
+ * 时间:2026-09-14
4
+ * 人员:AI && 徐红飞
5
+ * 入参:val 原始值;digits 小数位数
6
+ * 出参:指定小数位的十进制字符串
7
+ * 方法内容:不用 Number.toFixed。JS 二进制浮点会把 132898923.63 格式化成 132898923.6299999952
8
+ */
9
+
10
+ /**
11
+ * 按指定小数位四舍五入格式化数字
12
+ * 时间:2026-09-14
13
+ * 人员:AI && 徐红飞
14
+ * 入参:val 数字或数字字符串;digits 小数位数
15
+ * 出参:格式化后的字符串;空值返回 '';非数字字符串原样返回
16
+ * 方法内容:先转最短十进制串再按位四舍五入补零,避开原生 toFixed 的精度噪声
17
+ */
18
+ export function formatFixedDecimal(val, digits) {
19
+ if (val === '' || val == null) return ''
20
+ const raw = typeof val === 'string' ? val.replace(/,/g, '').trim() : val
21
+ if (raw === '') return ''
22
+ if (typeof raw === 'string' && Number.isNaN(Number(raw))) {
23
+ return String(val)
24
+ }
25
+ const n = typeof raw === 'number' ? raw : Number(raw)
26
+ if (Number.isNaN(n)) return String(val)
27
+ const d = digits == null || digits === '' ? 2 : Number(digits)
28
+ if (!Number.isFinite(d) || d < 0) return String(n)
29
+ return roundPlainDecimal(numberToPlainString(n), d)
30
+ }
31
+
32
+ /**
33
+ * 把 Number 转成普通十进制字符串(处理科学计数法)
34
+ * 时间:2026-09-14
35
+ * 人员:AI && 徐红飞
36
+ * 入参:n 有限数字
37
+ * 出参:不含 e 的十进制字符串
38
+ * 方法内容:String(n) 遇到科学计数法时按指数展开
39
+ */
40
+ function numberToPlainString(n) {
41
+ const s = String(n)
42
+ if (!/[eE]/.test(s)) return s
43
+ const sign = s.charAt(0) === '-' ? '-' : ''
44
+ const body = sign ? s.slice(1) : s
45
+ const [coeff, expStr] = body.split(/[eE]/)
46
+ let exp = Number(expStr)
47
+ const digits = coeff.replace('.', '')
48
+ const decLen = (coeff.split('.')[1] || '').length
49
+ exp -= decLen
50
+ if (exp >= 0) {
51
+ return sign + digits + '0'.repeat(exp)
52
+ }
53
+ const zeros = Math.abs(exp) - 1
54
+ return sign + '0.' + '0'.repeat(zeros) + digits
55
+ }
56
+
57
+ /**
58
+ * 对普通十进制字符串按小数位四舍五入并补零
59
+ * 时间:2026-09-14
60
+ * 人员:AI && 徐红飞
61
+ * 入参:str 十进制字符串;digits 小数位数
62
+ * 出参:补齐小数位后的字符串
63
+ * 方法内容:用整数进位避免浮点乘法误差
64
+ */
65
+ function roundPlainDecimal(str, digits) {
66
+ const neg = str.charAt(0) === '-'
67
+ if (neg) str = str.slice(1)
68
+ let [intPart, frac = ''] = str.split('.')
69
+ intPart = intPart || '0'
70
+ if (digits === 0) {
71
+ const roundUp = (frac.charAt(0) || '0') >= '5'
72
+ let i = BigInt(intPart)
73
+ if (roundUp) i += 1n
74
+ return (neg && i !== 0n ? '-' : '') + i.toString()
75
+ }
76
+ frac = frac.padEnd(digits + 1, '0')
77
+ const keep = frac.slice(0, digits)
78
+ const next = frac.charAt(digits)
79
+ if (next >= '5') {
80
+ const width = intPart.length + digits
81
+ const asInt = BigInt(intPart + keep) + 1n
82
+ let s = asInt.toString()
83
+ if (s.length < width) s = s.padStart(width, '0')
84
+ intPart = s.slice(0, s.length - digits) || '0'
85
+ frac = s.slice(s.length - digits)
86
+ } else {
87
+ frac = keep
88
+ }
89
+ return (neg ? '-' : '') + intPart + '.' + frac
90
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 数字展示精度:禁止 Number.toFixed 把 132898923.63 显示成 132898923.6299999952
3
+ */
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+
7
+ const src = fs
8
+ .readFileSync(path.join(__dirname, 'numberFormat.js'), 'utf8')
9
+ .replace(/export /g, '')
10
+ const exportsFromSrc = {}
11
+ new Function('exports', src + '\nexports.formatFixedDecimal = formatFixedDecimal')(
12
+ exportsFromSrc
13
+ )
14
+ const { formatFixedDecimal } = exportsFromSrc
15
+
16
+ const nativeBug = (132898923.63).toFixed(10)
17
+ if (nativeBug !== '132898923.6299999952') {
18
+ throw new Error('用例前提变化:原生 toFixed(10) 不再复现 6299999952,实际: ' + nativeBug)
19
+ }
20
+
21
+ const padded = formatFixedDecimal(132898923.63, 10)
22
+ if (padded !== '132898923.6300000000') {
23
+ throw new Error('10 位应补零为 132898923.6300000000,实际: ' + padded)
24
+ }
25
+
26
+ const two = formatFixedDecimal(132898923.63, 2)
27
+ if (two !== '132898923.63') {
28
+ throw new Error('2 位应为 132898923.63,实际: ' + two)
29
+ }
30
+
31
+ const empty = formatFixedDecimal(null, 10)
32
+ if (empty !== '') {
33
+ throw new Error('空值应返回空字符串,实际: ' + empty)
34
+ }
35
+
36
+ const text = formatFixedDecimal('abc', 2)
37
+ if (text !== 'abc') {
38
+ throw new Error('非数字字符串应原样返回,实际: ' + text)
39
+ }
40
+
41
+ const rounded = formatFixedDecimal(1.239, 2)
42
+ if (rounded !== '1.24') {
43
+ throw new Error('四舍五入 1.239 保留 2 位应为 1.24,实际: ' + rounded)
44
+ }
45
+
46
+ console.log('numberFormat.test.js passed')