将double转换为float是否总是返回相同的值?

22

double 强制转换为 float 是否总是产生相同的结果,还是可能存在一些“舍入差异”?

例如,在下面的代码中,x 是否始终等于 y

float x = (float)0.123456789d;

转换回去的值是否总是相同?

当将float转换为double,然后再将其转换回float时,情况如何,例如:(float)(double)someFloat

我最感兴趣的是在C#中的结果是什么,但如果您了解其他语言中的工作原理,请随意分享。


@Moozhe 不是在C#中,那里的十进制后缀是“m”。 - phoog
@Moozhe 错了,d是表示双精度浮点型 (double)。M 表示十进制数值类型(缩写为 "money")。http://msdn.microsoft.com/zh-cn/library/bfft1t3c.aspx - Mr Lister
6个回答

12
结果不应该受语言影响,除非语言偏离 IEEE 规范。 所有的浮点数都可以被准确表示为双精度数,所以从 float 到 double 再到 float 的转换应该得到与开始时相同的值。 类似地,将任何双精度值强制转换为 float 应该始终产生相同的结果,但是,当然,有许多不同的双精度值会截断为相同的 float 值。

7
如果您将一个double类型的值转换为float类型,那么您将会失去精度和数据。将float类型的值向上转型为double类型是扩展转换,如果再次转回去,则不会丢失任何数据... 除非在向下转换回float类型之前对该值进行了某些操作。
浮点数为了范围而牺牲了精度和准确性。单精度浮点数提供32位精度;双精度浮点数提供64位精度。但是它们可以表示远远超出底层精度所示范围的值。
C#中的float和double类型均为IEEE 754浮点数值。
  • float单精度IEEE 754值(32位),由以下内容组成:

    • 1位符号
    • 8位指数
    • 23位尾数/有效数字
  • double双精度IEEE 754值(64位),由以下内容组成:

    • 1位符号
    • 11位指数
    • 52位尾数/有效数字

尾数的有效精度比它的表面大小高1个比特(浮点数魔法)。

一些CLR浮点数资源供您使用:

这篇论文可能是关于浮点算术的危险和陷阱的经典论文。如果您不是ACM的成员,请点击标题上的链接查找文章的公开下载:
  • David Goldberg. 1991. 计算机科学家应该了解的浮点运算知识. ACM Comput. Surv. 23, 1 (March 1991), 5-48. DOI=10.1145/103162.103163 http://doi.acm.org/10.1145/103162.103163

    摘要
    浮点运算被许多人认为是一门神秘的学科。这相当令人惊讶,因为浮点在计算机系统中无处不在:几乎每种语言都有浮点数据类型;从个人电脑到超级计算机都有浮点加速器;大多数编译器都会不时地编译浮点算法;而且几乎每个操作系统都必须响应浮点异常,例如溢出。本文介绍了与计算机系统设计师直接相关的浮点方面的教程。它从浮点表示和舍入误差的背景开始,继续讨论IEEE浮点标准,并以计算机系统构建者如何更好地支持浮点为例结束。


1
在某些情况下,最接近数值数量的float表示可能与将最接近的double表示四舍五入为float所得到的值不同。两个这样的量是12,344,321.4999999991和12,345,678.50000000093。上下整数都可以精确地表示为float,但是最接近它们的double的小数部分恰好为0.5。因为将这样的double值(介于2^23和2^24之间,小数部分恰好为0.5)转换为float会四舍五入到最接近的偶数;编译器在每种情况下最终都会向远离原始数字更近的值四舍五入。
请注意,在实践中,编译器似乎将数字解析为double,然后转换为float,因此即使12344321.4999999991f应该四舍五入为12344321f,它仍会四舍五入为12344322f。同样,12345678.50000000093f应该四舍五入为12345679f,但它却四舍五入为12345678f,因此即使将数字直接指定为float,也无法避免由于转换为double然后float而失去精度的情况。
顺便说一下,值12344321.4999999992f和12345678.50000000094f被正确地四舍五入了。

1
考虑到它们具有不同的精度,即使你从低精度向更高精度转换(我想这实际上是你的疑问),结果也不能总是相同。
浮点运算,特别是类型转换,总是受到截断/舍入和任何其他类型的近似的影响。

3
这并不完全正确。当你试图使用二进制浮点数来表示十进制数时,它们会产生近似值。这个近似值是由于从一种进制到另一种进制的转换而产生的。浮点数和双精度浮点数都是基于2的数据类型,所以更大的数据类型可以完全表示比较小的类型所能表示的任何值。 - phoog
@phoog:我不太理解你的观点:(double)4.123401f在标准的ToString()中等于4.12340116500854这不是同一个数字。无论使用与否,在双精度单元格中的数字都不相同。 - Tigran
@phoog:错了。二进制表示也不一样 - Tigran
1
我撤回之前的说法。我一直在F# interactive中测试二进制值,这与C#讨论并不特别相关。我编写了一个C#控制台程序,将4.123401f转换为double并重新写入二进制表示。float值为 01000000100000111111001011100111,double值为0100000000010000011111100101110011100000000000000000000000000000;这些值完全相等。ToString输出的差异是因为float.ToString()将其输出四舍五入到较少的小数位数,而double.ToString()则不会。 - phoog

0
一个 double 应该能够精确地保存 float 的每个可能值。将 float 强制转换为 double 不应更改其值,并且在此期间如果您没有对 double 进行任何计算,则将其转换回 float 应返回原始值。

0
在C#中,浮点数使用IEEE 754格式存储(http://en.wikipedia.org/wiki/IEEE_754)。该格式由两部分组成:数字和指数。双精度浮点数具有52个数字,而单精度浮点数具有23个数字。基数为2,而不是10。因此,对于您上面的示例(0.123456789),数字将是111010110111100110100010101(123456789的二进制表示)。这是27个数字,在双精度浮点数中可以轻松容纳,但在单精度浮点数中则不行,因此,在往返转换中会丢失精度。
另一方面,如果您的数字是0.123456,则数字将是11110001001000000(17个数字),可以轻松容纳在单精度浮点数或十进制数中,因此在往返转换中不会丢失精度。

0.123456789d的二进制表示实际上是0011111110111111100110101101110100110111001110010110001101011111;尾数为(1).1111100110101101110100110111001110010110001101011111。相应的浮点值为00111101111111001101011011101010(尾数为(1).11111001101011011101010)。由于123456789/1000000000在二进制中无限重复,因此这两个值都不适合双精度甚至单精度,容量太小了。 - phoog

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