众所周知,浮点数操作是有精度损失的。这里对精度损失的原因进行跟踪。
废话少说,先上代码:
import java.math.BigDecimal;
public class Main {
static void parseFloatDetails(float v) {
int i = Float.floatToRawIntBits(v);
int sign = ((i & 0x80000000) >> 31); // 浮点数的正负符号
int exponent = ((i & 0x7f800000) >> 23); // 浮点数指数部分
exponent -= 127;
int significand = (i & 0x007fffff); // 浮点数尾数部分
String significand_str = Integer.toBinaryString(significand);
while (significand_str.length() < 23)
significand_str = "0" + significand_str;
// value = (1 + significand / 2^23) / 2^exponent
BigDecimal part1 = BigDecimal.valueOf(1.0).add(
BigDecimal.valueOf(significand).divide(
BigDecimal.valueOf(2.0).pow(23)));
BigDecimal part2 = BigDecimal.valueOf(2.0).pow(Math.abs(exponent));
part1 = (exponent >= 0 ? part1.multiply(part2) : part1.divide(part2));
System.out.println("1." + significand_str + "B * 2^" + exponent + " = " + part1.floatValue()
+ " (" + v + ")");
}
public static void main(String[] args) throws Throwable {
parseFloatDetails(1.3f);
parseFloatDetails(1.0f);
parseFloatDetails(1.3f - 1.0f);
parseFloatDetails(0.3f);
}
}
/* stdout:
1.01001100110011001100110B * 2^0 = 1.3 (1.3)
1.00000000000000000000000B * 2^0 = 1.0 (1.0)
1.00110011001100110011000B * 2^-2 = 0.29999995 (0.29999995)
1.00110011001100110011010B * 2^-2 = 0.3 (0.3)
*/
这里利用了浮点数 IEEE二进制浮点数算术标准(IEEE 754 )的相关参数。
精度损失的原因:
1) 部分十进制浮点数用二进制存储存在精度损失。
例如 十进制0.3D, 二进制无法精确表示,对应二进制 0.01001100110011001....B (无限循环二进制小数)
2) 所有的二进制浮点数都可无损的转换为十进制浮点数,只要不限制小数的尾数。
因为, 2^-1, 2^-2, 2^-3 .... 都可转换为十进制有限小数
3) 浮点数的四则运算都存在着精度损失。
哪怕是数量级相近的加减法(例如 1.3 - 1.0)。因为若 A和B运算生成C,由于二进制的关系,ABC的指数部分必定不会全部相等,则计算过程中必定存在浮点的指数对齐过程,同时伴随着尾数的位移和补零,而被舍弃的无限循环部分没有参与运算,则存在精度损失。(参见上面程序示例)
(如果要跟踪其他运算符精度损失详情或者双精度的浮点数计算,只需小小改动上述程序即可)
4) 浮点数进行等号比较的问题:
1.3 - 1.0 == 0.3 的比较方式必然导致失败。