V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
litianyou
V2EX  ›  程序员

浮点运算结果不准确算不算是 bug?任何人都不想得到不准确的结果吧

  •  
  •   litianyou · 2020-01-11 22:29:43 +08:00 · 8528 次点击
    这是一个创建于 1528 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近开发时遇到一个问题就是关于“浮点运算结果不准确”的问题,很多开发语言中都有这个问题,进而衍生出了一些类库去专门解决浮点运算偏差。

    我很好奇,开发人员应该没有人希望得到一个不稳定、不准确的结果,那么保留这个“浮点运算不准确”的特性有什么特殊意义吗?如果没有特殊意义,那么“浮点运算结果不准确”是不是 Bug 呢?如果是 Bug 的话为什么至今仍然有很多开发语言有这个问题呢?

    请各位有经验的大佬说说原因?

    81 条回复    2020-04-07 18:27:33 +08:00
    Sanko
        1
    Sanko  
       2020-01-11 22:34:27 +08:00 via Android   ❤️ 19
    计算机组成原理
    renmu
        2
    renmu  
       2020-01-11 22:34:53 +08:00 via Android   ❤️ 2
    二进制没办法完整表示浮点数
    misaka19000
        3
    misaka19000  
       2020-01-11 22:36:26 +08:00   ❤️ 1
    。。。

    大一的计算机组成原理里面就介绍过这个了吧
    misaka19000
        4
    misaka19000  
       2020-01-11 22:37:04 +08:00   ❤️ 2
    本质上因为浮点数是无限的,你无法用有限的的东西表示无限的东西
    jmc891205
        5
    jmc891205  
       2020-01-11 22:37:30 +08:00 via iPhone   ❤️ 1
    有限位二进制数怎么精确表示无限数量的浮点数
    litianyou
        6
    litianyou  
    OP
       2020-01-11 22:41:18 +08:00
    类库能够解决,开发语言应该也能想办法解决吧?
    存储时以浮点型存储,运算时按照那些类库的方法做运算,是不是可以解决这个问题?
    或者因为效率的问题,所以选择有需求的话自己用类库做精确运算?是这样吗?
    @Sanko
    @renmu
    @misaka19000
    @jmc891205
    bagel
        7
    bagel  
       2020-01-11 22:43:23 +08:00
    @misaka19000 #4 你说的本质是错的。如果人进化成有 8 个手指,日常数字就会使用 8 进制,然后 2 进制浮点数就能精确表示。很多人看了点教科书,以为自己懂了,其实压根没懂。
    malusama
        8
    malusama  
       2020-01-11 22:55:42 +08:00   ❤️ 1
    这是硬件使用二进制决定的啊.
    开发语言的作用是编译语句成为机器码操作操作硬件.
    agagega
        9
    agagega  
       2020-01-11 22:58:00 +08:00   ❤️ 9
    浮点数本来就不是为了表示「精确」的东西的。所以早期的一些 CPU 甚至都不能硬件支持浮点指令。而且在一些指令集的文档里面,我们常说的「整数」实际上叫 Fix-point numbers,和浮点数相对的。所以如果你需要一个确定精度的运算,请使用整数搭配固定的偏移去模拟。如果有更高精度的需求,请使用相关的库。

    至于你说为什么开发语言不试图去解决这个问题,因为:(1) 需要保持行为的一致性,浮点数并没有那么简单,IEEE-754 标准以及各种扩展实现、浮点环境、异常、rounding mode 都是坑。除了 Excel 这种用途的,正常的通用编程语言不应该自作聪明地去掩盖这个问题;(2) 浮点运算是有硬件指令支持的,自己去模拟一个所谓「准确」的运算,在不严格要求精度的时候,性能问题怎么办?
    Sketch
        10
    Sketch  
       2020-01-11 23:04:44 +08:00   ❤️ 2
    @bagel 恕我才疏学浅,如果人进化成有 8 个手指,那么我们该如何用二进制浮点数精确表示 sqrt(2)呢
    litianyou
        11
    litianyou  
    OP
       2020-01-11 23:04:54 +08:00
    @agagega 感谢大佬讲解。
    mikeguan
        12
    mikeguan  
       2020-01-11 23:08:15 +08:00 via Android
    把数字转换成字符串进行计算能不能避免精度的问题
    XiaoxiaoPu
        13
    XiaoxiaoPu  
       2020-01-11 23:10:54 +08:00 via iPhone
    @Sketch 哈哈,光速打脸
    litianyou
        14
    litianyou  
    OP
       2020-01-11 23:15:18 +08:00
    @mikeguan 你的意思是转整数吧?解决浮点运算偏差的类库一般是这样做的。就像 9 楼说的那样,整数搭配偏移去运算。
    XiaoxiaoPu
        15
    XiaoxiaoPu  
       2020-01-11 23:16:07 +08:00 via iPhone
    @mikeguan 可以实现更高精度,但还是不能彻底解决问题。如果用分数法,可以实现精确的有理数四则运算,但是对于无理数还是没有办法。
    ljpCN
        16
    ljpCN  
       2020-01-11 23:23:14 +08:00 via Android   ❤️ 2
    @Sketch 本贴讨论的主要问题应该是,用浮点数表示十进制有限小数会出现误差。你如果用无理数举例,那么本身十进制小数也做不到精确表示。本贴的问题应该这样举例:0.1 这个十进制小数不能表示为一个有限位数的二进制小数。而如果 0.1 是一个八进制数,那么对应的二进制数就是 0.001 ,也就不存在十进制有限转二进制有限小数时需要的舍入了。
    mxT52CRuqR6o5
        17
    mxT52CRuqR6o5  
       2020-01-11 23:25:39 +08:00 via Android
    @Sketch 浮点运算不准指的是,电脑算十进制的 0.1+0.2≠0.3。电脑算八进制的 0.1+0.2 就不会出现这种问题
    akira
        18
    akira  
       2020-01-11 23:42:26 +08:00   ❤️ 1
    性能和高精度的取舍
    CEBBCAT
        19
    CEBBCAT  
       2020-01-11 23:42:53 +08:00 via Android   ❤️ 1
    学校放假了,员工数着日子等过年了,终于开始群魔乱舞了吗?
    lance6716
        20
    lance6716  
       2020-01-12 00:22:24 +08:00 via Android
    @mxT52CRuqR6o5 宁觉得 sqrt 就不是“运算”了吗
    mxT52CRuqR6o5
        21
    mxT52CRuqR6o5  
       2020-01-12 00:25:31 +08:00 via Android
    @lance6716 宁觉得楼主所说的浮点运算不准指的是这个吗?楼主说的解决浮点运算偏差的类库是可以解决 sqrt(2)没法准确表达的?
    Sketch
        22
    Sketch  
       2020-01-12 00:37:47 +08:00
    是的,我清楚本贴的原意是指类似这些有理数的运算,我只是想反驳“二进制浮点数的精确表达”这一点。但同时,即使在二进制下,我们依然无法通过有限位数精确表示 1/3,1b/11b 的值,0.01010101...b,而只能获取一个近似值,即浮点运算结果的不准确
    @ljpCN
    @mxT52CRuqR6o5
    Vegetable
        23
    Vegetable  
       2020-01-12 01:06:32 +08:00
    服了,还有拿无理数出来讨论的,我怕一会儿虚数什么的都出来了
    lance6716
        24
    lance6716  
       2020-01-12 01:11:31 +08:00 via Android
    @mxT52CRuqR6o5
    > 宁觉得楼主所说的浮点运算不准指的是这个吗?
    是的。加法,乘法,整数次幂,有理数次幂,都是“运算”
    > 楼主说的解决浮点运算偏差的类库是可以解决 sqrt(2)没法准确表达的
    没读懂。宁给我断断句?
    Raymon111111
        25
    Raymon111111  
       2020-01-12 01:27:31 +08:00
    ?

    上课好好听课
    mxT52CRuqR6o5
        26
    mxT52CRuqR6o5  
       2020-01-12 01:42:10 +08:00 via Android
    @lance6716
    @litianyou
    楼主能说一下标题所说的浮点运算结果不准确指的是 0.1+0.2≠0.3 还是指上面这位兄弟说的 sqrt(2)算不准
    feather12315
        27
    feather12315  
       2020-01-12 01:47:00 +08:00 via Android
    这个问题用数学集合相关的概念解释啊。
    不是进制的问题,核心在与:同#5 的解释 -- 数是无限的,但是用于表示的东西是有限的。自然数扩充为整数,整数加上有限小数是有理数,有理数加上无理数是实数,实数之外还有虚数。

    一个数轴,自然数都表示不全,指望着表示所有的实数?更何况还有虚数。

    数学 /物理中有个概念:误差。在使用小数进行计算的时候,会要求误差控制在多少个小数点后面。高中物理都有,分别对比保留到保留小数点后面 M / N 位的误差。求小数点 N 位的时候,不都是在计算过程中使用 N+1,或者 N+位计算,甚至分数么?

    使用小数计算,人为都做不到 100%精确,怎么到计算机这就是个 bug 了?
    mxT52CRuqR6o5
        28
    mxT52CRuqR6o5  
       2020-01-12 02:05:44 +08:00 via Android
    @feather12315 0.1+0.2≠0.3
    mxT52CRuqR6o5
        29
    mxT52CRuqR6o5  
       2020-01-12 02:09:30 +08:00 via Android
    @lance6716 sqrt(2)的结果你也没法用一个十进制数准确表达,这又不是计算机产生的问题,想要准确表达 sqrt(2)在 X 进制表数法下只能保留根号
    lance6716
        30
    lance6716  
       2020-01-12 02:14:35 +08:00 via Android
    @mxT52CRuqR6o5 等一下,有的语言 0.1+0.2≠0.3,sqrt(2)^2≠2。有的语言(库) 0.1+0.2=0.3,sqrt(2)^2=2。人是“后者那种语言”,因为人是按照“符号”计算的,可以避免好多“精度丢失”,一般的程序语言在原理上都属于前者。
    完了 Jojo 看多了真喜欢用引号
    lance6716
        31
    lance6716  
       2020-01-12 02:17:58 +08:00 via Android
    @mxT52CRuqR6o5 sqrt(2)的结果确实没法用一个十进制数准确表达,但是我们都知道 sqrt(2)^2=2。这应该跟 0.1+0.2=0.3 是同一个问题吧
    feather12315
        32
    feather12315  
       2020-01-12 02:18:21 +08:00 via Android
    @mxT52CRuqR6o5 #29 等不等于,这个得看误差的范围。在数学的意义上,0.9999… (无限循环)等价于 1.0,可以被证明。
    msg7086
        33
    msg7086  
       2020-01-12 02:19:14 +08:00 via Android
    因为浮点数记录的是数字立即量本身,而立即量只能精确存储有限不循环小数(且精度受限)。

    十进制本身也不能在有限精度内做循环小数运算。
    比如三分之一,取 3 位小数是 0.333 ,乘上 3 以后是 0.999 ,一样会产生 0.001 的误差。

    要精确计算循环小数需要用比例类型,存储 1 和 3 而不是 0.333 。
    msg7086
        34
    msg7086  
       2020-01-12 02:31:30 +08:00
    再换一种说法,二进制中浮点数是无法存储 0.1 和 0.2 的。你看到的 0.1 和 0.2,他们其实是:

    0.1000000000000000055511151231257827021181583404541015625

    0.200000000000000011102230246251565404236316680908203125

    他们相加以后就会变成:
    0.3000000000000000444089209850062616169452667236328125
    (这里由于精度损失所以加出了 444 而不是 166。)

    而真正的 0.3 应该是:
    0.299999999999999988897769753748434595763683319091796875

    前者比后者大了整整:
    0.000000000000000055511151231257827021181583404541015625

    因此造成了计算误差。
    502badgateway
        35
    502badgateway  
       2020-01-12 02:34:56 +08:00 via Android
    0.5,0.25,0.125,0.0625……还有他们的和是能准确表示的啊……有的就不能,这不就是二进制的限制么?现代计算机的基础就是门电路,等量子计算机出来了就不存在这个问题了(雾
    msg7086
        36
    msg7086  
       2020-01-12 02:42:12 +08:00
    至于精确运算,一般就交给大整数或者大实数类库来做。
    一般用浮点而不用高精度运算主要还是为了效率,毕竟大多数人并不在意小数点后 10 位的误差。
    大数运算相比硬件浮点,即使是最快的类库也要比 double 慢一倍,所以还是让程序员主动选用为主。
    tianshilei1992
        37
    tianshilei1992  
       2020-01-12 03:40:35 +08:00
    有一个本质是,整数只要有 1,就可以表示任何数字,因为 1 是整数的最小不可分单元。而小数却不存在这个东西,你可以任意指定一个很小的数。因此小数就有了精度这个东西了,然后一个很自然的 trade off 就出现了:性能 vs 精度。这么多年 IEEE 一直没有推出更高精度的浮点数,反而 FP16 却被硬件更多的支持,这就是一个*大家需要的*精度 vs 运算速度的 trade off。甚至是,现在各大硬件厂商都在专门做 int8 的指令,一个原因就是,大家发现,ML/DL 很多时候 int8 也就够了…
    itskingname
        38
    itskingname  
       2020-01-12 08:09:16 +08:00 via iPhone   ❤️ 4
    楼上各位认真解释的同学,你们知道鸽子为什么这么大的梗吗?不要给他们解释了。他们不想听真正正确的答案。
    churchmice
        39
    churchmice  
       2020-01-12 09:48:11 +08:00 via Android
    @litianyou 好好看看计算机组成就不会问这么多问题了,也不用浪费时间在发帖扯蛋上面了

    看书十分钟,论坛吹水一整天
    conn4575
        40
    conn4575  
       2020-01-12 09:50:52 +08:00 via Android
    要不你来开发一个可以准确计算浮点数且性能和现在的浮点数计算差不所的系统?其中包括重新设计二进制、CPU、所有的 IO 等等
    xwkkk
        41
    xwkkk  
       2020-01-12 09:59:45 +08:00
    CSAPP 第一节
    MonoLogueChi
        42
    MonoLogueChi  
       2020-01-12 10:06:03 +08:00 via Android
    不算 bug,因为单精度本身就是这么设计的,而不是因为意外得到的错误。单精度是为了增加计算效率,有些场合并不需要十分精确是结果。比如游戏开发中,极少使用双精度,大部分场景都用单精度甚至半精度,因为单精度足够了,运算效率比双精度高,你能感觉到 1.00001 和 1.000000000001 秒的差别吗?
    shintendo
        43
    shintendo  
       2020-01-12 10:09:08 +08:00   ❤️ 1
    暴露 V2 水平的一贴……
    xuanbg
        44
    xuanbg  
       2020-01-12 10:34:07 +08:00
    浮点数运算精度的问题,只需要去了解一下:
    1、什么是浮点数,浮点数是如何表示和存储的
    2、浮点运算是如何进行的

    只要弄明白上面两个问题,就知道为啥需要浮点运算,以及为什么浮点运算会有精度问题。
    bitbegin
        45
    bitbegin  
       2020-01-12 10:36:18 +08:00
    实际上是有固定精度的 float 实现,它可以在相应范围内实现无误差计算。网址这里: http://www.dec64.com/

    可以想想 money 这样的场景,如果使用 float 是有误差的,对用户来说不可接受,所以都用 integer 和一个 10 为底的指数来表示,dec64 可以得到类似的效果。


    在 dec64 下面,上面说的 0.1 0.2 等等 都是可以无误差表示的,只是 dec64 是有范围限制,差不多到 10^143,而 double 可以到 10^308。


    IEEE 754 标准 已经做到硬件里面去了,它的计算效率就非常的高,像 dec64 这种只能用软件实现(有提供 asm 实现),效率有差别。
    ipwx
        46
    ipwx  
       2020-01-12 10:36:57 +08:00 via Android
    楼主你的需求就是各个语言里面的 decimal 库而已。一般都是标准库
    ylrshui
        47
    ylrshui  
       2020-01-12 10:40:46 +08:00 via iPhone
    sqrt(2)^2=2 是属于符号运算的范畴了。数值运算相比符号运算有误差不是很正常的吗,只看需要的精度即可,浮点数可以满足大多数情况下的精度。
    Reficul
        48
    Reficul  
       2020-01-12 10:47:05 +08:00
    类库最多做到有理数没有进度问题,无理数不可能没有精度问题。 比如 PI
    lrxiao
        49
    lrxiao  
       2020-01-12 11:02:40 +08:00
    不是 bug
    但确实有人用 fixed-precision floating point number
    li492135501
        50
    li492135501  
       2020-01-12 11:10:31 +08:00
    strictfp 了解一下
    jdhao
        51
    jdhao  
       2020-01-12 11:24:05 +08:00
    铁憨憨。。。
    ihipop
        52
    ihipop  
       2020-01-12 12:41:46 +08:00 via Android
    是 feature,如果不懂这个才是 bug
    Samuii
        53
    Samuii  
       2020-01-12 13:05:54 +08:00 via Android
    问这个问题的是在卖萌吗
    zhw2590582
        54
    zhw2590582  
       2020-01-12 13:13:20 +08:00
    网上有好多这方面的解释
    aliipay
        55
    aliipay  
       2020-01-12 14:16:43 +08:00   ❤️ 1
    楼上一大把离题万里的,你们真的是认真的吗? 楼主问的是为什么用类库实现而不是语言本身
    silentstorm
        56
    silentstorm  
       2020-01-12 14:22:47 +08:00 via Android
    只要采用十进制运算就不会有这样的误差了,重担交给题主了
    secondwtq
        57
    secondwtq  
       2020-01-12 14:23:32 +08:00   ❤️ 2
    "浮点运算结果不准确"恰恰是预期行为,因为浮点数的定义就决定了它必然”不准确“,浮点数本身就是一个 approximation。

    这和 primitive 的整数有有符号和无符号之分、范围限制、溢出问题之类某种程度上是同一个性质。同样我们可以问:primitive 整数有溢出是不是 bug ?
    不同的在于,primitive 整数溢出可以是 bug——一些编程语言包含了边界检查的语义,在出现溢出时会抛出异常。但是浮点运算从定义上就是不准确的,因此不算做 bug。
    之所以说它们有共同之处,是因为它们都是在计算机上实现计算过程时做出的妥协。

    比如说,Java 有 Primitive Type 和非 Primitive Type 之分,Primitive Type 成为了 Java 的”一切皆对象“原则上的一个漏洞( web.archive.org/web/20081012113943/http://www.eecs.harvard.edu/~greg/cs256sp2005/lec15.txt )。可见 Primitive Type 造成的问题不仅仅是“运算不精确”一个。楼主的问题”为什么至今仍然有很多开发语言有这个问题呢“首先需要解决的是“既然 Primitive Type 如此麻烦,为什么还要保留 Primitive Type 呢?”并且不只 Java,几乎所有编程语言都具有 Primitive Type,这是为什么呢?

    理论上,使用非常精简的规则,即可表达出所有计算需要的东西,包括数字( https://en.wikipedia.org/wiki/Church_encoding ),因此,Primitive Type 在理论上是不必要的。但是在实际应用的编程语言中,很少有大量应用 Church Encoding 的,相反,Primitive Type 被广泛使用。
    因为“In theory, theory and practice are the same. In practice, they are not.”

    如果把整个计算机看做一个系统的话,编程语言是开发者和计算机之间的接口,ISA 是软件和硬件之间的接口,这些接口都是越简洁越好。图灵机的基本规则也非常简单,如果只是实现一个计算机的话,几条指令完全够用了。
    上世纪著名的 PDP 系列有很多是硬件不支持乘法 /除法操作的。Intel 在 8086 之前也不支持,而一直到 486 之前,浮点运算还要靠 Coprocessor。一直到现在的 RISCV,乘除法、浮点还是 Extension,核心指令集只有不到 50 条指令。这个规模并不比 UNIX 最早用的早期 PDP-11 要小多少。
    同样的原则可以推出,GPU 所做的工作 CPU 也可以做,GPU 是完全没必要的东西。老黄赚的钱全是炒概念的智商税,AMD 也没必要养着 RTG。包括挖矿什么的也可以通通使用 CPU 搞定。
    但是另一方面,RISC-V 也有各种扩展(很多还没做完),现在的 x86 算上各种扩展已经有了一千多条指令。ARM 的核心指令集规模和其他 RISC 类似,但是随便一个 SIMD 扩展甩出来就是几十上百条指令。而现在不仅 CPU 和 GPU 很火,还加入了乱七八糟的 FPGA、TPU 之类的东西,老黄还多此一举的把 RT core 做进了 GPU 里面。这似乎与我们所追求的简洁构成了某种矛盾,最重要的是,老黄又赚了我们一波智商税。

    我在 V 站说明过“编程语言的设计可以影响给到编译器的程序信息的量,进而影响优化编译器的优化效果”( https://v2ex.com/t/632869#r_8401400 )以及“高级语言抽象好,低级语言上限高”( https://v2ex.com/t/594287#r_7803885 )的原理。同样的原则也适用在硬件上——硬件在执行计算时需要“我要执行什么计算”的信息,而 Church Encoding 之类的通用表示方法之所以没法用,就是因为它太通用了导致硬件得到的信息太少,执行效率太低——一个 C++ 程序可以被编译为机器码,但是给你一坨 C++ 编译出来的机器码(经过较多优化,无调试符号),不能反编译出原始的 C++ 程序,甚至就算再把原始 C++ 程序给你,把 C++ 代码和机器代码的位置对应起来在没有调试符号的情况下都是个难题,大量的高层程序信息在转换为具体的、底层的机器表示的过程中逐渐不可逆地丢失了。现代 CPU 会利用各种手段以利用更多的程序信息,达到更高的执行效率,但是当程序信息本身就不足时,硬件厂商也无能为力,所以现在硬件厂商宁愿教育开发者多写 “Modern Code” (虽然最后开发者还是更喜欢 Electron )来最大化硬件使用率,提高执行效率(这里的极端便是上个十年的 VLIW 架构——抛弃 CPU 部分的 hack 来简化硬件,寄希望于软件(包括编译器)能给出更多的信息)。另一方面,硬件厂商需要给出用来表示高效代码执行所需的接口,这就是各种乱七八糟的指令集扩展和非通用硬件。
    硬件本身则通过 Chip Specialization 的方法,来最大化这些信息的利用。什么是 Specialization ?比如说我们知道整数 a * 8 等价于 a << 3,那么编译器如果有“a 是整数”和“表达式 a * 8”这样的信息,便可以把 a * 8 specialize 为 a << 3。Specialization 要求获得足够的信息,如果编译器不知道 a 的类型,或者遇到“a * b”这样的表达式( b 的值无法推导),就没有办法做 Specialization。
    半导体中的 Chip Specialization 则是指对特定已知的计算,直接使用芯片硬件电路实现,而不是用通用的方法(先实现一个图灵完全的指令集,弄一个 CPU,再写软件实现算法)。这样做可以用更少的功耗,对特定计算实现更高的性能——因为算法直接在硬件实现,并且会用经过优化的方法实现。用软件实现和用优化的硬件实现的区别,就像用 Python 实现 FFT 算法性能不如直接调用 scipy 库一样——Python 直接实现的算法,在运行时除了你自己,计算机是不知道它在做 FFT 的,这个信息在源码之后就被丢失了。scipy 库则可以利用“我现在正在做的是 FFT”这项信息给出最优的实现,前提是你通过“调库”的方式,把这个信息告诉计算机。
    GPU 是对图形运算的 Specialization,GPGPU 则是对 SIMT 模型的 Specialization,RT core 是对光线追踪算法的 Specialization,现在手机厂商争相加入的 AI 芯片,则是对 AI 算法的 Specialization,苹果为新 Mac Pro 推出了 Afterburner 加速卡,貌似是用 FPGA 做的,可以看做是对 ProRes 格式的 Specialization。
    当然,越是做 Chip Specialization,就越会发现 Chip Specialization 的能力是有极限的,这就是现在半导体所讲的 The Accelerator Wall ( https://parallel.princeton.edu/papers/wall-hpca19.pdf )——芯片厂商在把常见算法都用硬件实现一遍之后就又没事可做了,现在看上去大家都在搞 Chip Specialization,只是因为之前都在搞通用处理器,没有来得及充分利用 Chip Specialization 的潜力而已,等到这波“红利”吃完了,还是会回到通过爬制程工艺,堆核扩大芯片规模来提升性能和能耗比的老路。

    Chip Specialization 不仅体现在 AI、挖矿、光追等“高大上”领域,不同位数的整数运算、乘除法运算、浮点运算同样也属于 Chip Specialization,只不过这些早就普及了。也正是因为这些东西普及率高,工业上的通用编程语言才会设计 Primitive Type,作用正是允许程序员将优化需要的信息 encode 在程序中,从而方便编译器或硬件的 Specialization (一般做成 Primitive Type 的,在整个系统栈中的某个或多个位置都会有 Specialization,比如上面提到某些处理器没有提供硬件乘法指令,这时编译器会调用一个优化过的库函数来做乘法)。
    需要注意的是,Primitive Type 和底层 Specialization 的对应关系,并不能动摇 Primitive Type 本身更像个 hack 的性质。Primitive Type 实际在程序语言中形成了某种边界模糊的 DSL,而将 Specialization 抽象为 DSL 的做法在最近越来越 explicit,比如 CUDA 则是程序员为 NVIDIA GPGPU 这一 Specialization 提供计算信息的工具,同样的现象出现在 AI 领域。
    所以 C 语言标准里会针对各种 Primitive Type 做出“至少 32 位”之类奇怪的限制,因为这些 Primitive Type 直接对应硬件或软件的 Specialization 或某个可以用来做 Specialization 的标准。

    无限范围 /精度的整数和实数在理论上是不能使用有限空间存储的,并且实现会比固定范围更复杂,而大多数情况下,其带来的好处无法 justify 其成本。最后形成的妥协便是:使用固定位数、有限精度的整数和浮点数来进行大多数的计算。在编程语言中做 Primitive Type,在编译器和库中针对这些类型做优化,在硬件中针对这些类型的运算做 Specialization。
    “任何人都不想得到不准确的结果吧”同样的话可以这么说“任何人都不想内存空间受限制吧”“任何人都不想网速有个最大值吧”“任何人都不想一次航班要好几个小时吧”“任何人都不想钱能花完吧”。

    浮点数只是系统给你提供的一个选择,当固定位数的整数 /浮点数无法满足你的需求时,你可以选择使用其他手段,就像在编程语言中定义新的函数、类型一样。比如使用符号计算,把你的公式本身(而不是公式运算出的值)存储起来,计算机来做化简,什么数都可以表示。如果楼主够厉害,够有钱,可以使用 Chip Specialization 的方式把这套系统用硬件实现,并做成编程语言的 Primitive Type (或一套 DSL )。就不会有这种问题了。

    真正的 bug 出在楼主的认识里。“浮点数”从定义上就是有理数的一个子集而不是实数,也不是有理数。各种 Primitive Integer Type 一般也对应的是整数的一个子集而不是整数。楼主将“浮点数”默认为“小数”或“实数”导致出现了这样的疑问。但是有没有想过,如果“浮点数”等于“实数”的话,为什么要叫“浮点数”这个奇怪的名字而不是“实数”呢。

    当然有些编程语言不负责任地定义了一个名字叫 “real” 类型,却用浮点数实现。real 这个名字上包含所有实数,但是只能包含有理数的一个子集。同理有些语言有名叫“int”或“integer”的类型,但是只能包含整数的一个子集。这种挂羊头卖狗肉的行为已经超越了 bug 的范畴,我个人是支持批判的。但是如果名为 “float”“single”“double”,用浮点数实现,只能表示部分有理数,这是预期行为,不是 bug。
    loveour
        58
    loveour  
       2020-01-12 14:55:20 +08:00
    很多解释可能对但是没说到点子上。为什么语言不改,不如问,什么情况下一门编程语言会改变代码行为?想要更精确有库可以用,改得更精确了万一有些依赖不精确的代码出问题了怎么办呢?更精确消耗性能更多了导致程序变慢了怎么办呢?一个有原因有预期的事情,编程语言为什么要改呢?
    Stevenv
        59
    Stevenv  
       2020-01-12 15:15:12 +08:00 via iPhone
    深入理解计算机系统第二章浮点数表示
    longbye0
        60
    longbye0  
       2020-01-12 17:27:55 +08:00
    八进制那位真是可怕。

    #7 「很多人看了点教科书,以为自己懂了,其实压根没懂。」
    ZRS
        61
    ZRS  
       2020-01-12 17:38:42 +08:00
    看了半天没看明白,到底在讨论浮点数的误差问题,还是不同进制的小数表示差异。。。
    realpg
        62
    realpg  
       2020-01-12 17:44:01 +08:00   ❤️ 1
    @Sketch #10
    你这个举例属于抓住了人家的文字漏洞……
    无理数别说计算机,连笔和纸都没法精确表达

    我觉得你应该能理解层主要表达啥,就不多说了
    panda1001
        63
    panda1001  
       2020-01-12 18:00:18 +08:00 via Android
    有一种专门研究的边缘学科叫数值分析,个人不严谨解释就是在误差下如何求解预期精度的值

    数值分析(英语:numerical analysis ),是指在数学分析(区别于离散数学)问题中,对使用数值近似(相对于一般化的符号运算)算法的研究。
    https://zh.m.wikipedia.org/wiki/%E6%95%B0%E5%80%BC%E5%88%86%E6%9E%90#%E8%AA%A4%E5%B7%AE%E7%9A%84%E7%94%A2%E7%94%9F%E5%8F%8A%E5%82%B3%E6%92%AD
    GrayXu
        64
    GrayXu  
       2020-01-12 19:04:46 +08:00
    没学过 major 课? float 本身就是逼近值,也才有了单精度双精度的说法,要是都按类库来实现,时空开销怎么弥补?
    sylxjtu
        65
    sylxjtu  
       2020-01-12 19:10:33 +08:00 via Android
    浮点运算加减乘除和开方都是准确舍入的,已经是最准确的解决方案了
    murmur
        66
    murmur  
       2020-01-12 19:11:29 +08:00
    楼主说的没错,所以有各种符号计算的软件,可以直接带根号带分数出结果
    ik2h
        67
    ik2h  
       2020-01-12 19:49:21 +08:00
    Sun 公司有一个叫《数值计算指南》的手册,英语好的可以看"What Every Computer Scientist Should Know About Floating-Point Arithmetic"---David Goldberg
    yhxx
        68
    yhxx  
       2020-01-12 20:17:00 +08:00
    楼主的意思应该是为什么语言层面没有把这个处理掉吧
    zhouzm
        69
    zhouzm  
       2020-01-12 20:22:48 +08:00
    gongzhang
        70
    gongzhang  
       2020-01-12 20:31:05 +08:00 via iPhone
    一楼就是正确答案,二楼立马歪了。三楼又说了正确答案,四楼又歪了
    cmdOptionKana
        71
    cmdOptionKana  
       2020-01-12 21:46:40 +08:00   ❤️ 1
    1. 编程属于专业领域,编程语言属于底层工具。

    2. 标题里说 “任何人都不想得到不准确的结果”,这里的任何人应该是指任何非专业人员,即普通用户。

    3. 专业程序员会想得到不准确的结果吗,答案是:会!
    - 专业程序员会根据具体的需求,在不需要非常精确的情况下,优先考虑运行效率。

    4. 现在普遍使用的电脑( CPU ),其底层基本都是二进制计算的,因此在计算十进制的时候会有些“小问题”。注意,这是电脑的物理结构决定的,是电脑的本质特征之一。

    5. 编程语言可以掩盖这个特征,但是,也可以选择暴露这个特征。编程语言作为一种专业领域的底层专业工具,一般来说,暴露这个特征是更好的选择。

    6. 任何专业领域的底层专业工具都是这样的,都会有一些普通用户看起来奇怪、用起来容易出错的地方,这是专业工具的特点。
    good1uck
        72
    good1uck  
       2020-01-12 22:27:49 +08:00   ❤️ 2
    1、楼主的原问题是:浮点数存在运算精度的问题,而编程语言本身没有解决这些问题,但是类库做到了。为什么编程语言不直接去解决这种问题而是要借助类库?

    2、原贴的意义在于讨论“编程语言的问题”,而不是数集问题,更不是进制问题。

    3、这个帖子下混战的太多了。然而都巧妙避开了楼主最关心的问题。A 指出,无理数无法正确表示。B 攻击 A,说这根本不是无理数的问题,而是二进制数没办法表示所有十进制小数的问题,如果你祖先只有 8 个手指那就可以表示了。C 站起来给 B 一巴掌,你放屁,你用八进制,去表示一个无理数 sqrt(2)给我看看? D 临空飞踹 C,怒吼道,本来是在讨论运算精度的问题,你非要精确表示一个数? E 听不惯了,揪着 D 的耳朵咬着牙皮笑肉不笑地说:你再给我说一遍,我开 2 的方怎么就不是运算了?? F,G,H,J..........................

    4、所有楼里,回答比较贴切的有
    #9 @agagega 
    #46 @ipwx
    #49 @lrxiao 
    #55 @aliipay
    请大家直接点赞就行。当然了,我也没看仔细,也许有些同学说的也对。
    但是有些同学呐,不要一上来就解决提出问的人,也不要连题目都看不懂就开始说这是“feature”。咱,不会的问题,就不要强答,不好吗?

    5、我之前发过一篇帖子,题目是“如何解决手里有把锤子,看什么都是钉子的问题”,其实恰恰就是担心自己会出现这种生搬硬套自己熟悉的理论,去解决毫不相干的问题的这种行为。因为,真的不显得比人更聪明啊,反倒是十分勉强。
    jeffh
        73
    jeffh  
       2020-01-12 22:27:50 +08:00
    看看计算机组成原理的书就知道了,要用二进制科学计数法浮点的方式表示小数,那么表示出来的浮点数只是数轴上离散的点,越靠近正无穷的方向,点越稀疏。

    另外并不是所有的浮点数都不能精确表示,像 0.1、0.25 是可以精确表示的。不能表示的点就无法画在数轴上。
    ma836323493
        74
    ma836323493  
       2020-01-13 09:34:55 +08:00 via Android
    去看下计算原理,就明白是不是 bug 可
    Sasasu
        75
    Sasasu  
       2020-01-13 10:01:28 +08:00
    产品经理:那是你的实现,我不管。我只知道 0.1 + 0.2 等于 0.3,不等于 0.3 就增加了使用难度,性能也不能下降
    dswyzx
        76
    dswyzx  
       2020-01-13 10:36:41 +08:00
    @Sasasu 用 excel 啊,要什么自行车
    cmdOptionKana
        77
    cmdOptionKana  
       2020-01-13 11:55:10 +08:00
    @Sasasu 面向普通用户的 APP 可以这样要求,而且也很应该这样要求,事实上都是这样要求的。

    但是如果是一种 “编程语言” 的作者或委员会,则一般不会要求把编程语言设计成那样。
    GeruzoniAnsasu
        78
    GeruzoniAnsasu  
       2020-01-13 13:18:59 +08:00 via Android   ❤️ 2
    > 那么保留这个“浮点运算不准确”的特性有什么特殊意义吗?如果没有特殊意义,那么“浮点运算结果不准确”是不是 Bug 呢?如果是 Bug 的话为什么至今仍然有很多开发语言有这个问题呢?


    1. 是的,有意义,意义在于与硬件行为保持一致以便程序编写者能更好地利用上硬件,并优化代码逻辑。

    2. 不是 bug,而是数学-工程实现 非完全一致带来的必然代价。有很多数学模型工程上都无法精确实现,只能近似。比如几乎所有的编程语言都是“图灵完全”的,但没有任何一种语言能写出图灵机,因为图灵机模型描述的无限长纸带是不可实现的。浮点数的问题也是“近似实现”带来的。

    3. 为了追求数学上的精确,还有非常多的办法,也有专门的用数学语言编程的编程语言。但它们的目标是去解决数学问题,由于硬件限制,这种语言一般完全达不到工业语言的性能水平。所以,事实上有解决了浮点精度 /符号计算问题的语言,但它们不适合写工业代码;也有充分暴露硬件实现以便编码者优化逻辑的工业用编程语言,但它们的计算有精度限制,与数学运算不完全等价。

    取舍问题
    xiangyuecn
        79
    xiangyuecn  
       2020-01-13 15:33:03 +08:00   ❤️ 1
    woodyfairy
        80
    woodyfairy  
       2020-01-15 17:01:03 +08:00
    @xiangyuecn 专业,这才是标准答案👍
    确实有些脚本语言已经开始通过语义进行优化处理了。以前偶现这种问题还不知道为何,看了这个网址才明白不同写法竟然也会有差别。
    p.s.需要显示小数的时候习惯使用 fix 两位一般都会回避掉这种问题吧
    mxT52CRuqR6o5
        81
    mxT52CRuqR6o5  
       2020-04-07 18:27:33 +08:00 via Android
    @good1uck 就是进制问题,详见 79 楼,没几个说到点子上的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3952 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 113ms · UTC 10:24 · PVG 18:24 · LAX 03:24 · JFK 06:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.