2014年2月10日 星期一

C++ float inaccuracy 浮點數誤差

Q1:
因為 8 個 bits,每個 bit 都只能存放 0/1 ,這只是簡單的排列組合問題,一個 bit 有 2 個狀態,8個 bits 就有 2*2*2*2*2*2*2*2 = 2^8 = 256 種狀態。於是若用 8bits 表示一個 "無號數" 的話,它的範圍是 0~255,256 種狀態。然而若用 8 bits 表示一個 "有號數" 的話,我們還要考慮正負號問題,於是把它切半,256/2 = 128,範圍是 -128 ~ +127,包含 0 總共 256 個。SO: 

typedef char i8;                   // 8bits 有號數
typedef unsigned char u8;    // 8bits 無號數
typedef short i16 ;              // 16 bits 有號數
typedef unsigned short u16; // 16 bits 無號數
typedef int i32 ;                  // 32 bits 有號數
typedef unsigned int u32 ;    // 32 bits 無號數
typedef long long i64;     // 64 bits 有號數
typedef unsigned long long u64;     // 64 bits 無號數

Q2: 
不論是單精度還是雙精度,它可以表示的小數還是有限的。假設現在我把 0.8 存成單精度,由於 0.8 轉成小數會是 0.11001100110011001100... (1100 一直 loop 下去),最後存成單精度只能存那 23 bits 的小數,最後再取出來的結果可能是 0.79999998 之類的,這個就叫做捨位誤差。至於 0.2 存的話也是存成 0.001000100010001...(0001 loop 下去) 所以如果寫出這樣的程式碼是非常危險 


解決方式1
用更高精度處理 , 原本使用 float  ,  改用 double . 
缺點: 影響計算速度 , 但是如果是很簡易計算則無仿。

解決方式2
输入后,十进制数变2进制。十进制小数 化 2进制小数 常常 化不尽,所以用 大于小于来判断float型是否正好大于小于某数值是困难的。简单办法是用字符串方法读入,直接判断字符串。
float i;
char str[32];
int j,L;
scanf("%s",str);
sscanf(str,"%f",&i);
L = strlen(str);
然后查找小数点,for (j=0;j<L;j++) if (str[j]=='.') { 有小数点,记录下j的位置
。。。  };
然后从最小的一位 str[L-1] 起循环,找到第一个不是 0 的位置。 if (str[k] !='0'){....}
算出位数。
解決方式3
偵測一個浮點數是否為2.34, 那麼便是偵測數值是否與2.34"夠接近":

fabs(x-2.34) <= 0.000001 (看你想要的精準度)

解決方式4
//四捨五入法 Dev-C++
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <cmath>
// 
using namespace std;
//
// Rounding float or double
//
// input float, output float
// 
float Round(const float &number, const int num_digits)
{
    float doComplete5i, doComplete5(number * powf(10.0f, (float) (num_digits + 1)));
    
    if(number < 0.0f)
     doComplete5 -= 5.0f;
    elsedoComplete5 += 5.0f;
    // 
    doComplete5 /= 10.0f;
    modff(doComplete5, &doComplete5i);
// cout<<"Float!"<<endl;
    return doComplete5i / powf(10.0f, (float) num_digits);
}
// 
// input double, output double
// 
double Round(double doValue, int nPrecision)
{
    static const double doBase = 10.0;
    double doComplete5, doComplete5i;
    
    doComplete5 = doValue * pow(doBase, (double) (nPrecision + 1));
    if(doValue < 0.0)
     doComplete5 -= 5.0;
    elsedoComplete5 += 5.0;
    doComplete5 /= doBase;
    modf(doComplete5, &doComplete5i);
// cout<<"double!"<<endl;
    return doComplete5i / pow(doBase, (double) nPrecision);
}
// 
// main program
// 
int main(int argc, char *argv[])
{
// 
// setprecision() 在Dev-C++是一個沒有確定規矩的round法
// 要搭配自己的round法運作! 
// 
double d1=3.1445, d2=-2.335e-3;
float f1=d1, f2=d2;
int i1=3, i2=5, i3=3;

cout<<"C++ default double d1= "<<fixed<<setprecision(i1)<<d1<<endl;
cout<<"C++ default double d2= "<<fixed<<setprecision(i2)<<d2<<endl;
cout<<"---------"<<endl;
cout<<"C++ default float f1= "<<fixed<<setprecision(i1)<<f1<<endl;
cout<<"C++ default float f2= "<<fixed<<setprecision(i2)<<f2<<endl;
cout<<"--------- --------- ---------"<<endl;
//
// 四捨五入 + setprecision()
//
//************************************************************************** 
// 
cout<<"Round d1= "<<fixed<<setprecision(i3)<<Round(d1,i1)<<endl;
cout<<"Round d2= "<<fixed<<setprecision(i2)<<Round(d2,i2)<<endl;
//**************************************************************************
cout<<"---------"<<endl;
cout<<"Round d1= "<<fixed<<setprecision(i3)<<Round(f1,i1)<<endl;
cout<<"Round d2= "<<fixed<<setprecision(i2)<<Round(f2,i2)<<endl;
cout<<"--------- --------- ---------"<<endl;
//
    system("PAUSE");
    
    return 0;
}
參考連結: http://www.programmer-club.com/ShowSameTitleN/c/32164.html

沒有留言:

張貼留言