【講古堂】浮點數java
(dubenju@126.com 2015/12/19)數據庫
衆所周知,因爲用高低電平的電路很容易實現二進制,因此在計算機中廣泛採用二進制來存儲數據。對應的二進制的位用Bit來表示。1字節=8Bits,若是考慮符號的話,那麼一個字節能存儲的數值範圍是-128到127。只能是整數,不能是小數。那麼小數怎麼辦呢?函數
小數的話,必定要有小數點的。若是把小數點固定在一個不變的位置的話,就成了定點數。好比Oracle數據庫的NUMBER(4, 2)則能存儲17.25或0.50這樣的數。spa
定點數(Fixed Point Number).net
所謂定點數,即約定數據的小數點位置是固定不變的。一般將定點數據表示成純小數或純整數。爲了將數表示成純小數,一般把小數點固定在數值部分的最高位以前;而爲了把數表示成純整數,則把小數點固定在數值部分的最後面。設計
對純小數進行運算時,要用適當的比例因子進行折算,以避免產生溢出,或過多損失精度。blog
假設用一個n位字來表示一個定點數x= x0 x1 x2 … xn-1,其中一位x0用來表示數的符號位,其他位數表明它的量值。爲了對全部n位進行統一處理,符號位x0一般放在最左位置,並用數值0和1分別表明正號和負號。對於任意定點數x= x0 x1 x2 … xn-1,若是x表示的是純小數,那麼小數點位於x0和x1之間,數的表示範圍爲:0≤|x|≤1-2-(n-1);若是x 表示的是純整數,則小數點位於最低位xn-1的右邊,數的表示範圍爲:0≤|x|≤2n-1-1。內存
在定點數表示中存在的一個問題是,難以表示數值很大的數據和數值很小的數據。例如,電子的質量(9×10^-28克)和太陽的質量(2×10^33克)相差甚遠,在定點計算機中沒法直接表示,由於小數點只能固定在某一個位置上,從而限制了數據的表示範圍。通常來講,定點數可表示的數值的範圍有限,但要求的處理硬件比較簡單。好比用NUMBER(4, 2)來存儲100.234是不行的。ci
使用定點數get
例子1
FXPN0002.cob
000000 IDENTIFICATION DIVISION.
000000 PROGRAM-ID. FXPN0002.
000000 AUTHOR. dubenju@126.com.
000000 DATE-WRITTEN. 2015.12.18.
000000 DATE-COMPILED.
000000*
000000 ENVIRONMENT DIVISION.
000000 CONFIGURATION SECTION.
000000 SOURCE-COMPUTER. HP.
000000 OBJECT-COMPUTER. HP.
000000*
000000 INPUT-OUTPUT SECTION.
000000 FILE-CONTROL.
000000 SELECT A-FILE ASSIGN TO "FXPN0002a.txt".
000000 SELECT B-FILE ASSIGN TO "FXPN0002b.txt".
000000*
000000 DATA DIVISION.
000000 FILE SECTION.
000000 FD A-FILE RECORDING MODE IS F.
000000 01 A-REC.
000000 05 ACC-NO PIC 9(3)V9(2).
000000 FD B-FILE RECORDING MODE IS F.
000000 01 B-REC.
000000 05 ACC-NO PIC 9(1)V9(4).
000000*
000000*
000000 WORKING-STORAGE SECTION.
000000*
000000 01 A02 PIC 9(3)V9(2).
000000 01 A03 PIC 9(1)V9(4).
000000**** USER-WORK-AREA.
000000*
000000 PROCEDURE DIVISION.
000000*
000000 OPEN OUTPUT A-FILE
000000 OUTPUT B-FILE.
000000*
000000 MOVE 123.45 TO A02.
000000 MOVE 1.2345 TO A03.
000000 DISPLAY '9(3)V9(2)=', A02.
000000 DISPLAY '9(1)V9(4)=', A03.
000000 MOVE A02 TO ACC-NO OF A-REC.
000000 MOVE A03 TO ACC-NO OF B-REC.
000000 WRITE A-REC.
000000 WRITE B-REC.
000000*
000000 CLOSE A-FILE
000000 B-FILE.
000000*
000000 STOP RUN.
000000*
結果:
DBJ@DBJ-PC /prj/cobol/cob
$ ./FXPN0002.exe
9(3)V9(2)=123.45
9(1)V9(4)=1.2345
DBJ@DBJ-PC /prj/cobol/cob
$ cat FXPN0002a.txt
12345
DBJ@DBJ-PC /prj/cobol/cob
$ cat FXPN0002b.txt
12345
DBJ@DBJ-PC /prj/cobol/cob
$
123.45和1.2345寫在文件中都是12345。即用五位數來存儲12345,在3位整數2位小數定義時,就表示123.45,在1位整數4位小數定義時,就表示1.2345。
例子2
FXPN0001.cob
000000 IDENTIFICATION DIVISION.
000000 PROGRAM-ID. DaFXPN0001.
000000 AUTHOR. dubenju@126.com.
000000 DATE-WRITTEN. 2015.12.18.
000000 DATE-COMPILED.
000000*
000000 ENVIRONMENT DIVISION.
000000 CONFIGURATION SECTION.
000000 SOURCE-COMPUTER. HP.
000000 OBJECT-COMPUTER. HP.
000000*
000000 INPUT-OUTPUT SECTION.
000000*
000000 DATA DIVISION.
000000 FILE SECTION.
000000*
000000*
000000 WORKING-STORAGE SECTION.
000000*
000000 01 A02 PIC 9(3)V9(2).
000000 01 A03 PIC 9(1)V9(4).
000000**** USER-WORK-AREA.
000000*
000000 PROCEDURE DIVISION.
000000*
000000 MOVE 12345 TO A02.
000000 MOVE A02 TO A03.
000000 DISPLAY '9(3)V9(2)=', A02.
000000 DISPLAY '9(1)V9(4)=', A03.
000000*
000000 STOP RUN.
000000*
結果:
DBJ@DBJ-PC /prj/cobol/cob
$ ./FXPN0001.exe
9(3)V9(2)=345.00
9(1)V9(4)=5.0000
DBJ@DBJ-PC /prj/cobol/cob
在處理時是按照小數點對齊的。
浮點數(Floating Point Number)
爲了表示更大範圍的數據,數學上一般採用科學計數法,把數據表示成一個小數乘以一個以10爲底的指數。
例如,在計算機中,電子的質量和太陽的質量能夠分別取不一樣的比例因子,以使其數值部分的絕對值小於1,即:
9×10^-28 = 0.9×10^-27
2×10^33 = 0.2×10^34
這裏的比例因子10^-27和10^34要分別存放在機器的某個單元中,以便之後對計算結果按此比例增大。顯然,這要佔用必定的存儲空間和運算時間。
浮點表示法就是把一個數的有效數字和數的範圍在計算機中分別予以表示。這種把數的範圍和精度分別表示的方法,至關於數的小數點位置隨比例因子的不一樣而在必定範圍內自由浮動,改變指數部分的數值至關於改變小數點的位置。在這種表示法中,小數點的位置是能夠浮動的,所以稱爲浮點表示法。
浮點數的通常表示形式爲:
一個十進制數N能夠寫成:N = 10e×M
一個二進制數N能夠寫成:N = 2e×M
其中,M稱爲浮點數的尾數,是一個純小數;e是比例因子的指數,稱爲浮點數的指數,是一個整數。在計算機中表示一個浮點數時,一是要給出尾數M,用小數形式表示;二是要給出指數e,用整數形式表示,常稱爲階碼。尾數部分給出有效數字的位數,於是決定了浮點數的表示精度;階碼部分指明瞭小數點在數據中的位置,於是決定了浮點數的表示範圍。浮點數也是有符號數。
規格化浮點數
若不對浮點數的表示作出明確規定,同一個浮點數的表示就不是唯一的。例如:
(1.75)10 = (1.11)2
= 1.11×2^0
= 0.111×2^1
= 0.0111×2^2
= 0.00111×2^3
※、10進制小數二進制變換方法參照http://my.oschina.net/dubenju/blog/535161
爲了提升數據的表示精度,須要充分利用尾數的有效位數。當尾數的值不爲0時,尾數域的最高有效位應爲1,不然就要用修改階碼同時左右移動小數點的辦法,使其變成符合這一要求的表示形式,這稱爲浮點數的規格化。
IEEE-754標準浮點格式
在IEEE-754標準出現以前,業界並無一個統一的浮點數標準,相反,不少計算機制造商都在設計本身的浮點數規則以及運算細節。爲了便於軟件的移植,浮點數的表示格式應該有一個統一的標準。
1985年,IEEE(Institute of Electrical and Electronics Engineers,美國電氣和電子工程師協會)提出了IEEE-754標準,並以此做爲浮點數表示格式的統一標準。目前,幾乎全部的計算機都支持該標準,從而大大改善了科學應用程序的可移植性。
IEEE標準從邏輯上採用一個三元組{S, E, M}來表示一個數N,它規定基數爲2,符號位S用0和1分別表示正和負,尾數M用原碼錶示,階碼E用移碼錶示。根據浮點數的規格化方法,尾數域的最高有效位老是1,由此,該標準約定這一位不予存儲,而是認爲隱藏在小數點的左邊,所以,尾數域所表示的值是1.M(實際存儲的是M),這樣可以使尾數的表示範圍比實際存儲多一位。爲了表示指數的正負,階碼E一般採用移碼方式來表示,將數據的指數e 加上一個固定的偏移量後做爲該數的階碼,這樣作既可避免出現正負指數,又可保持數據的原有大小順序,便於進行比較操做。
目前,大多數高級語言都按照IEEE-754標準來規定浮點數的存儲格式。IEEE-754標準規定了單精度浮點數(http://my.oschina.net/dubenju/blog/425019)和雙精度浮點數(http://my.oschina.net/dubenju/blog/543120)。
單精度格式(32位):符號位(S)1位;階碼(E)8位,階碼的偏移量爲127(7FH);尾數(M)23位,用小數表示,小數點放在尾數域的最前面;
雙精度格式(64位):符號位(S)1位;階碼(E)11位,階碼的偏移量爲1023(3FFH);尾數(M)52位,用小數表示,小數點放在尾數域的最前面。
在IEEE-754標準中,一個規格化的32位浮點數X的真值可表示爲:
X = (-1)s×(1.M)×2^E-127 e = E-127 (式2-9)
在IEEE-754標準中,一個規格化的64位浮點數X的真值可表示爲:
X = (-1)s×(1.M)×2^E-1023 e = E-1023 (式2-10)
使用單精度浮點數
flt.c
#include <stdio.h>
int main() {
float pi = 3.14159265;
float d = 30.0;
float a = d * pi;
printf("%8.8f\n", a);
return 0;
}
C:\prj\c>flt.exe
94.24777985
C:\prj\c>
Flt.java
package javay.test.java;
public class Flt {
public static void main(String[] args) {
float pi = 3.14159265f;
float d = 30.0f;
float a = d * pi;
System.out.println(a);
}
}
94.24778
3.14159265 * 30.0=94.2477795
使用雙精度浮點數
dbl.c
#include <stdio.h>
int main() {
double pi = 3.141592653589793;
double d = 30.0;
double a = d * pi;
printf("%16.16f\n", a);
return 0;
}
C:\prj\c>dbl.exe
94.2477796076937860
C:\prj\c>
Dbl.java
package javay.test.java;
public class Dbl {
public static void main(String[] args) {
double pi = 3.141592653589793;
double d = 30.0;
double a = d * pi;
System.out.println(a);
}
}
94.24777960769379
3.141592653589793×30.0=94.25777960769379
浮點數計算的精度誤差問題
從上面的兩個例子均可以看出,使用浮點數進行計算時,超過必定範圍是出現偏差。緣由是超出精度範圍,沒法精確計算。
float和double的精度是由尾數的位數來決定的。浮點數在內存中是按科學計數法來存儲的,其整數部分始終是一個隱含着的「1」,因爲它是不變的,故不能對精度形成影響。
float:2^23 = 8388608,一共七位,這意味着最多能有7位有效數字,但絕對能保證的爲6位,也即float的精度爲6~7位有效數字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度爲15~16位。
四則運算誤差
#include <stdio.h>
int main() {
printf("%15.15f\n", 0.05+0.01);
printf("%15.15f\n", 1.0-0.42);
printf("%15.15f\n", 4.015*100);
printf("%15.15f\n", 123.3/100);
return 0;
}
結果:
C:\prj\c>dbl.exe
0.060000000000000
0.580000000000000
401.499999999999940
1.233000000000000
C:\prj\c>
package javay.test.java;
public class Testb {
public static void main(String[] args) {
System.out.println(0.05+0.01);
System.out.println(1.0-0.42);
System.out.println(4.015*100);
System.out.println(123.3/100);
}
}
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999
解決浮點數精確計算有誤差的方法
因爲浮點數的存儲形式帶來的偏差是沒法避免和解決的。在Java中一般使用BigDecimal來解決精確的計算的。
BigDecimal(double val)
BigDecimal(String val)
BigDecimal用哪一個構造函數呢?
很簡單,double的存儲就不精確的,因此儘可能使用String的那個。
BigDecimal的比較
package javay.test.java;
import java.math.BigDecimal;
public class TestBigDecimal {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("15.0");
BigDecimal b = new BigDecimal("15.00");
BigDecimal c = new BigDecimal(15);
BigDecimal d = new BigDecimal("15.0");
System.out.println("(a == b)=" + (a == b));
System.out.println("a.equals(b)=" + a.equals(b));
System.out.println("a.compareTo(b)=" + a.compareTo(b));
System.out.println("(a == c)=" + (a == c));
System.out.println("a.equals(c)=" + a.equals(c));
System.out.println("a.compareTo(c)=" + a.compareTo(c));
System.out.println("(a == d)=" + (a == d));
System.out.println("a.equals(d)=" + a.equals(d));
System.out.println("a.compareTo(d)=" + a.compareTo(d));
}
}
(a == b)=false
a.equals(b)=false
a.compareTo(b)=0
(a == c)=false
a.equals(c)=false
a.compareTo(c)=0
(a == d)=false
a.equals(d)=true
a.compareTo(d)=0
看!你看到了什麼?在某些時候compareTo是優秀的。
(完)