这个问题在这里已有答案:
我想将一些人工精度损失引入两个数字进行比较,以平滑次要的舍入误差,这样我就不必在涉及 x
和 y
的每个比较中使用 Math.abs(x - y) < eps
惯用语 .
从本质上讲,我想要的东西行为类似于将 double
向下转换为 float
,然后将其向上转换回 double
,除了我还要保留非常大且非常小的指数,我想要对有效数量进行一些控制比特保留 .
给定以下函数,该函数生成64位IEEE 754数字的有效数的二进制表示:
public static String significand(double d) {
int SIGN_WIDTH = 1;
int EXP_WIDTH = 11;
int SIGNIFICAND_WIDTH = 53;
String s = String.format("%64s", Long.toBinaryString(Double.doubleToRawLongBits(d))).replace(' ', '0');
return s.substring(0 + SIGN_WIDTH, 0 + SIGN_WIDTH + EXP_WIDTH);
}
我想要一个函数 reducePrecision(double x, int bits)
,它会降低 double
的有效数字的精度,这样:
significand(reducePrecision(x, bits)).substring(bits).equals(String.format("%0" + (52 - bits) + "d", 0))
换句话说,在 reducePrecision(x, bits)
的有效位中 bits
-最高有效位之后的每个位应该为0,而 reducePrecision(x, bits)
的有效位中的 bits
-最高有效位应该合理地近似 x
的有效位中的 bits
-最重要的位 .
1 回答
假设
x
是您希望降低精度的数字,bits
是您希望保留的有效位数 .当
bits
足够大且x
的数量级足够接近0时,x * (1L << (bits - Math.getExponent(x)))
将缩放x
,以便需要移除的位将出现在小数分量中(在小数点之后),而位将是retain将出现在整数组件中(小数点之前) . 然后,您可以对此进行舍入以删除小数分量,然后将舍入的数字除以(1L << (bits - Math.getExponent(x)))
,以恢复x
的数量级,即:但是,
(1L << exponent)
将在Math.getExponent(x) > bits || Math.getExponent(x) < bits - 62
时崩溃 . 解决方案是使用Math.pow(2, exponent)
(或来自this answer的快速pow2(exponent)
实现)来计算2的分数或非常大的幂,即:但是,
Math.pow(2, exponent)
将在exponent
接近-1074或1023时崩溃 . 解决方案是使用Math.scalb(x, exponent)
,以便不必明确计算2的幂,即:但是,
Math.round(y)
返回long
,因此它不会保留Infinity
,NaN
,以及Math.abs(x) > Long.MAX_VALUE / Math.pow(2, exponent)
的情况 . 此外,Math.round(y)
总是将关系舍入到正无穷大(例如Math.round(0.5) == 1 && Math.round(1.5) == 2
) . 解决方案是使用Math.rint(y)
来接收double
并保留无偏的IEEE 754舍入到最接近的规则(例如Math.rint(0.5) == 0.0 && Math.rint(1.5) == 2.0
),即:最后,这是一个确认我们期望的单元测试:
它的输出: