V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
Livid
V2EX  ›  JavaScript

nstr - number → string, but looks good

  •  2
     
  •   Livid ·
    PRO
    · 6 天前 · 4285 次点击

    https://nstr.vercel.app/

    一个处理数字显示问题的 JS 库。

    尤其是在处理有小数点的数字时会很有用。

    32 条回复    2025-08-27 09:44:35 +08:00
    terryso
        1
    terryso  
       6 天前
    好东西, 看看去
    MENGKE
        2
    MENGKE  
       6 天前
    很有用,感谢分享
    pyyalt
        3
    pyyalt  
       6 天前
    好用,支持!
    dssxzuxc
        4
    dssxzuxc  
       6 天前
    确实 looks good, 源码才 113 行,实现方法很巧妙。

    console.log(new Decimal('0.1').plus(new Decimal('0.2')).toString())
    vs
    console.log(nstr(0.1+0.2))

    因为设计使然,超过 4 位(可自行设定)的 9 和 0 会被认为是噪音然后截断处理,不适用多位小数严格计算场景,其他场景都不错。
    retrocode
        5
    retrocode  
       6 天前
    另外如果有严格小数计算需求, 可以考虑 bignumber.js ,精度极高无任何误差, 在我以前某无聊的变态超大数计算项目里验证过, 小数点后精度 9999 无压力
    Valid
        6
    Valid  
       6 天前
    好东西
    mrlmh00
        7
    mrlmh00  
       6 天前
    我看样例都看不懂
    显示不了 451.79 ?
    451.79999999456789 = 451.8
    451.78999999456789 = 451.78
    451.77999999456789 = 451.78
    451.76999999456789 = 451.77
    hamsterbase
        8
    hamsterbase  
       6 天前   ❤️ 3
    不推荐这个库

    推荐使用 decimal.js 实现精确的计算。
    而不是计算以后猜数字


    0.3 - 0.1 // 0.19999999999999998
    x = new Decimal(0.3)
    x.minus(0.1) // '0.2'
    x // '0.3'
    dssxzuxc
        9
    dssxzuxc  
       6 天前
    @mrlmh00 #7
    这个库只是用来处理 0.1+0.2=0.30000000000000004 这种问题的,让简单的浮点计算适合人类阅读
    产生这种问题的浮点数都有个共同的特点:一连串的 0 或者 9 ,所以这个库就检测 0/9 连续出现的次数,超过判断条件后截断全部 0/9 最后简单处理一下进位
    需要多位小数严谨计算的应该用 decimal.js, bignumber.js
    这个库我觉得已经说得很清楚了,nstr ,就是数字转字符串,字符串是用来看的不是计算的,不需要严谨小数点计算的前端纯展示场景非常非常多
    chesha1
        10
    chesha1  
       6 天前
    为啥不用成熟的高精度库呢,这个库的规则太简单粗暴了
    dssxzuxc
        11
    dssxzuxc  
       6 天前   ❤️ 1
    @chesha1 #10 我觉得我在 4#的例子已经很有说服力了,在你想展示几个浮点数的计算结果时,你希望写的是
    new Decimal(a).plus(new Decimal(b).times(new Decimal(c))).minus(new Decimal(d))
    还是
    nstr(a+b*c-d)
    Mint0315
        12
    Mint0315  
       6 天前
    有用!
    mrlmh00
        13
    mrlmh00  
       6 天前
    @dssxzuxc 没看懂啊 451.78999999456789 = 451.78 为什么没有进位。。
    dssxzuxc
        14
    dssxzuxc  
       6 天前
    @mrlmh00 #13 研究了一下
    https://github.com/shuding/nstr/blob/main/src/index.ts#L64-L77
    进位失败是因为
    451.78+0.01 = 451.78999999999996
    而不是期望的 451.79
    需要一个更稳健的进位方法来解决这个问题
    chesha1
        15
    chesha1  
       6 天前
    @dssxzuxc #11 我会在后端做好 let x = new Decimal(a).plus(new Decimal(b).times(new Decimal(c))).minus(new Decimal(d)),然后让前端 {x}
    UnluckyNinja
        16
    UnluckyNinja  
       6 天前
    说计算精度需求的都跑偏了,网站里面的示例很清楚了,就是为了解决数字格式化的问题,避免因为精度误差导致显示的数字过长或过早使用科学计数法.

    不过 100 多行还是有点多了,网站的对比看下来,toFixed 其实很符合要求,只是不会移除尾部 0 ,那么其实再替换下尾部 0 就够了,一行解决
    200.0003.toFixed(3).replace(/(\.?|(?<=\.\d+))0+$/,'')
    (?<=\.\d+) 就是确保处于小数部分,避免移除了整数部分的尾部 0 ,可能还有其它边界情况没考虑,差不多这个意思。
    Chuckle
        17
    Chuckle  
       6 天前
    适合数据可视化上数字展示
    zbinlin
        18
    zbinlin  
       5 天前
    @dssxzuxc #14 我去提了个 PR
    dssxzuxc
        19
    dssxzuxc  
       5 天前   ❤️ 1
    @UnluckyNinja #16
    我以前项目也写过类似的东西
    ```ts
    function n_str(
    value: number,
    options: { fractionDigits?: number } = {},
    ): string {
    const { fractionDigits = 3 } = options
    const str = value.toFixed(fractionDigits).replace(/(\.?|(?<=\.\d+))0+$/, '')
    return str === '-0' ? '0' : str
    }
    ```
    toFixed()的问题是无法智能判断精度,需要传入参数指定精度
    nstr(1.123456) -> 1.123456
    n_str(1.123456) -> 1.123
    n_str(1.123456, { fractionDigits: 6 }) -> 1.123456
    这里的难点是无法简单判断出浮点数计算的小数位数,比如 0.1+0.2 ,人类都知道应该是 1 位小数点,但是如何从 0.30000000000000004 解析出来,还有科学计数法把这个问题搞得更加复杂,而 nstr 能解决这个问题。手动指定精度 2 行就解决了,为了少写这个参数我不介意在项目里多 install 一个包。

    目前 nstr 我测了所有情况,就剩 456.78999999456789=456.78 这个问题,其他的都正常
    在 nstr 的方案上使用 toFixed 或许能解决这个问题同时不引入其他边缘情况,例如楼上的 pr
    https://github.com/shuding/nstr/pull/2/commits/4713d6447bc8fd3af2e246e63ea2f0edb8445d07
    bli22ard
        20
    bli22ard  
       5 天前
    这个库设计很有问题,0.1+0.2 已经为浮点数了,然后又转字符串,还能将 0.1+0.2 还原为 0.3 。如果你想表示 0.33999999999999998 ,它会给你转 0.34 ,结果就是 0.33999999999999998==0.34 这么做,明显有问题
    dssxzuxc
        21
    dssxzuxc  
       4 天前
    @bli22ard #20
    0.3399999999999999 = 0.34 是预期行为,这个库就是用来做这件事的。
    这已经是第四遍回答了,当你需要显示/计算标准浮点/decimal 时,请使用原生 js 或者 decimal.js, bignumber.js 。
    #16 已经说的很清楚了,这个库的作用是
    `解决数字格式化的问题,避免因为精度误差导致显示的数字过长或过早使用科学计数法`。
    设计上超过一定数量的连续 0 or 9 会直接截断,很显然这样才能实现 0.1 + 0.2 = 0.3 ,而带来的代价就是无法表达含有超过一定数量的连续 0 or 9 。
    这个库的适用场景应该是大屏、统计页面等等各种数据可视化,四位以内小数运算是正确的,连续 0 or 9 超过一定次数就会自动四舍五入,严格计算老实去用 decimal.js, bignumber.js ,这只是解决了非严格 decimal 数据的展示问题,让编写显示浮点计算结果的代码更简单易懂适合人类阅读。
    bli22ard
        22
    bli22ard  
       4 天前
    @dssxzuxc #21 可能做法简洁,但是,我不认为是一个好方法,这样会让行为变得模糊起来。小数最佳实战应该是 decimal.js, bignumber.js 等计算完,然后格式化输出,而不是用这个格式化,造成 0.33999999999999998 看起来像 0.34 的不精确问题
    UnluckyNinja
        23
    UnluckyNinja  
       4 天前 via Android
    @bli22ard #22 怎么还没绕过这个弯,就不是精度的问题。会取近似值的,epsilon 都远远大于精度误差,根本没必要上高精度库,高精度库也解决不了显示问题。
    就假设我有一个原始就是 0.3000004 的数(不是通过计算得来的,可能是网络,可能是可视化的数据集,总之这个数字本身就是这个形式),不需要计算,直接显示成人类可读字符串表示,这个情况你用高精度库有什么意义吗
    bli22ard
        24
    bli22ard  
       4 天前
    @UnluckyNinja #23 你的 0.3000004 ,不是计算得来,那是怎么得来的?是输入吗,为什么输入的不是 0.3 ,而输入 0.3000004 ?如果输入的是 0.3000004 ,显示成 0.3 是不是有问题?你的假设是不是有问题?
    635925926
        25
    635925926  
       4 天前
    @dssxzuxc #11 那也得正确啊,不能为了写起来简单,就忽略正确性
    lvlongxiang199
        26
    lvlongxiang199  
       4 天前
    太投机取巧了, 输入一个大点的 int64 就精度丢失了 https://imgur.com/a/x6FFD5i
    dssxzuxc
        27
    dssxzuxc  
       4 天前
    @635925926 #25 0.1.2 版本已经修复了这个问题了。
    除此之外还有相关的 3 个 pr ,都是用的 toFixed ,简单易懂可以去除一堆边界条件,但是作者都没有接受,依旧采用土法硬算舍入值。
    我对浮点计算没有深入研究,理论上 toFixed 处理 x.xxx9 和 x.xxx0 是完全正确的,而作者看起来似乎在硬扣性能,只有某些条件下才会回退到 toFixed 。
    作者的代码实现还有个多余的
    if (result === '0') {
    result = '0'
    }
    完全没看懂在干啥
    现在我是选了个其中一个 fork ,copy 一下放进 utils 里。
    UnluckyNinja
        28
    UnluckyNinja  
       3 天前
    @bli22ard #24 3000004/1000000 得来的可不可以?来自于网络的数据源很难理解吗?你管人家怎么来的,现在问题就是要展示这个数据,而你非要给计算上高精度库,关键是这个情景下就不涉及计算啊。
    楼主热得快炸了,你就非得让楼主开空调?
    UnluckyNinja
        29
    UnluckyNinja  
       3 天前
    @bli22ard #24 不过这么多人都看错,不怪你,得怪作者毫无必要地放了很多计算过程当作例子,网站文本存在误导,“修复浮点精度问题”但实际上只是显示上,而不是给出一个精确的计算结果。
    然后 11 楼举了的例子也有一定误导,我用高精度库了更可能是因为我需要准确的结果,而不是为了数字转字符串看起来好看,这种情况下根本没有可比性。
    这个库本身就是只有 number 类型到 string 类型转换,这么一个目的和功能。给定一个数字,返回一个字符串,就这么简单,甭管输入哪里来的,用户提供的,库作者想管也管不了。做 OJ 的时候也没人问 input 怎么来的吧。

    不过本来我也不太看好这个库,为了这点功能徒增太多了复杂实现,
    如果本身就要精度正确,那直接上精度库就好了,
    如果要裁剪小数部分,那 toFixed 就可以了,去末尾 0 那就再加个正则替换。
    如果为了智能判断高熵部分并展示……我不知道什么情况下会有这样一个需求,为什么要去在乎一个比 epsilon 小很多的噪音,就算如此,展示比 epsilon 小的值,为什么要做字符比较而不是基于数学方式去判断(例如 for 循环递增提取 5 位移到小数点后,tofixed(5)判断是否等于 1 或 0 ,而不是连续比较 5 个 0 或者 5 个 9 )
    我很怀疑原作者的精神状态……
    bli22ard
        30
    bli22ard  
       3 天前
    UnluckyNinja
        31
    UnluckyNinja  
       3 天前 via Android
    @bli22ard #30 是的这些全是误导,他又不处理计算,都是 js 运行时自己计算,运算结果作为真正参数调用这个库函数,放这些算式纯纯误导。
    他本身就是想解决“显示”有精度误差的数字的问题(作为其中一个功能但不是唯一功能),但正常人都会去想引入高精度库直接解决精度问题,而引入高精度库并不能解决“输入本身是一个有噪音部分小数的显示问题”,which 正是这个库“真正想解决的问题”(尽管他的实现让人感觉是不是被 AI 带坏了)
    但愿你绕过来了
    heiya
        32
    heiya  
       3 天前
    @mrlmh00 #7 银行家舍入法
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2759 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 03:54 · PVG 11:54 · LAX 20:54 · JFK 23:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.