查找具有相同内部表示的float / double的最小值/最大值

刷新浮点 (也是PDF ),IEEE-754并参与有关转换为字符串时浮点舍入的讨论 ,让我修补一下:如何获得给定浮点数的二进制表示的最大值和最小值是平等的。

免责声明 :对于本次讨论,我喜欢坚持IEEE-754所描述的32位和64位浮点。 我对扩展浮点(80位)或四边形(128位IEEE-754-2008)或任何其他标准(IEEE-854)不感兴趣。

背景 :计算机在二进制表示中表示不好。 在C#中,float在内部将此表示为3DCCCCCD (C#使用舍入到最近),将double表示为3FB999999999999A 。 相同的位模式用于十进制0.100000005 (浮点)和0.1000000000000000124 (双0.1000000000000000124 ),但不用于0.1000000000000000144 (双0.1000000000000000144 )。

为方便起见,以下C#代码给出了这些内部表示:

 string GetHex(float f) { return BitConverter.ToUInt32(BitConverter.GetBytes(f), 0).ToString("X"); } string GetHex(double d) { return BitConverter.ToUInt64(BitConverter.GetBytes(d), 0).ToString("X"); } // float Console.WriteLine(GetHex(0.1F)); // double Console.WriteLine(GetHex(0.1)); 

0.1的情况下,没有用相同的位模式表示的低十进制数,任何0.99...99将产生不同的位表示(即,浮点数为0.999999937 3F7FFFFF内部产生3F7FFFFF )。

我的问题很简单:如何找到内部存储在同一二进制表示中的给定float(或double)的最低和最高十进制值。

为什么 :(我知道你会问)在转换为字符串时从.NET中舍入时发现错误,当它从字符串转换时,找到内部精确值并更好地理解我自己的舍入错误。

我的猜测是这样的:取尾数,移除其余部分,得到其精确值,得到一个(尾数位)更高,并计算平均值:低于该值的任何东西将产生相同的位模式。 我的主要问题是:如何将小数部分作为整数(位操纵它不是我最强的资产)。 Jon Skeet的DoubleConverter课程可能会有所帮助。

解决问题的一种方法是找到你的浮点数中的ULP大小,或者在L ast P花边中的U nit。 简化一点,这是给定浮点数与下一个更大数之间的距离。 再次,简化一点,给定一个可表示的浮点值x,其值在(x – 1/2 ulp)和(x + 1/2 ulp)之间的任何十进制字符串将在转换为浮动时舍入为x点值。

诀窍是(x +/- 1/2 ulp)不是可表示的浮点数,因此实际计算其值需要使用更宽的浮点类型(如果有的话)或任意宽度的大小数或类似的类型来进行计算。

你如何找到ulp的大小? 一个相对简单的方法大致是你建议的,这里写的是C-ish伪代码,因为我不知道C#:

 float absX = absoluteValue(x); uint32_t bitPattern = getRepresentationOfFloat(absx); bitPattern++; float nextFloatNumber = getFloatFromRepresentation(bitPattern); float ulpOfX = (nextFloatNumber - absX); 

这是有效的,因为在x的位模式中添加一个完全对应于向x的值添加一个ulp。 减法中不会发生浮点舍入,因为所涉及的值非常接近(特别是有一个ieee-754浮点运算定理,如果两个数x和y满足y / 2 <= x <= 2y,然后精确计算x – y )。 这里唯一的警告是:

  1. 如果x恰好是最大的有限浮点数,这将无效(它将返回inf ,这显然是错误的)。
  2. 如果您的平台没有正确支持逐渐下溢(比如一个嵌入式设备以齐纳模式运行),这对于非常小的x值都不起作用。

听起来你不太可能处于这两种情况中,所以这应该适合你的目的。

既然你知道x的ulp是什么,你就可以找到舍入到x的值的区间。 你可以精确地计算浮点数的ulp(x)/ 2,因为2的浮点除法是准确的(再次,禁止下溢)。 那么你只需要计算x +/- ulp(x)/ 2的值,适合更大的浮点类型(如果你对float感兴趣,则可以使用float )或者使用Big Decimal类型,并且你有你的间隔。

我通过这个解释做了一些简化的假设。 如果你需要这个确实拼写出来,请留下评论,当我有机会时,我会扩展有点模糊的部分。


另请注意您的问题中的以下陈述:

在0.1的情况下,没有用相同的位模式表示的低十进制数

是不正确的。 你恰巧正在查看错误的值(0.999999 …而不是0.099999 …… – 一个简单的拼写错误)。

Python 3.1刚刚实现了这样的东西:请参阅更改日志(向下滚动一下) , 错误报告 。