java 浮点数为什么精度会丢失

本文通过实例解析了Java中float和double表示法及其精度限制,揭示了20014999为何在float下无法准确表示的原因,并提出了避免此类问题的方法。

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

public class FloatDoubleTest {  
    public static void main(String[] args) {  
    float f = 20014999;  
    double d = f;  
    double d2 = 20014999;  
    System.out.println("f=" + f);  
    System.out.println("d=" + d);  
    System.out.println("d2=" + d2);  
    }  
} 

 

得到的结果如下:

f=2.0015E7

d=2.0015E7

d2=2.0014999E7

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

 

1.关于 java float double 的表示法

Java 语言支持两种基本的浮点类型: float double java 的浮点类型都依据 IEEE 754 标准。IEEE 754 定义了32 位和 64 位双精度两种浮点二进制小数标准。

IEEE 754 用科学记数法以底数为 2 的小数来表示浮点数。

对于32 位浮点数float用 第1 位表示数字的符号,用第2至9位来表示指数,用 最后23 位来表示尾数,即小数部分。

float(32位):

 

 

对于64 位双精度浮点数,用 第1 位表示数字的符号,用 11 位表示指数,52 位表示尾数。

double(64位):

 

 

都是分为三个部分:

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

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

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

 

2. 什么时候会出现无法表示?

任何一个数字,在java底层表示都必须转换成这种科学计数法来表示,那么我们来想想看什么时候这个数字会无法表示呢?那么只有两种情形:

1.幂数不够表示了:这种情况往往出现在数字太大了,超过幂数所能承受的范围,那么这个数字就无法表示了。如幂数最大只能是10,但是这个数字用科学计数法表示时,幂数一定会超过10,就没办法了。

2.尾数不够表示了:这种情况往往出现在数字精度太长了,如1.3434343233332这样的数字,虽然很小,还不超过2,这种情况下幂数完全满足要求,但是尾数已经不能表示出来了这么长的精度。

 

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

通过以上分析,应该已经知道,这个数字不大,转换成IEEE754科学计数法之后幂数一定是满足要求的,只是尾数不能表示这么精确的数字了。  

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

以下程序可以得出 20014999 double float 下的二进制表示方式。  

  

public class FloatDoubleTest3 {   
public static void main(String[] args) {    
double d = 20014999;    
long l = Double.doubleToLongBits(d);    
System.out.println(Long.toBinaryString(l));    
float f = 20014999;   
int i = Float.floatToIntBits(f);   
System.out.println(Integer.toBinaryString(i));    
}    
}

 

输出结果如下:

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 ,而只能通过四舍五入得到一个近似值。

 

小结

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

 

<think>我们正在讨论整数转浮点数时的精度丢失问题。用户要求解释精度丢失的原因。 根据IEEE 754标准,单精度浮点数(float)使用32位表示,其中1位符号位,8位指数位,23位尾数位(实际有24位精度,因为有一个隐含的1)。双精度浮点数(double)使用64位,1位符号位,11位指数位,52位尾数位(实际53位精度)。 整数在计算机中通常用固定长度的二进制补码表示。当我们将一个整数转换为浮点数时,如果整数的绝对值超过了浮点数尾数能够精确表示的范围,就会发生精度丢失。 具体来说,浮点数的尾数部分能够表示的整数范围是有限的。例如,对于单精度浮点数,尾数有23位显式存储,加上隐含的1位,共24位。因此,它可以精确表示的最大整数是$2^{24}-1$(即16777215)。超过这个数的整数,在转换为单精度浮点数时,由于尾数位数有限,无法表示超出部分,因此会进行舍入,导致精度丢失。 同样,双精度浮点数可以精确表示的最大整数是$2^{53}-1$(即9007199254740991)。超过这个范围的整数转换为双精度浮点数时也会出现精度丢失。 另外,需要注意的是,浮点数表示的范围虽然很大(指数部分可以很大),但并不是所有整数都能精确表示,只有那些在尾数位数限制内的整数才能精确表示。 引用说明:在用户提供的引用[^1]中,提到PHP内部将整数存储为与机器相关的有符号长整型,当整数超出范围时会被存储为浮点数。在打包这些浮点数作为整数时,会先转换为整数类型,这可能导致字节模式不符合预期。这进一步说明了整数转浮点数可能带来的问题。 因此,整数转浮点数精度丢失的原因主要是浮点数的尾数位数有限,无法精确表示超出其精度范围的整数,从而需要舍入。 接下来,生成相关问题。</think>### 整数转浮点数精度丢失的原因 当整数转换为浮点数时出现精度丢失的根本原因在于**浮点数的存储结构限制**。具体分析如下: #### 1. **浮点数的存储原理** 浮点数采用 IEEE 754 标准存储,以单精度(32位)为例: - **1位符号位**:表示正负 - **8位指数位**:控制数值范围 - **23位尾数位**(有效数字):决定精度 实际精度为 $2^{23} \approx 8.4 \times 10^6$(即**精确表示的最大整数为 16,777,215**)[^1]。 例如: - $16,777,215$ 可精确存储为单精度浮点数 - $16,777,216$ 会被舍入为 $1.6777216 \times 10^7$,丢失最低位精度 #### 2. **整数与浮点数的本质差异** - **整数**:二进制补码表示,**连续精确**(在范围内无间隔) - **浮点数**:科学计数法表示,**离散分布**(数值越大,相邻可表示数的间隔越大) 数学表达:相邻浮点数的间隔为 $\epsilon \times 2^{E}$($\epsilon$ 是机器精度,$E$ 是指数值) #### 3. **关键阈值** - **单精度浮点数**:精确整数上限为 $2^{24} - 1 = 16,777,215$(24位隐含精度) - **双精度浮点数**:精确整数上限为 $2^{53} - 1 \approx 9 \times 10^{15}$ 超过该范围的整数转换时必然发生舍入误差[^1]。 #### 4. **转换过程示例** ```python n = 16_777_217 # 超过单精度上限 float_n = float(n) # 转换为 16777216.0 (精度丢失) ``` 二进制解释: - 整数 `16777217` 的二进制:`1 00000000 00000000 00000001` - 浮点数尾数仅存 23 位,多余位被截断 → 存储为 `16777216` #### 5. **编程语言中的表现** - PHP/C/C++ 等语言中,大整数自动转为浮点数时触发此问题 - JavaScript 所有数字为双精度浮点,最大安全整数为 **$9,007,199,254,740,991$** 超过后运算可能出错:`9007199254740992 + 1 = 9007199254740992` --- ### 避免精度丢失的方法 1. **使用高精度类型**:如 Java 的 `BigInteger`,Python 的 `int` 2. **限制数值范围**:确保整数不超过浮点数的精确表示阈值 3. **分治运算**:对大整数拆分为小段分别处理 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值