如何将浮点数转换为双精度浮点数(两者都以IEEE-754表示),而不会失去精度?

7
我是一位有用的助手,可以为您翻译文本。

比如说,我有一个使用IEEE-754单精度格式编码的数字:

"0100 0001 1011 1110 1100 1100 1100 1100"  (approximately 23.85 in decimal)

上面的二进制数以字符串形式存储。

问题是,我该如何将这个字符串转换为IEEE-754双精度表示(类似于下面的示例,但值不同),而不会失去精度?

"0100 0000 0011 0111 1101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010"

这是一个使用IEEE-754双精度编码的数字。

我尝试使用以下算法将第一个字符串转换回十进制数,但它会失去精度。

num in decimal = (sign) * (1 + frac * 2^(-23)) * 2^(exp - 127)

我正在使用Windows平台上的Qt C++框架。
编辑:我必须道歉,可能我没有清楚地表达问题。我的意思是我不知道真正的值23.85,我只有第一个字符串,我想将其转换为双精度表示而不会失去精度。

1
@tenfour 我认为这是因为他将其存储为字符串。 - Caesar
5
第二个二进制字符串并不代表与第一个相同的数字,而是使用“double”精度。您试图解决什么问题? - Daniel Fischer
1
Daniel Fisher是正确的;这两个数字绝对不相同(第一个是23.84999847412109375,第二个是23.85000000000000142108547152020037174224853515625)。 - Stephen Canon
5个回答

4

好的:保留符号位,重写指数(减去旧的偏差值,加上新的偏差值),并在右侧用零填充尾数...

(正如@Mark所说,你必须单独处理一些特殊情况,也就是偏置指数为零或最大值时。)


谢谢!实际上,问题很简单。我只是被困在我的思维中了。 - Richard
你应该意识到,这样做并不能得到你在原帖中所要求的结果,对吧? - Analog File
@KerrekSB:一般情况下,这不会产生原始的十进制值!二进制小数和十进制小数之间的版本依赖于从十进制到二进制再返回十进制的转换之间的良好交互。如果您使用零填充,双精度浮点数将无法正确地转换回来。 - Dietmar Kühl
@DietmarKühl:原始的十进制值也不是浮点数的值。23.85是477/20,不能准确地表示为二进制浮点数。事实上,最接近477/20的双精度浮点数并不是由我的公式给出的那个。相反,我的公式展示了如何获得与输入浮点数相同值的双精度浮点数。 - Kerrek SB
1
@KerrekSB:如果你做正确的事情,你可以将十进制小数转换为二进制浮点数(“Bellerophon”),然后再将其转换回十进制小数(“Dragon4”),在非常合理的限制下(基本上,十进制数字的数量受到log_2(10)向下取整的限制),你可以得到原始值。如果你只是用零填充,格式化一个double不会产生与你来自的float相同的值! - Dietmar Kühl
这对于有限的正常非零数可以很好地工作;但是对于零、次正规、无穷大和NaN,您可能需要更加努力。特别要注意的是,即使您不关心次正规、NaN和无穷大,此方法也会将+/-0.0转换为非零值:因此,即使您不关心次正规、NaN和无穷大,为零添加一个特殊情况也可能是值得的。 - Mark Dickinson

2
首先,对于以二进制方式识别输入的做法点赞。
其次,该数字并不代表23.85,而是略小于该值。如果将其最后一个二进制位从0翻转为1,则该数字仍无法准确表示23.85,但可以在double中近似表示这些差异无法充分捕获在浮点数中,但可以在双精度浮点数中近似捕获。
第三,你认为失去的是准确性而不是精度。数字的精度总是通过从单精度转换为双精度来增加,而准确性永远不能通过转换来改善(你的不准确数字仍然不准确,但附加的精度使它更加明显)。
我建议在显示(或记录)数字之前将其转换为浮点数、四舍五入或添加非常小的值,因为视觉效果才是你真正失去的,而增加精度只是为了更好的显示。
要抵制在转换后立即进行舍入并在后续计算中使用舍入值的诱惑-这在循环中尤其危险。虽然这可能看起来可以在调试器中纠正问题,但积累的额外不准确性可能会更扭曲最终结果。

