近期在学习switch,遇到了switch不支持float、double、long的问题,也遇到了double对于小数的运算并不准确(甚至累计求和也不准确),查阅书籍,参考大神的讲解,整理出了这篇博客。
为什么不支持long
其实long数据表示范围太大了,int(-2147483648 ~ 2147483647)21亿已经足够表示。
为什么不支持float和double?
结论:其实浮点数计算机中通过二进制浮点运算标准IEEE754表示,而很多十进制小数在转换为二进制表示对时候是无限循环小数,导致了精确度的损失。所以浮点数在计算机中存储的只是近似值,这样让switch设定需要精确匹配不符。所以不支持。
第 14 章.块、语句和模式 --- Chapter 14. Blocks, Statements, and Patterns
举例:
double a = 0.1 + 0.2; System.out.println(a); // 输出 0.30000000000000004,而非 0.3
假如说switch可以用double
double value = 0.1 + 0.2; switch (value) { case 0.3: // 实际值为 0.30000000000000004,无法匹配 System.out.println("Matched 0.3"); break; default: System.out.println("Not matched"); }
这样输出结果肯定是Not matched
,value的 实际值并不严格等于0.3。
为什么会导致精确度的损失?谁让精确度损失的?
原因:1.十进制 0.1无法表示为有限的 2 的负幂次方的和,2.计算机存储中,使用了IEE754制定的标准让浮点数的尾数位数被截断,只能存储近似值。
-
为什么要截断?因为存储空间也是有限的,无法达成无限的存储,所以制定IEEE754标准来限制。
原因一、有些小数无法表示为有限的2都负次幂方的和。
我们知道整数采取除以 2 的模式来转换为二进制,小数部分则是通过乘以2的方法转换,现在我们复习一下小数的二进制转换方法:
-
将小数部分乘以2。
-
记录结果的整数部分(0或1)作为二进制位。
-
取结果的小数部分作为新的数值重复步骤1和2,直到结果为0或达到所需的精度。
-
将步骤2中记录的所有二进制位按顺序排列,即为小数部分的二进制表示。
我们用 0.6 来演示下:
乘以 2 | 整数部分 | 小数部分 |
---|---|---|
0.6 * 2 | 1 | 0.2 |
0.2 * 2 | 0 | 0.4 |
0.4 * 2 | 0 | 0.8 |
0.8 * 2 | 1 | 0.6 |
0.6 * 2 | 1 | 0.2 |
...... |
进入循环,所以 0.6 的二进制表示为 1001100110...
。按照同样的方式,可以得到
-
0.3 的二进制表示为
0100110011...
。
那么我们加上整数位,6.6 和 3.3 的二进制表示如下:
-
6.6:
110.1001100110...
-
3.3:
11.0100110011...
可以看到很多数在十进制转为二进制的时候,都无法表示为有限的二进制数。
原因二、尾数位数的截断
十进制转二进制,转化不到有限的小数,那么计算机中是如何存储这些数的呢?直接截断吗?
首先我们要了解浮点数和定点数
浮点数和定点数
在计算机中,小数点不用专门的器件表示,而是按约定的方式标出,共有两种方法表示小数点的存在,即定点表示和浮点表示。[1]
定点表示的数称为定点数,浮点表示的数称为浮点数。
简单来说,定点数就是小数点一直固定在最后一个整数位的后面。[1]
下面介绍一下浮点数
浮点数
就是小数点的位置可以浮动的数。
通常用
S:尾数(可正可负)
j:阶码(可正可负)
r:基数(或基值)
例如:
1.2345*10^2,1.2345尾数,10基数,2阶码。
浮点数在机器中的表示形式
如下图:
阶码:反应浮点数小数点的实际位置和浮点数的表示范围。
尾数的位数反应浮点数的精度。
为了提高精度,需要进行浮点数的规格化
-
只是一种表现形式,
我们知道尾数的位数决定浮点数的有效位数,有效位数越多,数据的精确度越高。
那么为了使有效数字尽量占满尾数数位,必须在运算过程中对浮点数进行规格化操作。
-
将尾数最高位为1的浮点数称为规格化数[1]。规格化后的数字精度最高。
-
通过固定一个
1
,可以节省一个存储位,提高尾数的有效位数。
IEEE754标准
前面我们知道了浮点数在机器中的表示形式,而现代计算机中,浮点数一般采用 IEEE 754
标准。
-
IEEE 754
规定了浮点数的存储方式、算术格式等方法。
IEE754标准不仅包含规格化数,还定义了非规格化数、特殊值等规则。现在我们以0.6(规格化数)进行讲解。
规格化形式:
符号(sign):1位,0表示正数,1表示负数
指数(exponent):对于单精度浮点数,使用8位;对于双精度,使用11位。指数字段存储的值是实际指数加上一个偏移量(bias)。
-
而在单精度浮点数中,偏移量是127
尾数(Mantissa):在单精度中,23位;在双精度中,52位。尾数部分是小数点后的部分。
-
尾数的位数反应浮点数的精度,它前面隐含一个1(对于非零数值)
我们知道:0.6的二进制:1001 1001 1001 1001 1001...
转化为1.尾数的形式:1.0011 0011 0011... × 2^(-1),左移1位,相当于乘2^(-1)。
-
所以指数值-1存储的指数是 126。
-
126转换为8位二进制:01111110
符号位(正数): 0 指数位(-1+127然后转二进制): 01111110 尾数位(23位,被截断): 00110011001100110011001
所以0.6实际在计算机中存储的二进制值为:
0 01111110 00110011001100110011001
这种截断会导致微小的误差,这就是switch不支持float和double的根本原因。
也是0.1 + 0.2 ≠ 0.3
的根本原因。
参考:
[1]计算机组成原理/唐朔飞编著.一2版.一北京:高等教育出版社,2008.1 ISBN978-7-04-022390-3