`

关于float,double的精度丢失

 
阅读更多

1.疑惑

由于对float或double 的使用不当,可能会出现精度丢失的问题。问题大概情况可以通过如下代码理解:

 

public class FloatDouble {

	/**功能:打印float和double浮点数十进制和二进制表示
	 * @author mike
	 * @param args
	 */
	public static void main(String[] args) {
		double d = 20014999;  
		long l = Double.doubleToLongBits(d);  
		System.out.println("Double:"+"十进制:"+d+",二进制:"+Long.toBinaryString(l));  
		float f = 20014999;  
		int i = Float.floatToIntBits(f);  
		System.out.println("Float:"+"十进制:"+f+",二进制:"+Integer.toBinaryString(i)); 
	}
}


打印结果


Double:十进制:2.0014999E7,二进制:100000101110011000101100111100101110000000000000000000000000000

Float:十进制:2.0015E7,二进制:1001011100110001011001111001100


从输出结果可以看出double 可以正确的表示20014999 ,而float 没有办法表示20014999 ,得到的只是一个近似值。这样的结果很让人讶异。20014999 这么小的数字在float下没办法表示。于是带着这个问 题,做了一次关于float和double学习,做个简单分享,希望有助于大家对java 浮 点数的理解。

 

2.分析


关于 java  float  double

Java 语言支持两种基本的浮点类型: float 和 double java 的浮点类型都依据 IEEE 754 标准。

IEEE 754 定义了32 位和 64 位浮点数使用二进制表示标准。

维基百科:http://zh.wikipedia.org/wiki/IEEE_754


浮点数在c/c++以及java中的内存布局遵循IEEE标准的,首先看一下IEEE所规定的存储的方式:

符号位 指数位 小数部分 指数偏移量 单精度浮点数 双精度浮点数
1 位[31] 8位 [30-23] 23位 [22-00] 127
1 位[63] 11 位[62-52] 52 位[51-00] 1023


     解释一下,首先float变量按上述标准是4个字节,其中最高位为符号位,1代表此浮点数为负数,0代表正数,接下来的8位为指数位,范围0~255,,IEEE规定了一个偏移量127,指数位的值减去127为小数的偏移。低23位为小数部分,这23位是来描述浮点数的值,偏移为0的情况下,这23位数是一个浮点数的小数部分,也就是说位于小数点的右边。

   IEEE 754 用科学记数法以底数为 的小数来表示浮点数。32 位浮点数用 位表示数字的符号,用 位来表示指数,用 23 位来表示尾数,即小数部分。作为有符号整数的指数可以有正负之分。小数部分用二进制(底数 2)小数来表示。对于64 位双精度浮点数,用 位表示数字的符号,用 11 位表示指数,52 位表示尾数。如下两个图来表示:

float(32位):

float

double(64位):

double

都是分为三个部分:

(1)符号位: 一 个单独的符号位s 直接编码符号s 。

(2)幂指数(指数位):k 位 的幂指数E ,移 码表示 

(3)小数位(尾数位):n 位 的小数,原码表示 

那么 20014999 为什么用 float 没有办法正确表示?

结合float和double的表示方法,通过分析 20014999 的二进制表示就可以知道答案了。

以下是 20014999  double  float 下的二进制表示方式。


Double:100000101110011000101100111100101110000000000000000000000000000

Float:1001011100110001011001111001100


对于输出结果分析如下。对于 double 的二进制左边补上符号位 0 刚好可以得到 64 位的二进制数。根据double的表示法,分为符号数、幂指数和尾数三个部分如下:

0 10000010111 0011000101100111100101110000000000000000000000000000

对于 float 左边补上符 号位 0 刚好可以得到 32 位的二进制数。 根据float的表示法, 也分为 符号数、幂指数和尾数三个部分如下 

0 10010111 00110001011001111001100

绿色部分是符号位,红色部分是幂指数,蓝色部分是尾数。

对比可以得出:符号位都是 0 ,幂指数为移码表示,两者刚好也相等。唯一不同的是尾数。

 double 的尾数 为: 001100010110011110010111 0000000000000000000000000000 省略后面的零,至少需要24位才能正确表示 

而在 float 下面尾数 为: 00110001011001111001100 ,共 23 位。

为什么会这样?原因很明显,因为 float尾数 最多只能表示 23 位,所以 24 位的 001100010110011110010111  float 下面经过四舍五入变成了 23 位的 00110001011001111001100 。所以 20014999  float 下面变成了 20015000 
也就是说
 
20014999 虽然是在float的表示范围之内,但  IEEE 754  float 表示法精度长度没有办法表示出 20014999,而只能通过四舍五入得到一个近似值。

double 的尾数 为: 001100010110011110010111加1后舍弃最后一位,就变成 float 尾数 为: 00110001011001111001100


3.引申:

 

(1)十进制转二进制:

 

     用2辗转相除至结果为1 将余数和最后的1从下向上倒序写 就是结果

     例如

     302 302/2 = 151 余0

     151/2 = 75 余1

75/2 = 37 余1

37/2 = 18 余1

18/2 = 9 余0

9/2 = 4 余1

4/2 = 2 余0

2/2 = 1 余0

故二进制为100101110

 

(2)十进制转二进制,小数部分

 

小数乘以2,取整,小数部分继续乘以2,取整,得到小数部分0为止,将整数顺序排列。

例如

0.8125x2=1.625 取整1

小数部分是0.625 0.625x2=1.25 取整1

小数部分是0.25 0.25x2=0.5 取整0

小数部分是0.5 0.5x2=1.0 取整1

小数部分是0

结束 所以0.8125的二进制是0.1101

 

(3)二进制转十进制

 

从最后一位开始算,依次列为第0、1、2...位 第n位的数(0或1)乘以2的n次方 得到的结果相加就是答案

例如:

01101011.

转十进制:

第0位:1乘2的0次方=1

1乘2的1次方=2

0乘2的2次方=0

1乘2的3次方=8

0乘2的4次方=0

1乘2的5次方=32

1乘2的6次方=64

0乘2的7次方=0

然后:1+2+0 +8+0+32+64+0=107.

二进制01101011=十进制107.

(4)浮点数转化成二进制 

已知:double类型38414.4。

求:其对应的二进制表示。

分析:double类型共计64位,折合8字节。由最高到最低位分别是第63、62、61、……、0位: 
最高位63位是符号位,1表示该数为负,0表示该数为正;  62-52位,一共11位是指数位; 51-0位,一共52位是尾数位。 


步骤:

按照IEEE浮点数表示法,下面先把38414.4转换为二制数。 


把整数部和小数部分开处理:整数部直接化二进制进制: 1001011000001110 。

 

小数的处理: 
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.4*2=0.8 取0

..............
实际上这永远算不完!这就是著名的浮点数精度问题。所以直到加上前面的整数部分算够53位就行了。

隐藏位技术:最高位的1不写入内存(最终保留下来的还是52位)。 
如果你够耐心,手工算到53位那么因该是:38414.4(10)=1001011000001110.0110011001100110011001100110011001100(2)

科学记数法为:1.001011000001110 0110011001100110011001100110011001100,右移了15位,所以指数为15。

或者可以如下理解:

1.001011000001110 0110011001100110011001100110011001100×2^15 

于是来看阶码,按IEEE标准一共11位,可以表示范围是-1024 ~ 1023。因为指数可以为负,为了便于计算,规定都先加上1023(2^10-1),在这里,阶码:15+1023=1038。二进制表示为:100 00001110; 符号位:因为38414.4为正对应 为0; 合在一起(注:尾数二进制最高位的1不要): 

01000000 11100010 11000001 110 01100  11001100  11001100  11001100  11001100



4.总结:

 

浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。往往产生误差不是 因为数的大小,而是因为数的精度。因此,产生的结果接近但不等于想要的结果。尤其在使用 float  double 作精确运 算的时候要特别小心。
可以考虑采用一些替代方案来实现。如通过
 
String 结合 BigDecimal 或 者通过使用 long 类型来转换。

 

分享到:
评论

相关推荐

    解决java数值范围以及float与double精度丢失的问题

    解决java数值范围以及float与double精度丢失的问题 Java中的数值范围和浮点数精度问题是许多开发者经常遇到的问题。下面我们将详细探讨Java中的数值范围、float和double类型的精度问题,并且提供解决方案。 一、...

    基于C++浮点数(float、double)类型数据比较与转换的详解

    例如,使用`%.8lf`格式化`float`可能无法避免精度丢失,而在适当情况下,对`double`使用`%.20lf`可以保留更多位数,减少精度损失。 3. **浮点数比较**: 直接使用`==`操作符比较两个`double`类型的浮点数是否相等...

    float_double存储问题

    float 和 double 是 C 语言和 C# 语言中最常用的浮点类型,它们分别使用单精度和双精度来存储浮点数据。 float 数据占用 32bit,而 double 数据占用 64bit。 在 IEEE 的规范下,float 遵从的是 IEEE R32.24,而 ...

    int 到 float 转换精度损失测试程序 C++ 源代码

    例如,可以采用`double`代替`float`以获得更高的精度,或者在必要时使用高精度库如GMP(GNU Multiple Precision Arithmetic Library)来避免精度损失。 总结来说,从`int`到`float`的转换可能会因为浮点数的有限...

    C# 按照IEEE 754标准对Float和Double类型进行转换

    在进行浮点数转换时,需要注意精度丢失和溢出问题。由于浮点数的有限精度,某些特定的十进制数字可能无法精确表示为二进制浮点数,这可能导致计算结果出现微小的误差。此外,过大或过小的数值可能会导致指数溢出,...

    iOS 解决floatValue,doubleValue等计算不精确问题,一句话解决精确计算,精确比较

    例如,`float`和`double`类型的值在进行加减乘除运算后,结果可能与预期不符,尤其是在比较两个浮点数是否相等时,直接使用`==`可能会得到错误的结果。 为了解决这个问题,我们可以采用以下策略: 1. **避免直接...

    Java 精确计算-double-float-String

    5. **转换类型**:如果需要将`BigDecimal`转换回`double`或`float`,可以使用`doubleValue()`和`floatValue()`方法,但要注意这可能会丢失精度。 在实际开发中,`BigDecimal`的使用可以确保计算结果的准确无误,但...

    float_double_explicit.rar_float

    标题中的“float_double_explicit.rar_float”暗示了这个压缩包可能包含了与浮点数(float)和双精度浮点数(double)处理相关的代码示例或测试用例,特别是关于明确(explicit)类型的转换。描述提到“Implements a ...

    详解java中float与double的区别

    float 的精度是 8 位有效数字,取值范围是 10 的 -38 次方到 10 的 38 次方,而 double 的精度是 17 位有效数字,取值范围是 10 的 -308 次方到 10 的 308 次方。 float 和 double 都可以用来进行浮点数的运算,...

    MySQL中Decimal类型和Float Double的区别(详解)

    本文主要探讨了Decimal、Float和Double三种数据类型之间的差异,特别是在处理数值计算时的精度问题。 首先,`Float`和`Double`是非标准的浮点数数据类型,它们在数据库内部存储的是近似值。这意味着在存储和计算...

    double 计算过程出现的误差

    尽管`double`类型的精度较高,但在某些情况下仍然会出现精度损失的问题,尤其是在累加等连续计算操作中更为明显。 #### 二、C#中的`double`计算误差示例 ```csharp double d = 0.0; for (int i = 0; i ; i++) { d...

    float型和double型数据的存储方式1

    而对于double,存储方式类似,只是指数位和尾数位更多,因此2.25在转换过程中不会丢失精度。 然而,像2.2这样的数,其二进制表示是无限循环的,这就带来了精度问题。由于float的尾数部分只能存储23位,2.2的小数...

    long_double_float_implicit.rar_float

    例如,从`long double`隐式转换为`float`时,超出`float`表示范围的部分将会丢失,这可能影响计算结果。为了确保精度,程序员应明确进行类型转换,并对可能的精度损失有清晰的理解。 在实际编程中,正确处理浮点数...

    LitJson修改版,Unity支持float数据类型

    原版LitJson库虽然功能强大,但在处理Unity中的`float`类型时可能会出现精度丢失或者无法正确识别的情况,因为Unity的`float`实际上是单精度浮点数,而JSON标准中默认使用的是双精度浮点数(`double`)。 经过修改...

    Java中double类型下出现精度计算错误情况下出力方法

    Java中的简单浮点数类型float和double不能够进行运算,因为大多数情况下是正常的,但是偶尔会出现如上所示的问题。这个问题其实不是JAVA的bug,因为计算机本身是二进制的,而浮点数实际上只是个近似值,所以从二进制...

    double_to_float_converter:将c ++源文件从double转换为float

    - **精度损失**:由于`float`的精度比`double`低,转换可能导致部分小数值丢失,特别是在`double`中存在无法精确表示的分数部分时。 - **溢出**:如果`double`的值超出`float`的表示范围,转换可能会导致溢出,结果...

    C#中处理多位小数精度的精度问题

    3. **避免浮点数的隐式转换**:即使在其他地方使用了`double`或`float`,也要确保在与`decimal`交互时不会发生隐式转换,因为这会导致精度损失。 4. **使用高精度库**:如果`decimal`的精度仍不足以满足需求,可以...

    double类型转换

    尽管`double`类型能够提供比`float`类型更高的精度,但在实际应用中,由于浮点数的二进制表示方式,有时仍然会出现精度丢失的问题。尤其在进行数学运算或比较时,这种精度问题可能会导致不准确的结果。 ### 保留两...

    C语言double和float 实例分析

    对于`double`和`float`之间的类型转换,如果`double`赋值给`float`,可能因为精度丢失而造成信息损失。反之,`float`赋值给`double`则不会丢失信息,因为`double`具有更宽的表示范围。 6. **示例代码解析**: 提供...

    关于浮点数的精度问题

    "关于浮点数的精度问题" 浮点数精度问题是一个经典的问题,对于了解和学习C语言有一定帮助。浮点数的精度问题是由于计算机对浮点数的存储方式和表示方法所致。 IEEE754 的浮点数存储格式对浮点数的表示方法进行了...

Global site tag (gtag.js) - Google Analytics