java double float 精度_Java中浮点类型的精度问题 double float

要说清楚Java浮点数的取值范围与其精度,必须先了解浮点数的表示方法与浮点数的结构组成。因为机器只认识01,你想表示小数,你要机器认识小数点这个东西,必须采用某种方法。比如,简单点的,float四个字节,前两个字节表示整数位,后两个字节表示小数位(这就是一种规则标准),这样就组成一个浮点数。而Java中浮点数采用的是IEEE 754标准。

IEEE 754 标准

IEEE 754 标准是IEEE二进位浮点数算术标准(Standard for Floating-Point Arithmetic)的标准编号,等同于国际标准ISO/IEC/IEEE 60559  。该标准由美国电气电子工程师学会(IEEE)计算机学会旗下的微处理器标准委员会(Microprocessor Standards Committee, MSC)发布。

这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number),一些特殊数值(如:无穷 Inf、非数值NaN),以及这些数值的"浮点数运算子";它也指明了四种数值修约规则和五种例外状况(包括例外发生的时机与处理方式)。

IEEE 754 标准规定了计算机程序设计环境中的二进制和十进制的浮点数自述的交换、算术格式以及方法。

IEEE 754标准是最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。

IEEE 754 标准规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43位以上,很少使用)与延伸双精确度(79位元以上,通常以80位元实做)。只有32位模式有强制要求,其他都是选择性的。

官方文档中的介绍

Floating-Point Types, Formats, and Values

The floating-point types arefloatanddouble, which are conceptually概念associated with the single-precision 32-bit and double-precision 64-bit format IEEE 754 values and operations as specified指定 inIEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Standard 754-1985 (IEEE, New York).

浮点类型是float 和double,它们是概念性概念,与单精度32位和双精度64位格式的IEEE 754的值和运算相关,这些是在这个标准中制订的:IEEE标准二进制浮点运算 ANSI / IEEE标准 754-1985(IEEE,纽约)。

The IEEE 754 standard includes not only positive and negative numbers that consist of a sign and magnitude量级, but also positive and negative zeros, positive and negativeinfinities, and specialNot-a-Numbervalues (hereafter今后 abbreviated缩写 NaN). A NaN value is used to represent the result of certain某些 invalid operations such as dividing zero by zero. NaN constants of bothfloatanddoubletype are predefined asFloat.NaNandDouble.NaN.

IEEE 754标准不仅包括正数和负数,它们包括符号和量级,还包括正零和负零,正负无穷大和特殊非数字值(以下简称为NaN)。NaN值用于表示某些无效操作的结果,例如将零除零。float和double类型的NaN常数预定义为Float.NaN和Double.NaN。

Every implementation of the Java programming language is required to support two standard sets of floating-point values, called thefloat value setand thedouble value set. In addition, an implementation of the Java programming language may support either or both of two extended-exponent扩展指数 floating-point value sets, called thefloat-extended-exponent value setand thedouble-extended-exponent value set. These extended-exponent value sets may, under certain circumstances, be used instead of the standard value sets to represent the values of expressions of typefloatordouble(§5.1.13,§15.4).

Java编程语言的每个实现都需要支持两个标准的浮点值集合,称为float value set和double value set。此外,Java编程语言的实现可以支持称为float扩展指数值集合和double扩展指数值集合的两个扩展指数浮点值集合中的一个或两者。在某些情况下,这些扩展指数值集可以用来代替标准值集合来表示类型float或double(§5.1.13,§15.4)表达式的值。

The finite有限的 nonzero values of any floating-point value set can all be expressed in the forms·m· 2(e-N+ 1), wheresis +1 or -1,mis a positive integer less than 2N, andeis an integer betweenEmin= -(2K-1-2) andEmax= 2K-1-1, inclusive包含, and whereNandKare parameters that depend on the value set. Some values can be represented in this form in more than one way; for example, supposing that a valuevin a value set might be represented in this form using certain values fors,m, ande, thenif it happened thatmwere even andewere less than 2K-1, one could halvemand increaseeby 1 to produce a second representation for the same valuev. A representation in this form is callednormalizedifm≥ 2N-1; otherwise the representation is said to bedenormalized. If a value in a value set cannot be represented in such a way thatm≥ 2N-1, then the value is said to be adenormalized value, because it has no normalized representation.

任何浮点值集合中的【有限非零值】都可以用s·m·2(e-N+ 1)来表示,其中s是 +1 或 -1,m是小于 2N的正整数,E是 [ Emin= -(2K-1-2)  ,Emax= 2K-1-1]之间的整数,并且其中参数N和 K是依赖于集合的值。一些值可以以多种方式以这种形式表示;例如,假设值集合中的值v可以使用s,m和e的某些值以此形式表示,则如果发生m为偶数且e小于2K-1,则可以将一半米和增加e1以产生相同的值的第二表示v。在这种形式的表示被称为归一化的,如果m≥2N-1;否则表示被称为非规范化。如果在设定的值的值不能在这样的方式来表示中号≥2Ñ-1,则该值被认为是一个非标准化的值,因为它没有归一化表示。

