问题

一个浮点数到底是怎么被转换为字符串输出?一个浮点数不精确,那么其输出的值是怎么被确定的呢?

回答
咱们来聊聊这浮点数是怎么变成咱们眼睛里看到的数字字符串的。这事儿说起来,比直接给个列表要有趣得多。

你想啊,电脑里储存的浮点数,比如 `3.14159265358979323846`,它在内存里根本不是这么一长串小数摆在那儿的。它得符合一个标准,最常见的就是 IEEE 754 标准。这个标准把它拆成了三部分:

1. 符号位 (Sign Bit): 就一个比特,0 表示正数,1 表示负数。简单直接。

2. 指数位 (Exponent): 这部分决定了小数点应该往哪儿移。你可以理解成科学计数法里的“乘以 10 的多少次方”或者“乘以 2 的多少次方”。它不是直接存一个整数,而是存一个“偏移量”,这样做是为了能表示非常大和非常小的数。

3. 尾数位 (Mantissa/Significand): 这部分才是存储数字的“有效数字”部分。因为浮点数有个“规格化”的过程,就像我们写科学计数法时,会把数字写成 `1.xxxx 10^y` 的形式,这个 `1.xxxx` 就是尾数。在二进制里,它就是 `1.xxxx...`。 IEEE 754 的设计是有个隐含的“1.”在前面的,也就是说,尾数位真正储存的是小数点后面的部分。

所以,当你看到一个浮点数,比如 `3.14`,在电脑里它可能被表示成类似 `(1) 2^1 (1.10010001000011010100101...)` 这样的形式(这只是个概念上的简化)。

关键来了:为什么浮点数不精确?

原因就在于二进制。我们人类习惯用十进制,1/10、1/100 这种都很容易表示。但在二进制里,很多我们看起来很简单的十进制小数,比如 0.1,在二进制里就是个无限循环小数 (`0.0001100110011...`)。电脑的存储空间是有限的,它没办法把无限循环小数存下来,只能存一个近似值。这就像你用有限的纸币去买无限长的绳子,你只能截取一部分。

那么,输出的字符串值又是怎么确定的呢?

这就是“浮点数到字符串转换”或者叫做“打印浮点数”这个过程要干的事情。当你想把电脑里储存的那个近似的浮点数展示出来时,就需要一个算法来决定这个字符串应该是什么样子。这个算法的目标是:

尽可能准确地反映原始的数值。 也就是说,你打印出来的数字,应该跟电脑里那个近似值“最接近”。
在有限的精度下,选择一个看起来最自然的表示。 比如,一个非常小的数,你不会想看到 `0.0000000000000000000000000000000000000000000000000000000000000000123` 这样的,而是希望看到 `1.23e70` 这样的科学计数法。

这个转换过程通常会遵循一些规则,比如:

1. 选择一个合适的精度。 打印多少位小数?这通常由你使用的函数(比如 C 语言的 `printf` 或 Python 的 `str()`)来决定。如果你没指定,它会有一个默认值。
2. 进行“四舍五入”或“截断”。 电脑里存的那个近似值,通过一系列数学运算(主要是乘法和加法,模拟十进制的展开),会得到一个更长的十进制表示。然后,根据你设定的打印精度,决定是把超出精度的部分“舍掉”还是“四舍五入”。
3. 考虑舍入误差。 因为最开始存储的浮点数就不精确,所以这个转换过程本身也可能引入新的舍入误差。最理想的情况是,你打印出来的字符串,当你再把它转换回浮点数时,会得到一个和原始计算机内部表示非常接近的数。
4. 处理特殊情况。 比如无穷大 (`Inf`)、负无穷大 (`Inf`)、非数字 (`NaN`),这些都有特殊的字符串表示。

举个例子(更形象的理解):

想象你在银行存了 100 块钱,但是银行的账本精度只到分。你存的时候,它内部记录可能是 `100.00`。

现在,你取了 1/3 的钱,也就是大约 `33.333333...` 块。银行内部可能只能记录 `33.33` (如果四舍五入) 或者 `33.33` (如果截断)。

当你想打印出来你取了多少钱时,账本上写的是 `33.33`。那么,输出的字符串就是 `"33.33"`。

这里面,`1/3` 这个数本身在十进制里就有循环小数的问题,更何况在二进制内部,它存储的就已经是“近似”了。打印出来的 `33.33` 是根据银行内部的记账精度(两位小数)和一种舍入规则(比如四舍五入)得出的结果。

