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 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).html

浮點類型是float 和double,它們是概念性概念,與單精度32位和雙精度64位格式的IEEE 754的值和運算相關,這些是在這個標準中制訂的:IEEE標準二進制浮點運算 ANSI / IEEE標準 754-1985(IEEE,紐約)。
java


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.express

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).oracle

Java編程語言的每一個實現都須要支持兩個標準的浮點值集合,稱爲float value set 和 double value set此外,Java編程語言的實現能夠支持稱爲float擴展指數值集合double擴展指數值集合的兩個擴展指數浮點值集合中的一個或二者在某些狀況下,這些擴展指數值集能夠用來代替標準值集合來表示類型floatdouble§5.1.13 §15.4表達式的值
app


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 s, m, 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.less

任何浮點值集合中的【有限非零值】均可以用 s · m ·2 e - N + 1)表示其中 是 +1 或 -1,是小於 2的正整數是 [ Emin = -(2K-1-2)  ,  Emax = 2K-1-1 之間的整數,而且其中參數 和 是依賴於集合的值。一些值能夠以多種方式以這種形式表示; 例如,假設值集合中的值v可使用sme的某些值以此形式表示 ,則若是發生m爲偶數且e小於2 K -1,則能夠將一半和增長e 1以產生相同的值的第二表示v在這種形式的表示被稱爲歸一化的,若是m ≥2 N -1 ; 不然表示被稱爲非規範化若是在設定的值的值不能在這樣的方式來表示中號 ≥2 Ñ -1,則該值被認爲是一個非標準化的值,由於它沒有歸一化表示。
eclipse


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)的約束。
ide


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


相關文章
相關標籤/搜索