Switch中为什么不支持float、double、long?——IEEE754标准

        近期在学习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的方法转换,现在我们复习一下小数的二进制转换方法:

  1. 将小数部分乘以2。

  2. 记录结果的整数部分(0或1)作为二进制位。

  3. 取结果的小数部分作为新的数值重复步骤1和2,直到结果为0或达到所需的精度。

  4. 将步骤2中记录的所有二进制位按顺序排列,即为小数部分的二进制表示。

我们用 0.6 来演示下:

乘以 2整数部分小数部分
0.6 * 210.2
0.2 * 200.4
0.4 * 200.8
0.8 * 210.6
0.6 * 210.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

【计组】.透彻理解浮点数与IEEE754标准哔哩哔哩bilibili

【算法】解析IEEE 754 标准 - HDWK - 博客园

Java中的关键字是指具有特定含义和用途的保留字,不能作为标识符(变量名、类名、方法名等)使用。以下是Java中所有关键字的作用: 1. abstract:抽象类或抽象方法的修饰符,表示该类或方法不可被实例化或调用,只能被子类继承或实现。 2. assert:断言语句的关键字,用于在代码中检查某个条件是否为真,如果不为真就会抛出一个 AssertionError 异常。 3. boolean:布尔类型的数据类型,表示真或假。 4. break:跳出循环或 switch 语句的关键字。 5. byte:字节类型的数据类型,表示-128到127之间的整数。 6. case:在 switch 语句中用于判断某个值是否等于某个常量。 7. catch:用于捕获异常的关键字,在 try-catch-finally 结构中使用。 8. char:字符类型的数据类型,表示单个字符。 9. class:声明一个类的关键字。 10. const:Java中没有实际意义,但为了与 C++ 兼容保留了该关键字。 11. continue:跳过当前循环中剩余的语句,执行下一次循环。 12. default:在 switch 语句中用于指定默认的分支。 13. do:循环语句的关键字,执行一个语句块,然后根据条件判断是否继续执行。 14. double:双精度浮点数类型的数据类型。 15. else:在 if 语句中用于指定条件不成立时执行的代码块。 16. enum:枚举类型的关键字,用于定义一组常量。 17. extends:表示一个类继承自另一个类的关键字。 18. final:修饰类、方法和变量,表示它们的值或引用无法改变。 19. finally:在 try-catch-finally 结构中,无论是否发生异常都会执行的代码块。 20. float:单精度浮点数类型的数据类型。 21. for:循环语句的关键字,用于执行一个语句块多次。 22. goto:Java中没有实际意义,但为了与 C++ 兼容保留了该关键字。 23. if:条件语句的关键字,用于判断某个条件是否成立。 24. implements:表示一个类实现一个接口的关键字。 25. import:引入一个包或类的关键字。 26. instanceof:用于判断一个对象是否属于某个类的关键字。 27. int:整数类型的数据类型。 28. interface:声明一个接口的关键字。 29. long:长整型数据类型。 30. native:表示一个方法是本地方法(由本地代码实现)的关键字。 31. new:创建一个新对象的关键字。 32. package:声明一个包的关键字。 33. private:表示一个方法或变量只能被同一个类中的其他方法或变量访问的关键字。 34. protected:表示一个方法或变量可以被同一个包中的其他类访问,或者被继承的子类访问的关键字。 35. public:表示一个方法或变量可以被任何类访问的关键字。 36. return:从方法中返回值的关键字。 37. short:短整型数据类型。 38. static:表示一个方法或变量属于类而不是属于对象的关键字。 39. strictfp:表示一个方法是严格遵守 IEEE 754 浮点数规范的关键字。 40. super:表示一个对象所属的超类的关键字。 41. switch:多分支语句的关键字。 42. synchronized:表示一个方法或代码块是线程安全的关键字。 43. this:表示当前对象的关键字。 44. throw:用于抛出异常的关键字。 45. throws:用于声明某个方法可能会抛出的异常的关键字。 46. transient:表示一个变量不会被序列化的关键字。 47. try:异常处理语句的关键字。 48. void:表示一个方法不返回任何值的关键字。 49. volatile:表示一个变量是易变的(可能会被多个线程同时访问)的关键字。 50. while:循环语句的关键字,用于执行一个语句块多次,直到条件不成立为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值