【转】为什么 toFixed 会存在误差?

原文地址:https://wyiyi.github.io/amber/2021/03/25/number-precision/

cover

description: "来看看规范里怎么说"
date: 2021.07.04 10:34
categories:
- JavaScript
tags: [JavaScript, Number]
keywords: toFixed, MDN, precision, ECMAScript


在保留有效数字的时候我们经常会使用到 toFixed() 函数,但发现这个方法会存在一些奇怪的问题。

toFixed() 的值错误?

在JS中会有如下的现象,我们需要对最后的结果值进行保留固定位数且四舍五入处理,但发现结果不是所期望的。

1.5.toFixed(0) // 2 正确
1.35.toFixed(1) // 1.4 正确
1.335.toFixed(2) // 1.33  错误
1.3335.toFixed(3) // 1.333 错误
1.33335.toFixed(4) // 1.3334 正确
1.333335.toFixed(5)  // 1.33333 错误
1.3333335.toFixed(6) // 1.333333 错误

MDN 中关于 toFixed 的 Warning 也表明了浮点数不能以二进制精确表示所有小数,这可能会导致意外结果:

Warning: Floating point numbers cannot represent all decimals precisely in binary.
This can lead to unexpected results, such as 0.1 + 0.2 === 0.3 returning false .

为什么会导致 toFixed() 的值不准确呢?

ECMAScript® 2015 Language Specification(Standard ECMA-262
6th Edition,即 ES6)中关于 Number.prototype.toFixed 描述如下:

Number.prototype.toFixed(fractionDigits)

The following steps are performed:

    1. Let x be thisNumberValue(this value).
    2. ReturnIfAbrupt(x).
    3. Let f be ToInteger(fractionDigits). (If fractionDigits is undefined, this step produces the value 0).
    4. ReturnIfAbrupt(f).
    5. If f < 0 or f > 20, throw a RangeError exception. However, an implementation is permitted to extend the behaviour of toFixed for values of f less than 0 or greater than 20. In this case toFixed would not necessarily throw RangeError for such values.
    6. If x is NaN, return the String "NaN".
    7. Let s be the empty String.
    8. If x < 0, then
        Let s be "-".
        Let x = –x.
    9. If x ≥ 10^21, then
        Let m = ToString(x).
    10.Else x < 1021,
         a. Let n be an integer for which the exact mathematical value of n ÷ 10f – x is as close to zero as possible. If there are two such n, pick the larger n.
         b. If n = 0, let m be the String "0". Otherwise, let m be the String consisting of the digits of the decimal representation of n (in order, with no leading zeroes).
         c. If f ≠ 0, then
            i.  Let k be the number of elements in m.
            ii. If k ≤ f, then
                1. Let z be the String consisting of f+1–k occurrences of the code unit 0x0030.
                2. Let m be the concatenation of Strings z and m.
                3. Let k = f + 1.
            iii. Let a be the first k–f elements of m, and let b be the remaining f elements of m.
            iv.  Let m be the concatenation of the three Strings a, ".", and b.
    11. Return the concatenation of the Strings s and m.

ECMAScript® 2021 中关于此部分的描述与上面略有差异,但不影响结果。

我们将 1.335.toFixed(2) 值代入:

Number.prototype.toFixed ( fractionDigits ) => 1.335.toFixed(2)

1. x = thisNumberValue(1.335),x = 1.335
2. ReturnIfAbrupt(x),返回 x
3. f = ToInteger(fractionDigits),fractionDigits = 2,f = 2
4. ReturnIfAbrupt(f),返回 f
5. 如果 f < 0 或 f > 20,抛出 RangeError 异常。具体的实现允许扩展 toFxied 的行为,以支持 f < 0 或 f > 20 的情况,此时不会抛出异常。由于 f = 2 所以不会异常
6. 如果 x 是 NaN,则就会返回字符串型的 NaN(不满足)
7. s = ''
8. 如果 x < 0:(不满足)
   a. 则s变成"-"
   b. x = -x
9. 如果 x >= 10^21 则令 m = ToString(x)(不满足)
10. 如果 x < 10^21(x = 1.335,满足)
    a. 使 n 为整数以满足 n / 10^f – x 尽可能的接近于 0,如果存在两个这样的 n,选择较大的。
       候选的 n 有 132、133、134, n/10^2 - 1.335 计算结果如下: 
        ' 132/100-1.335 ' —— -0.014999999999999902
        ' 133/100-1.335 ' —— -0.004999999999999893
        ' 134/100-1.335 ' ——  0.0050000000000001155
        当 n 的值为 133 时最接近 0,所以 n = 133
    b. 如果 n = 0,则让 m = "0",否则,让 m 是由 n 的十进制表示的数字组成的字符串(按顺序,没有前导零),所以 m = 133
    c. 如果 f ≠ 0, 则(f = 2,满足)
        i. k 为 m 的数字个数,所以 k = 3,
       ii. 如果 k < f 则(k = 3,f = 2,不满足)
           1. 设z是由代码单元 0x0030的f +1– k次出现组成的字符串。
           2. 让m是字符串z和m的串联。
           3. 令k = f + 1。
       iii. a 为 m 的前 k-f(3-2)个元素,所以 a = 1,b 为 m 的余下元素,所以 b = 33,
        iv. m 为 a、'.' 和 b 的连接,所以 m = 1.33
11. 返回字符串 s('')和 m(1.33)的串联,即 1.33

所以 1.335.toFixed(2) = 1.33

因为在计算机中使用的是二进制进行计算,十进制浮点数与二进制数转换二进制算术运算 中介绍过在使用二进制进行浮点数的表示和运算过程中会存在丢失精度的问题,按照上面规范中要求的 toFixed 的算法,在计算 n / 10^f – x 时就会出现误差,这也就是 toFixed() 会存在错误值的原因。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容