既然浮点数据类型不精确,那么浮点数存在的意义在哪?

2021-03-18 14:20:45 +08:00
 xiaokongwu

既然二进制无法精确的表示小数,那么为什么设计浮点数(float/double)这种不精确的格式,而不直接设计一种基础的,可以精确表示小数的基础数据类型呢?

IEEE 虽然规定了浮点数的格式,但这种浮点数格式仍然是不精确的,为什么不直接规定一种精确的存储格式呢? 比如整数小数分开存储,前 N 位是整数,后 N 位是小数,再来几位存 scale/precision 之类的设计,就像 Java 里的 BigDecimal 一样

8722 次点击
所在节点    Java
83 条回复
Dvel
2021-03-18 15:19:39 +08:00
现在的语言几乎都有 Decimal 类型了,日常的不需要太高精度的小计算用 float 就行了。
rnicrosoft
2021-03-18 15:19:47 +08:00
@xiaokongwu 我懂你意思了,只不过平常用不到而已,有需要的自己弄一个高精度计算的库就行,不一定要做进语言标准里。
geelaw
2021-03-18 15:23:41 +08:00
@xiaokongwu #16 浮点数是指对于数 X*B^Y 直接存储了 X 和 Y 的格式,且两者没有必然的绑定关系,在一定范围内可以独立变化。double 和 float 也都是存储了 X 和 Y 。如果你说的格式里 Y 被 X 决定,则不是浮点数。

BigDecimal 的开销主要不在于它是个 object,而是在于它可以达到任意精度,即使去掉 object 的 overhead 仍然不如 double 高效。另外 BigDecimal 看起来是可以根据需要变化自己的长度的,而 primitive 常常是定长的,因此 Java 的模型下它必须是 object 。

可以参考 CLR 的 System.Decimal,这是一个定长的十进制浮点数,但范围和精度也当然都是固定的。

另外你怎么就知道 BigDecimal 计算没有精度问题呢?根据文档,BigDecimal 是十进制浮点数,它不能精确表达 1/3 。
imn1
2021-03-18 15:37:13 +08:00
浮点数我理解是没有违反数学理论,就是精确到“所需”,换句话无限小数无法精确,有限小数就是精确到“有限”位
然后,就是精确的概念,计算机和人脑定义不同,例如 1/1.0 的结果,人脑定义自然是整数 1,计算机定义是浮点数,“精确”的逻辑等不成立,既然前期定义不同,要靠后期修正,编程语言是其中一种方式
lithiumii
2021-03-18 15:38:42 +08:00
@xiaokongwu 按我理解,如果有基础的精确类型,就只是把这个额外的开销转移到基础里了而已
whileFalse
2021-03-18 15:46:45 +08:00
既然圆周率不精确,那么为什么还要计算圆的周长和体积?
icyalala
2021-03-18 15:50:47 +08:00
你希望的是能精确表达"十进制" 的小数,可惜计算机是 "二进制"的。
而二进制和十进制转换即使到今天也是非常复杂的。
DOLLOR
2021-03-18 16:15:27 +08:00
建议去了解一下数字是怎样在计算机里表示的
http://blog.sina.com.cn/s/blog_6e51df7f0100tbje.html
xiaokongwu
2021-03-18 16:41:31 +08:00
感谢各位的回复。总结一下:float/double 这种基本的浮点数类型,还是有存在的意义的。对于只读不写的场景,基本的浮点类型完全可以;不过我还是认为编程语言可以提供一种基础的“decimal”类型来解决十进制计算的精度问题,而不是各种奇淫巧技 /额外对象 /三方数字库之类的东西
lakehylia
2021-03-18 16:55:53 +08:00
对于无理数来说,任何格式都没有办法存储精确值,只是看你应用的场景,选一个你需要的精度。
Kr98
2021-03-18 17:06:20 +08:00
楼主你要理解精度损失的来源

不是 2 进制还是 10 进制的问题
不是 float 还是 decimal 类型的问题

计算精度损失来自于“有限的存储空间”
lonewolfakela
2021-03-18 17:09:49 +08:00
@xiaokongwu "认为编程语言可以提供一种基础的'decimal'类型"
这个问题之所以不能实现,是有这样一个原因的:编程语言里的真正的“基础类型”,必须是能反映到 CPU 硬件指令上的类型;而 CPU 能做的指令都是有限的,一般来说,是要求基础类型的长度固定的:比如 float 就是 32 字节,double 就是 64 字节。那么 CPU 在执行一个比如“float 加法”的指令的时候,它能知道自己要做的事情实际上就是“读入两个 32 字节数据,送入一个浮点加法单元,这个单元在经过固定的时间长度之后就能得到结果,之后 CPU 就能进行后续计算”。
而对于你说的这种实际存储长度不定的数据,CPU 一来不能静态地确定每次需要读入 /写出的数据长度,二来也不能固定地知道每次例如加法乘法之类的操作需要运行多长时间。因此即使要在 CPU 上进行相关运算,也几乎必然会先被拆分成几个“子任务”进行计算:先要读入你说的那个 scale/precision 值,然后决定接下来读入多少位整数和小数,甚至可能还要进行一次额外计算来考虑数据计算单元计算一次加法要多少个时钟周期,以便安排接下来的指令。这样复杂的一个东西很显然是不适合作为一个“基础类型”存在的。这也是为啥现有大部分对这种 decimal 类型的实现都是用额外对象 /第三方库之类的东西。
lonewolfakela
2021-03-18 17:14:20 +08:00
@xiaokongwu “对于只读不写的场景,基本的浮点类型完全可以;”
即使对于需要写、甚至一些需要进行复杂运算的场景,现有的不精确的浮点类型依然是足够的。
一个典型的场景就是 3D 游戏里的场景渲染。计算机要把一个模型的三维坐标转换成屏幕上的二维坐标,这个步骤基本上都是用 float 类型来算的(甚至 double 都用不上)。道理也很简单,你打游戏的时候是不会在意面前的那只即将被你打爆的外星小怪兽在屏幕上的位置是不是距离“它应该出现的物理上绝对准确的那个坐标”偏离了 0.0001 个像素的。相反,游戏里最重要的是计算速度,只有算得够快游戏才能不卡。所以在这个场景下选择 float 就是最佳的选择。
ipwx
2021-03-18 17:15:27 +08:00
15L @xiaokongwu
"float/double 这种浮点数字类型存在的意义在哪呢,除了占用低,完全不能拿来做运算"
----

