定点数和浮点数
前言
在说定点数和浮点数之前,先回顾一下,整数和小数在计算机怎么表示——二进制形式。
进制计数制
生活中常见的是十进制,那么在计算机中,常见的进制:
- 二进制(B,binary),基本符号0、1
- 八进制(O,Octal),基本符号0、1、2、3、4、5、6、7
- 十进制(D,Decimal),基本符号0、1、2、3、4、5、6、7、8、9
- 十六进制(H,Hexadecimal),基本符号0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F

进制之间的转换
进制之间的转换,可以分为二进制转其他进制,或者反过来,其他进制转二进制。
在转换之前,需要了解原码、反码、补码
原码、反码、补码
原码,就是符号位加上数字的二进制数表示的形式。
🌰 举例子,Java中byte(1一个字节,长度8位)类型:
1
2+7(D)的原码:0000 0111
-7(D)的原码:1000 0111反码,正数情况下,与原码一致;负数情况下,符号位不变,在原码的基础每一位取反。
🌰 举例子,Java中byte(1一个字节,长度8位)类型:
1
2+7(D)的原码:0000 0111,反码:0000 0111
-7(D)的原码:1000 0111,反码:1111 1000补码,正数情况下,与原码一致;负数情况下,在反码基础加1
🌰 举例子,Java中byte(1一个字节,长度8位)类型:
1
2+7(D)的原码:0000 0111,反码:0000 0111,补码:0000 0111
-7(D)的原码:1000 0111,反码:1111 1000,补码:1111 1001
总结,正数情况下,三码相同;负数情况下,反码符号位不变,其余位取反,补码在反码加1。
二进制转其他(R)
- 二进制转十进制
1
1011(B) = 1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 = 11(D)
- 二进制转八进制
整数部分从低位到高位方向每3位改写成八进制数替换,最后不足3位时在高位补0。
小数部分从高位向低位方向每3位改写成八进制数替换,最后不足3位时在低位补0。1
1011(B) = 001 011 = 13(O)
- 二进制转十六进制
整数部分从低位到高位方向每4位改写成十六进制数替换,最后不足4位时在高位补0。
小数部分从高位向低位方向每4位改写成十六进制数替换,最后不足4位时在低位补0。1
10110(B) = 1011 0 = B0(H)
其他(R)转二进制
反过来即可
十进制正数、负数转其他进制(R)
要将整数部分和小数部分分别进行转换,这两部分的转换规则是不同的。
- 整数部分的转换规则:
反复除以基数R,直到余数为0为止,每次除法得到的余数从下到上依次排列即是R进制数最终结果。

所以135(D) = 207(O) = 10000111(B)
- 小数部分的转换规则:
反复乘以基数R,得到的数字小数部分继续与基数R相乘,直到结果的小数部分为0为止,
每次相乘得到的中间结果从高到低依次排列即是最终结果。
1 | 0.6875 * 2 = 1.375 整数部分=1 (高位) |
所以0.6875(D) = 0.1011(B)。
1 | 0.6875 * 8 = 5.5 整数部分=5 (高位) |
所以0.6875(D) = 0.54(O)。
有一种情况就是每次乘积后的小数部分永远得不到0,这种情况下得到的是个近似值。什么是定点数
定点数,就是固定小数点的位置,小数点前后的数字用二进制存储在计算机中的一种方式。
🌰 举例子,Java中byte(1一个字节,长度8位)类型,假设约定整数部分占4位,小数部分占4位。
1 | 2.1(D) = 0010 0001(B) |
总结,定点数逻辑简单,但是有一个弊端,按照以上的约定规则整数最大数为15(D) = 1111(B)
,小数部分最小为0.9375(D) = 0.1111(b)
。
表示范围有限,假设想表示更大的数:
- 扩大字节长度,占2个以上字节。这种方式占用计算机内存
- 扩大整数部分占用位数,小数点右移。比如整数占5位。这种方式会降低小数精度。
相反,想表示更小的数,也会有这样的问题。
什么是浮点数
浮点数,就是小数点的位置不是固定的,小数点前后的数字用二进制存储在计算机中的一种方式。
科学计数法
浮点数是采用科学计数法的方式来表示的。
1 | 1015(D) = 0.1015 * 10^4 (D) |
这样的形式,小数点就是浮动。根据以上的表示格式,总结出:
1 | V = (-1)^S * M * R^E |
其中各个变量的含义如下:
- S:符号位,取值 0 或 1,决定一个数字的符号,0 表示正,1 表示负
- M:尾数,用小数表示,例如前面所看到的 1.015 * 10^3,1.015 就是尾数
- R:基数,表示十进制数 R 就是 10,表示二进制数 R 就是 2
- E:指数,用整数表示,例如前面看到的 10^-1,-1 即是指数

