程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 100個0.1相加等於多少?,1000.1相加

100個0.1相加等於多少?,1000.1相加

編輯:關於C語言

100個0.1相加等於多少?,1000.1相加


一、前言

  在大家的認知過程中可能會認為計算機是不會出現計算錯誤的,但是實際上,依然存在程序運行後無法得到正確數值的情況。其中,最經典的就是小數運算。(做金融的一定要小心!!!)

二、引入

  在我們的世界裡面,100個0.1相加就是10,這個是沒有疑問的。但是當我們用C語言如下的程序來計算的時候,結果並非是10(不同語言計算的結果可能不同,這裡主要說C)。

  首先是一段計算代碼:

#include <stdio.h>

int main(void) { 
      float sum;
        int i;
        sum = 0;
        for (i=0 ;i<100;i++) {
            sum += 0.1;
        }
        printf("%f\n",sum);
}

運行結果如下:

10.000002

計算機通過編譯、鏈接、運行得到的結果是10.000002。程序沒有錯。現在讓我們來看一下具體原因吧。

三、計算機計算結果不正確的原因

  簡單來說,就是無法表示正確的數值,導致計算出來的結果成了近似值。下面進一步剖析一下。

  首先,我們來看一下在計算機世界裡面如何用二進制數表示小數:

  例如把1011.0011這個小數點的二進制數轉成十進制數。(只需將各數位數值和位權相乘,然後將相乘的結果相加)

  也就是:1*2^3+0*2^2+1*2^1+1*2^0+0*2^(-1)+0*2^(-2)+1*2^(-3)+1*2^(-4) = 11.1875。

  了解了二進制表示的小數轉十進制的方法後,計算出錯的原因也就容易理解了。用小數點後4位用二進制表示時的數值范圍為:0.0000~0.1111。因此,對應的十進制結果如下:

從上面的對照表可以看出,0的下一位就是0.625。因此0~0.0625之間的數值計算機無法用小數點後4位數的二進制數表示。因此可以看出0.1無法用4位二進制數表示。就算增加二進制的位數,也無法得到2^(-x) =0.1 這個結果。

  實際上,十進制0.1轉成二進制後,就變成了0.0001100110011……(1100循環)這樣的循環小數。就像1/3是一個道理。因此100各0.1相加不等於10,而是等於近似值。

---------------------------------------------以上就能夠回答標題的原因了---------------------------------------------

四、What is 浮點數?

  其實像剛才那樣的1011.0011這種表現形式完全是紙面上的二進制數表現形式,在計算機內部是無法使用的(計算機內部只是0101001……沒有"."這個概念)。實際上,編程語言提供了雙精度浮點數(double)和單精度浮點數(float)。雙精度浮點數類型用64位、單精度浮點數用32位來表示全體小數。

  浮點數:就是用符號、尾數、基數和指數表示的小數。

  

其中:±表示符號,m表示尾數,n表示基數,e表示指數。實際數據中不考慮基數。因此:

其中:

1、符號部分:1表示負、0表示正或者0。

2、尾數部分用的是:將小數點前面的值固定位1的正則表達式。

3、指數部分:用的是EXCESS系統表現。

  

  先看看尾數部分。對於十進制的0.75。我們有如下的表示方法:

  ①、0.75 = 0.75*10^0

  ②、0.75 = 75*10^(-2)

  ③、0.75 = 0.075*10^1

  十進制的表示正則為:小數點前面是0,小數點後面第一位不是0的規則表示。而對於二進制也是一樣的道理,使用的是:將小數點前面的值固定為1的正則。也就是將二進制數表示的小數左移或右移(邏輯移位)數次後,整數部分的第一位變成1,第二位之後變成0.而且第1位的1在實際數據中不保存。

  例如1011.0011:

  移位變成0001.0110011,確保小數點後23位:0001.01100110000000000000000,僅保留小數點後面完成正則:01100110000000000000000。

  再看看指數部分。EXCESS系統表現:將指數部分表示范圍的中間值設置為0,使得負數不需要用符號來表示。例如當指數部分是8為單精度浮點時,最大值11111111=225的1/2即01111111=127表示0。雙精度類似。

  因此對於單精度浮點數的表現,其表示范圍就是:00000000~11111111也就是-127~128。看下面例子:

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
    float data;
    unsigned long buff;
    int i;
    char n[34];
    //將0.75以單精度浮點數形式存儲在data中
    data = (float)0.75;

    memcpy(&buff,&data,4);
    for (i=33;i>=0;i--) {
        if(i==1 || i==10) {
            n[i] = '-';
        }else {
            if(buff%2==1) {
                n[i] = '1';
            }else {
                n[i]='0';
            }
            buff/=2;
        }
        
    }
    n[33] = '\0';
    printf("%s\n",n);

}

運行結果:

0-01111110-10000000000000000000000

其中01111110是126,EXCESS表示為-1。

小數點前面的第一位是1。因此尾數就是:1.10000000000000000000000也就是1.5。

也就是+1.5*2^(-1) = 0.75。

五、如何避免小數計算出錯導致的問題

  可以將小數替換成整數來計算。然後在縮小相應的倍數。

 

注:

  1、如果有什麼Bug或者說的不對的地方,歡迎大家隨時提建議或者意見。 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved