作为单元测试的一部分,我需要测试一些边界条件。一种方法接受
System.Double
参数。
有没有办法获得next-smallest双精度值? (即将尾数减少 1 个单位值)?
我考虑过使用
Double.Epsilon
但这是不可靠的,因为它只是从零开始的最小增量,因此不适用于较大的值(即 9999999999 - Double.Epsilon == 9999999999
)。
那么需要什么算法或代码来实现:
NextSmallest(Double d) < d
...永远是真的。
如果您的数量有限,您可以在
BitConverter
类中使用一些方便的方法:
long bits = BitConverter.DoubleToInt64Bits(value);
if (value > 0)
return BitConverter.Int64BitsToDouble(bits - 1);
else if (value < 0)
return BitConverter.Int64BitsToDouble(bits + 1);
else
return -double.Epsilon;
IEEE-754 格式的设计使得组成指数和尾数的位一起形成一个与浮点数具有相同顺序的整数。因此,要获得最大的较小数,如果该值为正数,则可以将该数减一;如果该值为负数,则可以加一。
其有效的关键原因是尾数的前导位没有被存储。如果尾数全为零,那么您的数字就是 2 的幂。如果从指数/尾数组合中减去 1,您将得到所有 1,并且必须借用指数位。换句话说:你必须递减指数,这正是我们想要的。
Math.BitIncrement
/Math.BitDecrement
。不再需要手动进行位操作
返回大于指定值的最小值。
返回小于指定值的最大值。
Double.BitIncrement
/Double.BitDecrement
以及 IFloatingPointIeee754<TSelf>
中的通用版本:BitDecrement/BitIncrement
有关双精度浮点的维基百科页面在这里:http://en.wikipedia.org/wiki/Double_ precision_floating-point_format
为了好玩,我编写了一些代码来分解
double
格式的二进制表示,递减尾数并重新组合所得的双精度值。由于尾数中的隐式位,我们必须检查它并相应地修改指数,并且它可能会在接近极限时失败。
这是代码:
public static double PrevDouble(double src)
{
// check for special values:
if (double.IsInfinity(src) || double.IsNaN(src))
return src;
if (src == 0)
return -double.MinValue;
// get bytes from double
byte[] srcbytes = System.BitConverter.GetBytes(src);
// extract components
byte sign = (byte)(srcbytes[7] & 0x80);
ulong exp = ((((ulong)srcbytes[7]) & 0x7F) << 4) + (((ulong)srcbytes[6] >> 4) & 0x0F);
ulong mant = ((ulong)1 << 52) | (((ulong)srcbytes[6] & 0x0F) << 48) | (((ulong)srcbytes[5]) << 40) | (((ulong)srcbytes[4]) << 32) | (((ulong)srcbytes[3]) << 24) | (((ulong)srcbytes[2]) << 16) | (((ulong)srcbytes[1]) << 8) | ((ulong)srcbytes[0]);
// decrement mantissa
--mant;
// check if implied bit has been removed and shift if so
if ((mant & ((ulong)1 << 52)) == 0)
{
mant <<= 1;
exp--;
}
// build byte representation of modified value
byte[] bytes = new byte[8];
bytes[7] = (byte)((ulong)sign | ((exp >> 4) & 0x7F));
bytes[6] = (byte)((((ulong)exp & 0x0F) << 4) | ((mant >> 48) & 0x0F));
bytes[5] = (byte)((mant >> 40) & 0xFF);
bytes[4] = (byte)((mant >> 32) & 0xFF);
bytes[3] = (byte)((mant >> 24) & 0xFF);
bytes[2] = (byte)((mant >> 16) & 0xFF);
bytes[1] = (byte)((mant >> 8) & 0xFF);
bytes[0] = (byte)(mant & 0xFF);
// convert back to double and return
double res = System.BitConverter.ToDouble(bytes, 0);
return res;
}
所有这些都会给你一个与初始值不同的值,因为尾数最低位的变化......理论上:)
这是一个测试:
public static Main(string[] args)
{
double test = 1.0/3;
double prev = PrevDouble(test);
Console.WriteLine("{0:r}, {1:r}, {2:r}", test, prev, test - prev);
}
在我的电脑上给出以下结果:
0.33333333333333331, 0.33333333333333326, 5.5511151231257827E-17
差异是存在的,但可能低于舍入阈值。不过,表达式
test == prev
的计算结果为 false,并且存在实际差异,如上所示:)