【講古堂】浮點數

講古堂浮點數java

dubenju@126.com 2015/12/19數據庫

 

衆所周知,因爲用高低電平的電路很容易實現二進制,因此在計算機中廣泛採用二進制來存儲數據。對應的二進制的位用Bit來表示。1字節=8Bits,若是考慮符號的話,那麼一個字節能存儲的數值範圍是-128127。只能是整數,不能是小數。那麼小數怎麼辦呢?函數

 

小數的話,必定要有小數點的。若是把小數點固定在一個不變的位置的話,就成了定點數。好比Oracle數據庫的NUMBER(4, 2)則能存儲17.250.50這樣的數。spa

 

定點數(Fixed Point Number).net

所謂定點數,即約定數據的小數點位置是固定不變的。一般將定點數據表示成純小數或純整數。爲了將數表示成純小數,一般把小數點固定在數值部分的最高位以前;而爲了把數表示成純整數,則把小數點固定在數值部分的最後面。設計

對純小數進行運算時,要用適當的比例因子進行折算,以避免產生溢出,或過多損失精度。blog

假設用一個n位字來表示一個定點數x= x0 x1 x2 … xn-1,其中一位x0用來表示數的符號位,其他位數表明它的量值。爲了對全部n位進行統一處理,符號位x0一般放在最左位置,並用數值01分別表明正號和負號。對於任意定點數x= x0 x1 x2 … xn-1,若是x表示的是純小數,那麼小數點位於x0x1之間,數的表示範圍爲:0≤||≤1-2-(n-1);若是x 表示的是純整數,則小數點位於最低位xn-1的右邊,數的表示範圍爲:0≤||≤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.451.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^-2710^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年,IEEEInstitute of Electrical and Electronics Engineers,美國電氣和電子工程師協會)提出了IEEE-754標準,並以此做爲浮點數表示格式的統一標準。目前,幾乎全部的計算機都支持該標準,從而大大改善了科學應用程序的可移植性。

IEEE標準從邏輯上採用一個三元組{S, E, M}來表示一個數N,它規定基數爲2,符號位S01分別表示正和負,尾數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位):符號位(S1位;階碼(E)8位,階碼的偏移量爲1277FH);尾數(M23位,用小數表示,小數點放在尾數域的最前面;

 

雙精度格式(64位):符號位(S1位;階碼(E)11位,階碼的偏移量爲10233FFH);尾數(M52位,用小數表示,小數點放在尾數域的最前面。

 

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

 

浮點數算的精度問題

從上面的兩個例子均可以看出,使用浮點數進行計算時,超過必定範圍是出現偏差。緣由超出精度範,沒法精確算。

floatdouble的精度是由尾數的位數來決定的。浮點數在內存中是按科學數法來存的,其整數部分始是一個含着的「1」,因爲它是不的,故不能精度形成影響。

 

float2^23 = 8388608,一共七位,意味着最多能有7位有效數字,但絕對能保6位,也即float的精度6~7位有效數字;

double2^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是優秀的。

(完)

相關文章
相關標籤/搜索