2
IEEE-754(以及浮点数)无法完全精确地表示周期性二进制小数。即使它们实际上是具有相对较小的整数分子和分母的有理数。一些语言提供了可能做到这一点的有理类型(它们也支持无界精度整数的语言)。
因此,您发布的这两个数字不是同一个数字。
实际上,它们是:
10111.11011001100110011000000000000000000000000000000000000000 ... 10111.11011001100110011001100110011001100110011001101000000000 ...
其中“...”表示无限序列的“0”。
Stephen Canon在上面的评论中为您提供了相应的十进制值(我没有检查它们,但我没有理由怀疑他得到了正确的结果)。
因此,您想要进行的转换无法完成,因为单精度数字没有您需要的信息(您无法知道该数字是否实际上是周期性的,还是仅看起来像是因为恰好重复)。

抱歉,我不明白。在C编程中说"float"转换为"double"时没有精度损失,这似乎是个悖论…… - Richard
没有精度损失。如果将浮点数转换为双精度,您将获得相同的值。问题在于您不想获得相同的值,而是想获得另一个值。以十进制为例,具有6位尾数的十进制浮点数可以存储123.4545(作为1.234545×10^2)。如果增加到8位,则可以获得123.4545(作为1.23454500×10^2),而不会失去精度。但是您想要的是另一个数字123.454545。 - Analog File

1

最简单的方法可能是将字符串转换为实际的浮点数,将其转换为双精度浮点数,然后再将其转换回字符串。


-1
二进制浮点数通常无法精确表示十进制小数值。从十进制小数值转换为二进制浮点数(参见William D.Clinger的《如何准确读取浮点数》中的“Bellerophon”)以及从二进制浮点数转换回十进制值(参见Guy L.Steele Jr.和Jon L.White的《如何准确打印浮点数》中的“Dragon4”)会产生预期结果,因为一个将十进制数转换为最接近的可表示的二进制浮点数,另一个控制误差以确定它来自哪个十进制值(这两个算法在David Gay的dtoa.c中得到改进并更加实用)。这些算法是从类型T中存储的浮点数值中恢复std::numeric_limits<T>::digits10个十进制位数(除了可能的尾随零)的基础。

不幸的是,将float扩展为double会破坏其值:尝试格式化新数字在许多情况下不会产生原始小数,因为用零填充的float与最接近的doubleBellerophon创建的数字不同,因此Dragon4期望的结果也不同。然而,基本上有两种方法可以很好地解决这个问题:

  1. 正如某些人建议的那样,将float转换为字符串,然后将该字符串转换为double。虽然这不是特别高效,但可以证明会产生正确的结果(当然,假设实现了不完全复杂的算法)。
  2. 假设您的值在合理的范围内,您可以将其乘以10的幂,使得最低有效十进制位为非零,将此数字转换为整数,将该整数转换为double,最后将得到的double除以原始的10的幂。我没有证明这会产生正确的数字,但对于我感兴趣并希望在float中准确存储的值的范围,这种方法是有效的。
避免这个问题的一个合理方法是首先使用C++中描述的十进制浮点值,如Decimal TR所述。不幸的是,这些还不是标准的一部分,但我已经向C++标准化委员会提交了提案以改变这种情况。

问题是将一个二进制浮点数转换为另一个二进制浮点数,但这个答案提供了关于表示十进制值和转换为十进制的无关陈述。此外,它声称将float扩展为double会对值造成破坏是荒谬的:值不会改变。(如果某些软件将double转换为十进制,然后以与float不同的方式显示它,则这是该软件的错误,与原始问题无关。) - Eric Postpischil
在理解浮点数的过程中,一个常见的谬误是认为float表示形式为M*2^N的确切数量,其中M和N是某个范围内的整数。虽然float确实具有该形式的精确“名义”值,但我不会说float代表该确切数量。数字2000000.13f的名义值恰好为2000000.125,但是该特定的float将用于从2000000.0625到2000000.1875的所有值。因为从... - supercat
2000000.1200到2000000.1299将在可能的范围内,小数点后第二位以后的数字没有有用信息; 因此报告的值四舍五入为2000000.13。虽然没有确切名义值为2000000.125(与浮点数匹配)的“double”,但没有一个意味着“在2000000.0625和2000000.1875之间”。具有相同名义值的“double”具有不同的语义含义。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接