小数的二进制表示
小数计算出错的原因
有些数字(如:0.1)使用二进制本就做不到精确表示,使用近似数用来运算在误差足够小的时候,结果看上去是精确的,但不精确才是事实。
为什么计算机不使用十进制呢?
- 计算机使用的电子元器件只能表示两个状态,通常是低压和高压,对应 0 和 1。
- 使用二进制容易基于这些电子元器件构建硬件设备和进行运算。
- 如果非要使用十进制,则这些硬件就会复杂很多,并且效率低。
浮点数
- 为什么叫做浮点数?
- 这是由于小数的二进制表示中,表示那个小数点的时候,点不是固定的,而是浮动的。
- float 和 double 被称为浮点数据类型,小数运算被称为浮点运算。
- 浮点数的表示
- 几乎所有的硬件和编程语言表示小数的二进制格式都是一样的。这种格式是一个标准,叫做 IEEE 754 标准,它定义了两种格式。
- 32 位(对应 Java float)
- 32 位格式中,1 位表示符号,23 位表示尾数,8 位表示指数。
- 64 位(对应 double)
- 64 位格式中,1 位表示符号,52 位表示尾数,11 位表示指数
- 32 位(对应 Java float)
- 二进制中为表示小数,采用类科学表示法,形如 m×(2^e)。m 称为尾数,e 称为指数。指数可以为正,也可以为负。
- 查看浮点数的具体二进制形式
- Integer.toBinaryString(Float.floatToIntBits(value))
- 几乎所有的硬件和编程语言表示小数的二进制格式都是一样的。这种格式是一个标准,叫做 IEEE 754 标准,它定义了两种格式。
解决方法
在 Java 中精确计算小数,可以使用 BigDecimal,但是效率比较低。
- 精确表示
- BigDecimal 类在内部使用任意精度的十进制数字来表示数值。
- BigDecimal 的运算方法都是基于十进制的。这意味着它可以准确地存储任何大小和精度的小数,只要内存足够。
- BigDecimal 通常使用 char[] 或 int[] 来存储数字的每一位十进制数字,以及一个表示小数点位置的标度 (scale)。
- 例如,BigDecimal 可以将 “0.1” 直接存储为十进制的 “1” 和标度 “1” (表示小数点后一位),将 “123.456” 存储为十进制的 “123456” 和标度 “3”。
- 效率较低
- BigDecimal 的运算是软件实现,没有硬件加速,需要进行对象创建、方法调用,并且使用更复杂的十进制算法。因此,BigDecimal 的效率远低于 float 和 double。