那么问题来了,1/3 这个无法被十进制和二进制精确表示的小数,楼主打算怎么处理呢? BigDecimal 也不能解决存储 1/3 精确值的问题。

好,假设楼主为了精确用两个整数存储分母和分子,用分数(比如 Python 内置类型 fraction )。那么 sqrt, exp, log, sin, cos, ... 这些函数出来的大多是无限不循环小数(无理数),楼主打算怎么精确表示呢?

事实上各种运算中,“不精确”才是常态,精确才是少部分情况。需要精确的时候,楼主可以用分数,或者用整数模拟定点小数,或者用 BigDecimal 这种十进制小数(处理货币)啊?
xiaokongwu
2021-03-18 17:22:30 +08:00
@lonewolfakela 感谢大佬回复,解释的真好。

“这样复杂的一个东西很显然是不适合作为一个“基础类型”存在的。这也是为啥现有大部分对这种 decimal 类型的实现都是用额外对象 /第三方库之类的东西。”

这段精辟,基础类型就干基础类型的事,复杂的工作交给上层解决
xiaokongwu
2021-03-18 17:24:57 +08:00
再次感谢各位大佬的回复,第一次在 V2EX 发帖有这么多人解答,太意外了
ayase252
2021-03-18 17:32:51 +08:00
你永远无法用有限的集合映射到无限集合。你要强行映射必然会有精度。IEEE 754 代表了业界 99%都能通用的一种解决方案。如果不符合自己需求,那么需要自行实现
lonewolfakela
2021-03-18 17:34:47 +08:00
顺便补充一下,并不是所有的编程语言的基础类型里都没有楼主所说的这种任意精度小数的。
例如在 mathematica 中,这类具有任意指定的精度的近似实数就是四种基本数字类型中的一种( Integer,Rational,Real,Complex 这四者中的 Real )
但是它这么设计是有原因的:
1 、Mathematica 不是一个类似 C 语言那样很底层、很接近 CPU 的一个语言,而是一个抽象程度较高的语言。用它编写的东西在电脑上执行前本来就会经过变化相当大的翻译,所以实际上它的“基础类型”也不是那么基础,相关操作在实际运行的时候也都会被翻译成更低级的子任务;
2 、Mathematica 是面向数学家的语言。相比工程师的“只要在误差范围内就算成功”的思维方式,数学家还是更喜欢能有精确一些的东西的。

所以归根到底还是出于语言的不同设计思路以及面向的不同使用人群的考量罢了。
nightwitch
2021-03-18 17:47:34 +08:00
在 IEEE754 之前,每个制造芯片的厂商都要考虑浮点数怎么表示的问题,你的这些思考都是当年就被考虑烂了的内容。

浮点数据的不精确本质原因在于要用有限的位数去表达无限长度的量,无论你用什么精妙的结构去模拟这个过程都一定会产生舍入误差。
gBurnX
2021-03-18 17:53:07 +08:00
1.目前计算机体系中的浮点的设计原则,是因为以前计算机处理浮点功能少、性能低。
现代的计算机,对于现代的大型游戏与各种运用程序来说,浮点处理性能也不行。


2.可以设计一套用纯粹的数学公式作为标记的计数系统,比如圆周率,存π比存 3.233366666 要更精确。这种公式系统的优点是精准,但缺点是性能很低。你可以自己实现一套简单的,然后拿去和目前的图形引擎比比图形渲染性能。


3.类似的问题还有 Datetime 长度问题,以及 IPv4\v6 的长度问题。虽然现在这些数据结构被加长,但长度仍然是个固定值。如果人类在很久很久以后还存在,那么以后当 64 位的 Datetime 被用完,肯定会再次遇到前年虫问题;当 IPv6 被分完,下次说不定会再出个 IPv8 甚至 IPv16...

这些值其实完全可以一劳永逸地设计成动态长度,PC 也就算了,你看看路由器、交换机以及更低成本的物联网小硬件,那可怜的主频,那可悲的带宽,还要节能,还要考虑成本.....就算她们想要运算复杂的动态长度,但臣妾们也做不到啊。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/762814

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX