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

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

IEEE 754 标准

更多详见:https://baike.baidu.com/item/IEEE%20754

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位模式有强制要求,其他都是选择性的。

官方文档中的介绍

更多详见:https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3

Floating-Point Types, Formats, and Values

The floating-point types are float and double, which are conceptually概念 associated with the single-precision 32-bit and double-precision 64-bit format IEEE 754 values and operations as specified指定 in IEEE 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 negative infinities, and special Not-a-Number values (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 both float and double type are predefined as Float.NaN and Double.NaN.

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

Every implementation of the Java programming language is required to support two standard sets of floating-point values, called the float value set and the double 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 the float-extended-exponent value set and the double-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 type float or double (§5.1.13§15.4).

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

The finite有限的 nonzero values of any floating-point value set can all be expressed in the form s · m · 2(e - N + 1), where s is +1 or -1, m is a positive integer less than 2N, and e is an integer between Emin = -(2K-1-2) and Emax = 2K-1-1, inclusive包含, and where N and K are 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 value v in a value set might be represented in this form using certain values for sm, and e, then if it happened that m were even and e were less than 2K-1, one could halve m and increase e by 1 to produce a second representation for the same value v. A representation in this form is called normalized if m ≥ 2N-1; otherwise the representation is said to be denormalized. If a value in a value set cannot be represented in such a way that m ≥ 2N-1, then the value is said to be adenormalized value, because it has no normalized representation.

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

The constraints on the parameters N and K (and on the derived parameters Emin and Emax) for the two required and two optional floating-point value sets are summarized in Table 4.1.

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

Table 4.1. Floating-point value set parameters

Parameter float float-extended-exponent double double-extended-exponent
N 24 24 53 53
K 8 ≥ 11 11 ≥ 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   尾数取值范围(精度)

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

double  64bit双精度   1bit(0正1负)   11bits      -2^10~2^10-1(-1024~1023)     2^1023(10^308级别的数)    52bit       45035_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 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001

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

1001 1001 1001 1001 1001 1000 0000 0000 0000 0000 0000 0000 0000

如果你拿 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

来自为知笔记(Wiz)

时间: 2024-08-09 06:16:01

Java中浮点类型的精度问题 double float的相关文章

java 基础 浮点类型

1.浮点类型用于表示小数的数据类型. 2.浮点数原理:也就是二进制科学计数法. 3.Java的浮点类型有float和double两种. 4.Java默认浮点类型计算的结果是double类型,字面量也是double类型. 1.十进制浮点数科学计数法: 219345 = 2.19345*(10^5) 2.二进制浮点数科学计数法: 10111 = 1.0111*(2^100) 1.float类型共32位,1位为符号位, 指数8位, 尾数23位. 2.float的精度是23位(即能精确表达23位的数,超

java中整数类型的2进制表示

java中的4种整数类型:byte,short,int,long均采用补码表示. 如下图右边所示 在java中对二进制数没有"字面"表示方法.十六进制可以用OX或者Ox指示,八进制可以用O指示. 下面的代码可以输出byte类型的数据的2进制表示形式: public class Test { public static void main(String[] args) { byte a = -128; test(a); } public static void test(byte m){

Java中值类型与引用类型

JAVA中值类型和引用类型的不同? 1.定义 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值,那么调用这个方法是传入的变量的值也将改变:值类型表示复制一个当前变量传给方法,当你在这个方法中改变这个变量的值时,最初生命的变量的值不会变.通俗说法: 值类型就是现金,要用直接用:引用类型是存折,要用还得先去银行取现. 2.分类 (1)值类型 值类型也就是基本数据类型 基本数据类型常被称为四类八种. 四类:1.整型 2.浮点型 3.字符型4.逻辑

java中String类型转换方法

integer to String : int i = 42;String str = Integer.toString(i);orString str = "" + idouble to String :String str = Double.toString(i);long to String :String str = Long.toString(l);float to String :String str = Float.toString(f);String to intege

java中基本类型与装箱基本类型“==”比较出现的几种情况

java中基本类型与装箱基本类型"=="比较出现的几种情况 java有一个类型系统有两部分组成,包含基本类型(primitive),例如:int.double等,还有引用类型(reference type),例如:String.List.每个基本类型都有一个对应的引用类型,称为装箱基本类型(boxed promitive).装箱基本类型中对应于int.double的是Integer.Double. Java 1.5发行版本中增加了自动装箱和自动拆箱,自动装箱和自动拆箱就是我们所知道的&

Object,scanner,String,StringBuffer,Java中基本类型的包装类型等

一.Scanner类 Scanner类:简单文本扫描器1.键盘录入步骤:(1)需要创建键盘录入对象:Scanner 键盘录入对象=new Scanner(System.in):(2)导包:import java.util.Scanenr(快捷键:ctrl+shift+o):(3)接收数据:XXX 变量名= 键盘录入对象.nextXXX():(例如:int 变量名=键盘录入对象.nextInt():).2.构造方法public Scanner(InputStream source):以输入流形式录

Java中String类型详解

这篇博客是我一直想总结的,这两天一直比较忙,先上传下照片吧,过后有时间再弄成正常的. 本文主要是对Java中String类型的总结,包括其在JVM中是怎么存储的...

ORACLE 中NUMBER 类型 低精度转换成高精度

例如: 表User中有一个字段 salary  Number(10,3), 如果想把字段salary的类型提高精度到salary  Number(10,6),保留六位小数, 解决办法:1,ALTER TABEL USER MODIFY SALARY NUMBER(13,6); 解释:number类型刚开始是,长度10位,3位小数,如果想增加3位小数,对应的长度也必须增加,否则无法修改.所以NUMBER(13,6);这样就可以提高精度了, ORACLE 中NUMBER 类型 低精度转换成高精度

(转)Java中String类型的参数传递问题

这篇文章主要介绍了简单谈谈Java中String类型的参数传递问题的相关资料,需要的朋友可以参考下 提要:本文从实现原理的角度上阐述和剖析了:在Java语言中,以 String 作为类型的变量在作为方法参数时所表现出的“非对象”的特性. 一.最开始的示例 写代码最重要的就是实践,不经过反复试验而得出的说辞只能说是凭空遐想罢了.所以,在本文中首先以一个简单示例来抛出核心话题: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public clas