續(xù)上期《如何操作數(shù)據(jù)》本文和大家一起學(xué)習(xí)小數(shù)的計(jì)算
違反直覺(jué)的事實(shí)
計(jì)算機(jī)之所以叫"計(jì)算"機(jī)就是因?yàn)榘l(fā)明它主要是用來(lái)計(jì)算的辆沦,"計(jì)算"當(dāng)然是它的特長(zhǎng),在大家的印象中挤土,計(jì)算一定是非常準(zhǔn)確的琴庵。但實(shí)際上,即使在一些非逞雒溃基本的小數(shù)運(yùn)算中迷殿,計(jì)算的結(jié)果也是不精確的。
比如:
float f = 0.1f*0.1f; System.out.println(f);
這個(gè)結(jié)果看上去筒占,不言而喻贪庙,應(yīng)該是0.01,但實(shí)際上翰苫,屏幕輸出卻是0.010000001止邮,后面多了個(gè)1。
看上去這么簡(jiǎn)單的運(yùn)算奏窑,計(jì)算機(jī)怎么會(huì)出錯(cuò)了呢导披?
簡(jiǎn)要答案
實(shí)際上,不是運(yùn)算本身會(huì)出錯(cuò)埃唯,而是計(jì)算機(jī)根本就不能精確的表示很多數(shù)撩匕,比如0.1這個(gè)數(shù)。
計(jì)算機(jī)是用一種二進(jìn)制格式存儲(chǔ)小數(shù)的墨叛,這個(gè)二進(jìn)制格式不能精確表示0.1止毕,它只能表示一個(gè)非常接近0.1但又不等于0.1的一個(gè)數(shù)。
數(shù)字都不能精確表示漠趁,在不精確數(shù)字上的運(yùn)算結(jié)果不精確也就不足為奇了扁凛。
0.1怎么會(huì)不能精確表示呢?在十進(jìn)制的世界里是可以的闯传,但在二進(jìn)制的世界里不行谨朝。在說(shuō)二進(jìn)制之前,我們先來(lái)看下熟悉的十進(jìn)制。
實(shí)際上字币,十進(jìn)制也只能表示那些可以表述為10的多少次方和的數(shù)则披,比如12.345,實(shí)際上表示的:110+21+30.1+40.01+5*0.001洗出,與整數(shù)的表示類(lèi)似士复,小數(shù)點(diǎn)后面的每個(gè)位置也都有一個(gè)位權(quán),從左到右共苛,依次為 0.1,0.01,0.001,...即10^(-1), 10^(-2), 10^(-3)判没。
很多數(shù),十進(jìn)制也是不能精確表示的隅茎,比如1/3, 保留三位小數(shù)的話(huà)澄峰,十進(jìn)制表示是0.333,但無(wú)論后面保留多少位小數(shù)辟犀,都是不精確的俏竞,用0.333進(jìn)行運(yùn)算,比如乘以3堂竟,期望結(jié)果是1魂毁,但實(shí)際上卻是0.999。
二進(jìn)制是類(lèi)似的出嘹,但二進(jìn)制只能表示哪些可以表述為2的多少次方和的數(shù)席楚,來(lái)看下2的次方的一些例子:
可以精確表示為2的某次方之和的數(shù)可以精確表示,其他數(shù)則不能精確表示税稼。
為什么一定要用二進(jìn)制呢烦秩?
為什么就不能用我們熟悉的十進(jìn)制呢?在最最底層郎仆,計(jì)算機(jī)使用的電子元器件只能表示兩個(gè)狀態(tài)只祠,通常是低壓和高壓,對(duì)應(yīng)0和1扰肌,使用二進(jìn)制容易基于這些電子器件構(gòu)建硬件設(shè)備和進(jìn)行運(yùn)算抛寝。如果非要使用十進(jìn)制,則這些硬件就會(huì)復(fù)雜很多曙旭,并且效率低下盗舰。
有什么有的小數(shù)計(jì)算是準(zhǔn)確的
如果你編寫(xiě)程序進(jìn)行試驗(yàn),你會(huì)發(fā)現(xiàn)有的計(jì)算結(jié)果是準(zhǔn)確的桂躏。比如钻趋,我用Java寫(xiě):
System.out.println(0.1f+0.1f); System.out.println(0.1f*0.1f);
第一行輸出0.2,第二行輸出0.010000001沼头。按照上面的說(shuō)法爷绘,第一行的結(jié)果應(yīng)該也不對(duì)啊进倍?
其實(shí)土至,這只是Java語(yǔ)言給我們?cè)斐傻募傧螅?jì)算結(jié)果其實(shí)也是不精確的猾昆,但是由于結(jié)果和0.2足夠接近陶因,在輸出的時(shí)候,Java選擇了輸出0.2這個(gè)看上去非常精簡(jiǎn)的數(shù)字垂蜗,而不是一個(gè)中間有很多0的小數(shù)楷扬。
在誤差足夠小的時(shí)候,結(jié)果看上去是精確的贴见,但不精確其實(shí)才是常態(tài)烘苹。
怎么處理計(jì)算不精確
計(jì)算不精確,怎么辦呢片部?大部分情況下镣衡,我們不需要那么高的精度,可以四舍五入档悠,或者在輸出的時(shí)候只保留固定個(gè)數(shù)的小數(shù)位廊鸥。
如果真的需要比較高的精度,一種方法是將小數(shù)轉(zhuǎn)化為整數(shù)進(jìn)行運(yùn)算辖所,運(yùn)算結(jié)束后再轉(zhuǎn)化為小數(shù)惰说,另外的方法一般是使用十進(jìn)制的數(shù)據(jù)類(lèi)型,這個(gè)沒(méi)有統(tǒng)一的規(guī)范缘回,在Java中是BigDecimal吆视,運(yùn)算更準(zhǔn)確,但效率比較低切诀,本節(jié)就不詳細(xì)說(shuō)了揩环。
二進(jìn)制表示
我們之前一直在用"小數(shù)"這個(gè)詞表示float和double類(lèi)型,其實(shí)幅虑,這是不嚴(yán)謹(jǐn)?shù)模?小數(shù)"是在數(shù)學(xué)中用的詞丰滑,在計(jì)算機(jī)中,我們一般說(shuō)的是"浮點(diǎn)數(shù)"倒庵。float和double被稱(chēng)為浮點(diǎn)數(shù)據(jù)類(lèi)型褒墨,小數(shù)運(yùn)算被稱(chēng)為浮點(diǎn)運(yùn)算。
為什么要叫浮點(diǎn)數(shù)呢擎宝?這是由于小數(shù)的二進(jìn)制表示中郁妈,表示那個(gè)小數(shù)點(diǎn)的時(shí)候,點(diǎn)不是固定的绍申,而是浮動(dòng)的噩咪。
我們還是用10進(jìn)制類(lèi)比顾彰,10進(jìn)制有科學(xué)表示法,比如123.45這個(gè)數(shù)胃碾,直接這么寫(xiě)涨享,就是固定表示法,如果用科學(xué)表示法仆百,在小數(shù)點(diǎn)前只保留一位數(shù)字厕隧,可以寫(xiě)為1.2345E2即1.2345*(10^2),即在科學(xué)表示法中俄周,小數(shù)點(diǎn)向左浮動(dòng)了兩位吁讨。
二進(jìn)制中為表示小數(shù),也采用類(lèi)似的科學(xué)表示法峦朗,形如 m*(2^e)建丧。m稱(chēng)為尾數(shù),e稱(chēng)為指數(shù)波势。指數(shù)可以為真茶鹃,也可以為負(fù),負(fù)的指數(shù)表示哪些接近0的比較小的數(shù)艰亮。在二進(jìn)制中闭翩,單獨(dú)表示尾數(shù)部分和指數(shù)部分,另外還有一個(gè)符號(hào)位表示正負(fù)迄埃。
幾乎所有的硬件和編程語(yǔ)言表示小數(shù)的二進(jìn)制格式都是一樣的疗韵,這種格式是一個(gè)標(biāo)準(zhǔn),叫做IEEE 754標(biāo)準(zhǔn)侄非,它定義了兩種格式蕉汪,一種是32位的,對(duì)應(yīng)于Java的float逞怨,另一種是64位的者疤,對(duì)應(yīng)于Java的double。
32位格式中叠赦,1位表示符號(hào)驹马,23位表示尾數(shù),8位表示指數(shù)除秀。64位格式中糯累,1位表示符號(hào),52位表示尾數(shù)册踩,11位表示指數(shù)泳姐。
在兩種格式中,除了表示正常的數(shù)暂吉,標(biāo)準(zhǔn)還規(guī)定了一些特殊的二進(jìn)制形式表示一些特殊的值胖秒,比如負(fù)無(wú)窮缎患,正無(wú)窮,0阎肝,NaN (非數(shù)值较锡,比如0乘以無(wú)窮大)。
IEEE 754標(biāo)準(zhǔn)有一些復(fù)雜的細(xì)節(jié)盗痒,初次看上去難以理解,對(duì)于日常應(yīng)用也不常用低散,本文就不介紹了俯邓。
如果你想查看浮點(diǎn)數(shù)的具體二進(jìn)制形式,在Java中熔号,可以使用如下代碼:
Integer.toBinaryString(Float.floatToIntBits(value)) Long.toBinaryString(Double.doubleToLongBits(value));
總結(jié)
- 小數(shù)計(jì)算為什么會(huì)出錯(cuò)呢稽鞭?理由就是:很多小數(shù)計(jì)算機(jī)中不能精確表示。
- 計(jì)算機(jī)的基本思維是二進(jìn)制的引镊,所以朦蕴,意料之外,情理之中!
整數(shù)的二進(jìn)制和小數(shù)的二進(jìn)制我們都做了一定的分析學(xué)習(xí)弟头,那字符和文本呢吩抓?編碼是怎么回事?亂碼又是什么原因赴恨?下期繼續(xù)更新……關(guān)注
今天正值正月十五中國(guó)傳統(tǒng)節(jié)日元宵節(jié)疹娶,筆者在這里祝愿大家身體健康、萬(wàn)事如意伦连,大家元宵節(jié)快樂(lè)雨饺!