浮点数标准
根据以上的公式,指数和尾数分配规则不同,产生结果也不同。,因此有了浮点数标准。
单精度浮点数float:32 位,符号位 S 占 1 bit,指数 E 占 8 bit,尾数 M 占 23 bit
双精度浮点数double:64 位,符号位 S 占 1 bit,指数 E 占 11 bit,尾数 M 占 52 bit
为了使其表示的数字范围、精度最大化,浮点数标准还对指数和尾数进行了规定:
- 尾数 M 的第一位总是 1(因为 1 <= M < 2),因此这个 1 可以省略不写,它是个隐藏位,
这样单精度 23 位尾数可以表示了 24 位有效数字,双精度 52 位尾数可以表示 53 位有效数字 - 指数 E 是个无符号整数,表示 float 时,一共占 8 bit,所以它的取值范围为 0 ~ 255。
但因为指数可以是负的,所以规定在存入 E 时在它原本的值加上一个中间数 127,这样 E 的取值范围为 -127 ~ 128。
表示 double 时,一共占 11 bit,存入 E 时加上中间数 1023,这样取值范围为 -1023 ~ 1024。
除了规定尾数和指数位,还做了以下规定:
- 指数 E 非全 0 且非全 1:规格化数字,按上面的规则正常计算
- 指数 E 全 0,尾数非 0:非规格化数,尾数隐藏位不再是 1,而是 0(M = 0.xxxxx),这样可以表示 0 和很小的数
- 指数 E 全 1,尾数全 0:正无穷大/负无穷大(正负取决于 S 符号位)
- 指数 E 全 1,尾数非 0:NaN(Not a Number)

标准浮点数的表示
有了统一的浮点数标准,我们再把 25.125 转换为标准的 float 浮点数:
- 整数部分:25(D) = 11001(B)
- 小数部分:0.125(D) = 0.001(B)
用二进制科学计数法表示:
1 | 25.125(D) = 11001.001(B) = 1.1001001 * 2^4(B) |
所以 S = 0,尾数 M = 1.001001 = 001001(去掉1,隐藏位),
指数 E = 4 + 127(中间数) = 135(D) = 10000111(B)。 填充到 32 bit 中,如下:

浮点数精度丢失
🌰 举例子,0.2(D)用浮点数表示,在转二进制时发现乘以2陷入循环,而浮点数的尾数长度有限,
因此不能准确表示0.2,发生精度丢失情况。
1 | 0.2 * 2 = 0.4 -> 0 |
浮点数的范围和精度
单精度浮点数 float 为例。
它能表示的最大二进制数为 +1.1.11111…1 * 2^127(小数点后23个1), 而二进制 1.11111…1 ≈ 2,
所以 float 能表示的最大数为 2^128 = 3.4 * 10^38,
即 float 的表示范围为:-3.4 * 10^38 ~ 3.4 * 10 ^38。
float 能表示的最小二进制数为 0.0000….1(小数点后22个0,1个1),用十进制数表示就是 1/2^23。
用同样的方法可以算出,
double 能表示的最大二进制数为 +1.111…111(小数点后52个1) * 2^1023 ≈ 2^1024 = 1.79 * 10^308,
所以 double 能表示范围为:-1.79 * 10^308 ~ +1.79 * 10^308。
double 的最小精度为:0.0000…1(51个0,1个1),用十进制表示就是 1/2^52。
BigDecimal
从上得知,浮点数存在精度丢失的问题,那么在开发过程中,一般使用BigDecimal
确保精度。
BigDecimal 使用注意事项
- 在使用BigDecimal时,为了防止精度丢失,推荐使用它的
BigDecimal(String val)
构造方法
或者BigDecimal.valueOf(double val)
静态方法来创建对象。 - 使用
divide
方法的时候尽量使用divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
,
其中scale
表示要保留几位小数,roundingMode 代表保留规则,并且RoundingMode参数不要选择UNNECESSARY
,
否则很可能会遇到ArithmeticException
(无法除尽出现无限循环小数的时候)。 - 大小比较使用
compareTo
方法
BigDecimal如何防止精度丢失
add
源码分析
1 | private static BigDecimal add(final long xs, int scale1, final long ys, int scale2) { |

这是重点,逐行分析:
checkScale
方法检查sdiff
从long转int类型是否相等,不想等则检查是否超出Integer.MAX_VALUE
,以及检查ys是否为0,返回sdiff
的int值longMultiplyPowerTen
方法,把ys扩大,乘以10的raise次方- 最终变成long类型的整型数相加
结论
BigDecimal在计算时,实际会把数值扩大10的n次倍,变成一个long型整数进行计算,
整数计算时自然可以实现精度不丢失。同时结合精度scale,实现最终结果的计算。