The constraints on the parametersNandK(and on the derivedparametersEminandEmax) for the two requiredand two optional floating-point value sets are summarizedinTable 4.1.

表4.1中总结了两个必需和两个可选浮点值集合的参数 N 和 K(以及派生参数Emin和Emax)的约束。

Table 4.1. Floating-point value set parameters

Parameterfloatfloat-extended-exponentdoubledouble-extended-exponentN24245353

K8≥ 1111≥ 15

Emax+127≥ +1023+1023≥ +16383

Emin-126≤ -1022-1022≤ -16382

...

浮点数的组成结构

符号位S_指数位E_尾数位M

例如,一个float类型的数据占用4个字节共32位,其各个组成部分为:

符号位(S):最高位(31位)为符号位,表示整个浮点数的正负,0为正,1为负

指数位(E):23-30位共8位为指数位,这里指数的底数规定为2。并且指数位是以补码的形式来划分的(最高位为指数位的符号位,0为正,1为负)。另外,标准中还规定了,当指数位8位全0或全1的时候,浮点数为非正规形式,所以指数位真正范围为:-126~127。

尾数位(M):0-22位共23位为尾数位,表示小数部分的尾数,即形式为1.M或0.M,至于什么时候是 1 什么时候是 0,则由指数和尾数共同决定。小数部分最高有效位是1的数被称为正规(规格化)形式。小数部分最高有效位是0的数被称为非正规(非规格化)形式,其他情况是特殊值。

取值范围

float和double的【取值范围】是由【指数的位数】来决定的,其中,负指数决定了浮点数所能表达的【绝对值最小】的非0数,而正指数决定了浮点数所能表达的【绝对值最大】的数,也即决定了浮点数的取值范围。

S:符号位,E:指数位,M:尾数位

float:S1_E8_M23,指数位有8位,指数的取值范围为-2^7~2^7-1(即-128~127)

float的取值范围为-2^128 ~ +2^127(10^38级别的数)

double:S1_E11_M52,指数位有11位,指取的取值数范围为-2^10~2^10-1(即-1024~1023)

double的取值范围为-2^1024 ~ +2^1023(10^308级别的数)

PS:官方文档中好像说float指数的取值范围为-126~127,double指取的取值数范围为-1022~1023

精度

float和double的【精度】是由【尾数的位数】来决定的,float的尾数位有23位,double的尾数位有52位。

float:S1_E8_M23,尾数位有23位,2^23 = 83886_08,一共7位,这意味着最多能有7位有效数字,但能保证的为6位,也即float的精度为6~7位有效数字;

double:S1_E11_M52,尾数位有52位,2^52 = 45035_99627_37049_6,一共16位,同理,double的精度为15~16位有效数字。

总结

浮点数float和double在内存中是按科学计数法来存储的,取值范围是由指数的位数来决定的,精度是由尾数的位数来决定的。

浮点数  精度/位数      符号位S         指数位E   扩展范围(指数的取值范围)      最大/小值(取值范围)          尾数位M   尾数取值范围(精度)

float32bit单精度1bit(0正1负)8bit-2^7~2^7-1(-128~127)2^127(10^38级别的数)23bit8388608,7位,精度为6~7位

double64bit双精度1bit(0正1负)11bits-2^10~2^10-1(-1024~1023)2^1023(10^308级别的数)    52bit45035_99627_37049_6,16位,精度为15~16位

浮点数和二进制的相互转化

十进制浮点数如何用二进制表示

计算过程:将该数字乘以2,取出整数部分作为二进制表示的第1位(大于等于1为1,小于1为0);然后再将小数部分乘以2,将得到的整数部分作为二进制表示的第2位......以此类推,直到小数部分为0。

特殊情况: 小数部分出现循环,无法停止,则用有限的二进制位无法准确表示一个小数,这也是在编程语言中表示小数会出现误差的原因

下面我们具体计算一下0.6的小数表示过程:

0.6 * 2 = 1.2 ——————- 1

0.2 * 2 = 0.4 ——————- 0

0.4 * 2 = 0.8 ——————- 0

0.8 * 2 = 1.6 ——————- 1

0.6 * 2 = 1.2 ——————- 1

0.2 * 2 = 0.4 ——————- 0

…………

我们可以发现在该计算中已经出现了循环,0.6用二进制表示为 1001 1001 1001 1001 ……

如果是10.6,那个10.6的完整二进制表示为 1010.1001 1001 1001……

二进制浮点数如何转换为十进制

计算过程:从左到右,v[i] * 2^( - i ), i 为从左到右的index,v[i]为该位的值,直接看例子,很直接的