所以,浮点数转字符串,不是直接把内存里的二进制比特翻译成字符,而是一个根据特定算法,将计算机内部存储的(往往是近似的)数值,按照人类可读的、符合特定格式(如精度、科学计数法)的方式重新计算和表示出来的过程。输出的值,就是这个算法在指定精度下计算出的最“合适”的字符串表现。

网友意见

user avatar

浮点数的二进制表示是精确的,比如 0.3 的最接近二进制表示是 00111110100110011001100110011010。

因此,“打印浮点数”这个问题实际上是:寻找一个最短的十进制数,使得它的最接近二进制表示和给定的二进制相同。

目前这方面最好的算法是 Ryu,可参考这里:github.com/ulfjack/ryu

user avatar

系统存储和输出浮点数时,是没有“精确值“的概念的。它的确会按照IEEE754的方式“趋近”。下文详细讲讲。

这里从Java开始讲解(但其实这是个普遍的问题,不限于Java)。Java中println(一个浮点数)实际使用的是Float.toSring(float f)。其文档如下。我把关于位数的说明用红框标注。

英文有点绕,怎么理解呢?我画了个图说明下这个问题。(留意下下这个图比例上并不精确,大致说明一下概念而已)

图中数轴下面表示精确的数学上的值。上面则是float的表示,包括就是IEEE754的Hex表示,和程序对这个float的输出数值。如果你还不理解IEEE754是啥,你可以大致理解为,一个浮点数表示为:

其中mantissa表示有效数字,exponent是2的幂,sign是表示正负数的符号。对于数字精度,最关键的是mantissa。

每个float的最小精度的变动是其mantissa部分加1或者减1。因为mantissa长度有限(float为23个bit),每个float实际上表示的是数学上一个范围(如上图)在这个范围内,无法再精细的分辨。比如,没法精确的区分0.79999995和0.79999996——他们的IEEE754 Hex表示都是0x3f4ccccc。对mantissa加1或者减1都会跳到隔壁的“范围”里

每个范围都会有一个对应的数值,就是通过上面的公式计算的结果。比如,0x3f4ccccc的值是“1.5999999046325684 * 2^(-1) = 0.7999999523162841796875“。这个值可以唯一的确定一个“范围”。值得注意的是,对于每一个浮点数,可以找到一个最小的位数n,使得这个值round到n后,依然可以和隔壁的范围不冲突。这也就是上面文档中

... but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of typefloat...

要表达的意思。

例如,对于0x3f4ccccc,这个round的数值就是0.79999995。如果再少保留一位,把末尾的5干掉,四舍五入后就是0.8了,这就跟0x3f4ccccd一样,分不开了。

上图中给出了3个float的“round值“。对于0x3f4ccccc,值就是“0.79999995”;下一个是0x3f4ccccd,这个值就是“0.8”,再下一个是0x3f4cccce,值是“0.8000001”。

当你在程序中输入了一个浮点数X,再输出得到Y,就会经历以下过程。1)程序编译器/解释器会按照IEEE754的方式存储那个数字;2)当你输出时,程序就会按照IEEE754计算值的公式算出对应的“值”,并且round到足够能和相邻浮点数分辨的位数,得到一个字符串表示,也就是Y。

比如你写"float a = 0.8f",程序解析为“用float格式存储0.8,保存到变量a的内存地址上“,他的IEEE754 Hex表示是0x3f4ccccd,其值就是0.800000011920928955078125,最小能分辨的值就是0.8。请留意:虽然你代码写的是0.8,输出的也是0.8,但是这俩0.8不是一回事。比如,如果你输入的是0.799999999,得到的IEEE754表示依然是0x3f4ccccd,输出的还是0.8。

这个“可以最小分辨的值的字符串表示”就是Float.toString(float f)返回的结果,也就是我们常规看到的printf(float f)打印到控制台的结果。

顺便说说另外一个例子:

对于0.8f + 0.1f,其IEEE754的Hex表示为“0x3f666667“, 最小可分辨值是"0.90000004"。

对于0.9f,其IEEE754的Hex表示为“0x3f666666“,最小可分辨值刚好为"0.9"。

所以你可以留意到println(0.8f + 0.1f)与println(0.9f)的输出会不一样。

so,那个该死的把浮点数输出的算法到底是什么?这个问题比表面上看起来要困难得多。可以参考这个系列文章:

或者直接从这篇论文开始:

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有