我们再拿0.6的二进制表示举例:1001 1001 1001 1001

1 * 2^-1 + 0 * 2^-2 + 0 * 2^-3 + 1 * 2^-4 + 1 * 2^-5 + ……

= 1 * 0.5 + 0 + 0 + 1 * 1/16 + 1 * 1/32 + ……

= 0.5 + 0.0625 + 0.03125 + ……

=无限接近0.6

为何float类型的值赋给double类型的变量后可能会出现精度问题

因为float的尾数位有23位,double的尾数位有52位,所以将float类型中保存的0.6的二进制转换成double类型时(低位的二进制全变成了0),与用double类型保存的0.6的二进制是不一样的,所以才出现了问题。

用更形象的图表表示就是:

float类型的变量f=0.6f:    1001 1001 1001 1001 1001 100

double类型的d1=0.6d:1001 1001 1001 100110011001 1001 1001 100110011001 10011001

将float类型的变量 f赋值给double类型的变量 d2 后,d2 中实际中的数据为:

1001 1001 1001 1001100110000000000000000000000000000000

如果你拿 d2 去和 d1 比较的话,他们是不相等的

float f = 0.6f;

double d1 = 0.6d;

double d2 = f;

System.out.println((d1 == d2) + " " + f + " " + d2);//false 0.6 0.6000000238418579

double d = 0.6;

System.out.println((float) d + " " + d);//0.6 0.6

不过还有一个问题,就是为啥d2的值会大于的d1,而不是小于d1?

浮点数参与运算示例代码

我们知道浮点数是无法在计算机中准确表示的,例如0.1在计算机中只是表示成了一个近似值,因此,对浮点数的运算时结果具有不可预知性。

在进行数字运算时,如果有double或float类型的浮点数参与计算,【可能】会出现计算不准确的情况。如以下示例代码:

//注意,以下案例是刻意挑选出来的,并【不是所有】情况都会出现类似问题的

System.out.println(0.05+0.01); //0.060000000000000005

System.out.println(1.0-0.42); //0.5800000000000001

System.out.println(4.015*100); //401.49999999999994

System.out.println(123.3/100); //1.2329999999999999

为解决这种问题,在涉及到浮点数计算的,可以使用使用BigDecimal,如下:

double addValue = BigDecimal.valueOf(0.05).add(BigDecimal.valueOf(0.01)).doubleValue();

System.out.println("0.05+0.01=" + (0.05 + 0.01) + " " + addValue);//0.05+0.01=0.060000000000000005 0.06

double subtractValue = BigDecimal.valueOf(1.0).subtract(BigDecimal.valueOf(0.42)).doubleValue();

System.out.println("1.0-0.42=" + (1.0 - 0.42) + " " + subtractValue);//1.0-0.42=0.5800000000000001 0.58

double multiplyValue = BigDecimal.valueOf(4.015).multiply(BigDecimal.valueOf(100)).doubleValue();

System.out.println("4.015*100=" + (4.015 * 100) + " " + multiplyValue);//4.015*100=401.49999999999994 401.5

double divideValue = BigDecimal.valueOf(123.3).divide(BigDecimal.valueOf(100), 10, BigDecimal.ROUND_HALF_UP).doubleValue();

System.out.println("123.3/100=" + (123.3 / 100) + " " + divideValue);//123.3/100=1.2329999999999999 1.233

如果不想麻烦,对于一般的运算我们也可以不用计较,毕竟在采取指定的RoundingMode格式化数据后,都会返回可以预见的近似值,比如:

String pattern = "#,##0.00";//强制保留两位小数,整数部分每三位以逗号分隔,整数部分至少一位

DecimalFormat format = new DecimalFormat(pattern);

format.setRoundingMode(RoundingMode.HALF_UP);//默认不是四舍五入,而是HALF_EVEN

System.out.println(format.format(0.05 + 0.01)); //0.06

System.out.println(format.format(1.0 - 0.42)); //0.58

System.out.println(format.format(4.015 * 100)); // 401.50

System.out.println(format.format(123.3 / 100)); //1.23

但是对于【比较型】的计算(大于小于等于),就一定要小心了

double d = 0.06;//Java当中默认声明的小数是double类型的,其默认后缀"d"或"D"可以省略

float f = 0.06f;//如果要声明为float类型,需显示添加后缀"f"或"F"

System.out.println((0.05 + 0.01) + " " + (0.05f + 0.01f));//0.060000000000000005 0.060000002

System.out.println((d == (0.05 + 0.01)) + " " + (f == (0.05f + 0.01f)));//false false

System.out.println(d + " " + f + " " + (float) d + " " + (double) f);//0.06 0.06 0.06 0.05999999865889549

System.out.println((d == f) + " " + (d == (double) f) + " " + ((float) d == f));//false false true

//虽然向下转型后可以保证相等,但是一般不会主动干丢失精度的事的!

2017-8-29

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值