iOS浮點(diǎn)類(lèi)型精度問(wèn)題的原因與解決辦法
前言
相信不少人(其實(shí)我覺(jué)得應(yīng)該是每個(gè)人)都遇到過(guò)一個(gè)問(wèn)題,那就是當(dāng)服務(wù)端返回的JSON數(shù)據(jù)中出現(xiàn)了小數(shù)時(shí),客戶端用CGFloat去解析時(shí)總是會(huì)出現(xiàn)精度丟失的問(wèn)題,尤其當(dāng)遇到敏感數(shù)據(jù)時(shí),這種精度丟失是完全不能被容忍的,本文會(huì)從簡(jiǎn)單的解決方案和原理出發(fā),一起帶大家回顧一下這個(gè)其實(shí)大家以前都學(xué)過(guò)但是都忘的差不多了的小問(wèn)題。
如何解決浮點(diǎn)型精度問(wèn)題
四舍五入處理
//例如服務(wù)端返回了這么一個(gè)json { "price":1.9 } // 客戶端解析price后并且打印1.9 CGFloat price = model.price NSLog(@"%f",price) 得到的結(jié)果是 1.8999999999999999
這個(gè)時(shí)候呢,作為一個(gè)咸魚(yú)開(kāi)發(fā)者,可以寫(xiě)下如下代碼:
NSLog(@"%.2f",price) 輸出:1.89 //不夠準(zhǔn)確,沒(méi)關(guān)系,還有辦法 NSLog(@"f%@",round(price*10)/10); 輸出1.9
當(dāng)然了,還有apple專(zhuān)門(mén)為精度問(wèn)題提供的 NSDecimalNumber類(lèi)型也可以解決這個(gè)問(wèn)題。NSDecimalNumber的用法非常簡(jiǎn)單。(至于怎么個(gè)簡(jiǎn)單,請(qǐng)自行百度,別問(wèn)我,我不百度也寫(xiě)不出來(lái))
更優(yōu)的解決方案
- 那么問(wèn)題來(lái)了,作為一個(gè)懶到一整年都不愿意寫(xiě)文章的咸魚(yú)中的咸魚(yú),我應(yīng)該選擇哪種方式去解決這個(gè)問(wèn)題呢?
好的,重頭戲來(lái)了,接下來(lái),我們上代碼!
//服務(wù)端必須返回字符串類(lèi)型,如果服務(wù)端沒(méi)有照做: **請(qǐng)帶著錘子和煤氣罐去找后端開(kāi)發(fā)人員解決** @property (nonatomic,copy) NSString *price;
是的,我會(huì)選擇不解決這個(gè)問(wèn)題,把皮球踢出去,這才是一個(gè)合格的開(kāi)發(fā)者該做的事情嘛!
精度丟失的原因
解決浮點(diǎn)精度問(wèn)題是一個(gè)方面,但是如此簡(jiǎn)單的內(nèi)容不足以我水完一整篇文章,那么接下來(lái),我們來(lái)講講為什么好好的數(shù)字解析出來(lái),他咋就不準(zhǔn)確了呢?
可能很多人都能大概講出來(lái)精度丟失是因?yàn)楦↑c(diǎn)數(shù)存儲(chǔ)方式的問(wèn)題,畢竟這玩意兒其實(shí)專(zhuān)業(yè)對(duì)口的筒子們?cè)谛5臅r(shí)候都學(xué)過(guò),但是大家摸摸自己的小腦袋,嘿,是不是和我一樣?全忘光了?
而且鑒于總有些基礎(chǔ)很牛逼的面試官和剛好復(fù)習(xí)過(guò)這部分內(nèi)容的裝逼面試官就是喜歡挑這些小問(wèn)題來(lái)刁難咱們這些老年摸魚(yú)程序員,下面我們就來(lái)復(fù)習(xí)一下這部分的知識(shí)吧。
浮點(diǎn)類(lèi)型的存儲(chǔ)方式
浮點(diǎn)類(lèi)型在計(jì)算機(jī)中的存儲(chǔ)方式是以科學(xué)計(jì)數(shù)法的方式來(lái)存儲(chǔ)的:
例如: 科學(xué)計(jì)數(shù)法表示小數(shù) 90.9 => 9.09 x 10^1 8.3 => 8.3 x 10^0
我們以8.3為例子,要存儲(chǔ)8.3 (8.3 x 10^0), 首先肯定要將8.3轉(zhuǎn)化為2進(jìn)制,8轉(zhuǎn)為二進(jìn)制時(shí)1000,那么0.3呢?
年邁的程序員喲,你是不是猛然發(fā)現(xiàn)居然忘了小數(shù)是如何轉(zhuǎn)化為2進(jìn)制的呀,放下你準(zhǔn)備百度的顫抖的雙手,你想要的,我這里都有!
以0.9為例子,將0.9乘以2,得到的數(shù)字整數(shù)部分為二進(jìn)制的小數(shù)第一位,將結(jié)果部分的小數(shù)部分取出并再乘以2,取整數(shù)部分為第2位,不斷重復(fù)以上操作,直到結(jié)果等于0或者出現(xiàn)循環(huán)為止。
0.9 *2 = 1.8 第1位 1 0.8 *2 = 1.6 第2位 1 0.6 *2 = 1.2 第3位 1 0.2 *2 = 0.4 第4位 0 0.4 *2 = 0.8 第5位 0 0.8 *2 = 1.6 第6位 1 出現(xiàn)循環(huán)了 那么0.9的二進(jìn)制就是 0.1 1100 1100 1100 1100... 其中1100無(wú)限循環(huán)
經(jīng)過(guò)上面一番復(fù)習(xí)我們知道8.3的二進(jìn)制可表示為 1000.0 1001 1001 1001... (1001無(wú)限循環(huán)) 用科學(xué)計(jì)數(shù)法表示則為, 1.000 0 1001 1001 1001 x 2^3
整數(shù)部分 | 指數(shù)部分 | 小數(shù)部分 |
---|---|---|
1 | 3 | .000 0 1001 1001 1001 |
而我們知道float是4(32位)個(gè)字節(jié),在存儲(chǔ)時(shí)他的每個(gè)bit是如下分配的
第31位符號(hào)位 | 23-30位(指數(shù)位) | 0-22位(小數(shù)位) |
---|---|---|
1 | 3 | .000 0 1001 1001 1001 |
有效位數(shù)
可以看到尾數(shù)部分有23位,但是由于是科學(xué)技術(shù)法之后任何一個(gè)數(shù)字都可以用 1.aaa x 2^b 這種形式來(lái)表示,所以1可以省略,(這個(gè)時(shí)候就有人要抬杠了,為啥,那遇到0.aaa x 2^b這種咋辦呀! 答曰:b可以是負(fù)數(shù)?。敲纯偣?3位的數(shù)據(jù)實(shí)際可以表達(dá)的位數(shù)其實(shí)就是24位。
- 24位可以表達(dá)的最大數(shù)字是 16777215 ,如果大于這個(gè)數(shù)就無(wú)法精確表示了。
當(dāng)然大于16777215一不定是完全不能精確表示的,比如16777216,他的二進(jìn)制表達(dá)形式是1后面帶1堆0. 因?yàn)樗?的整數(shù)次冪,那么后面全是0 所以23位能把該數(shù)字存儲(chǔ)下來(lái),如果在23位0后面還出現(xiàn)了1就不行了,以此類(lèi)推,16777215 后面還會(huì)有數(shù)字剛好可以滿足這種2的整數(shù)次冪的條件,也可以正確表達(dá)。
雖然大于16777215的數(shù)字也有部分可以精確表達(dá),但是我們談精度的話肯定就要精確了,那么能精確表達(dá)的數(shù)字就只有0-16777215之間的了,16777215之間我們數(shù)一下,一共是8位,但是由于最高位1開(kāi)頭并不能全部包含,所以說(shuō)精度應(yīng)該是7位有效數(shù)子。
另外提一下浮點(diǎn)數(shù)的表達(dá)范圍,這個(gè)范圍肯定是由指數(shù)來(lái)確定了,具體是多少我就不算了,大家有興趣的可以去算一算。
指數(shù)的存儲(chǔ)方式:移位存儲(chǔ)
可以看到指數(shù)部分一共是給了8個(gè)bit位,由于指數(shù)有正負(fù),那么假設(shè)我們第一位表示符號(hào)位,那么我們可以表示的數(shù)字范圍為 -127 ~ +127
那么能表示的范圍被分為兩部分:
1 000000 0~ 1 111111 1 => -0 到 -127
0 000000 0~ 0 111111 1 => +0 到 127
很明顯,如果這樣會(huì)出現(xiàn)一個(gè) -0 和一個(gè) +0,為了避免這個(gè)問(wèn)題所以出現(xiàn)了移位存儲(chǔ):即如果最高位不用來(lái)表示符號(hào)位,8個(gè)bit 可以存儲(chǔ)的范圍是 0-255,我們重新來(lái)規(guī)劃下兩個(gè)區(qū)間:
- 00000000 - 01111111 => 0-127 減去127則表示-127-0之前的數(shù)字
- 10000000 - 11111111 => 128-256 減去127則表示1-127之前的數(shù)字
這樣一來(lái)規(guī)避了+-0的問(wèn)題, 上文中所說(shuō)的8.3 轉(zhuǎn)化為小數(shù)后的指數(shù)位是3,那么3實(shí)際存儲(chǔ)的時(shí)130,也就是 10000010,我們更新一下實(shí)際存儲(chǔ)的表格
第31位符號(hào)位 | 23-30位(指數(shù)位) | 0-22位(小數(shù)位) |
---|---|---|
1 | 10000010 | .000 0 1001 1001 1001 |
double類(lèi)型
double類(lèi)型是8字節(jié)64位,其表示的位數(shù)和float所不同只有位數(shù)差別,其他都一樣,雙精度表示的位數(shù)如下:
第63位符號(hào)位 | 52-62位(指數(shù)位) | 0-51位(小數(shù)位) |
---|
總結(jié):輸出結(jié)果丟失精度原因
回到最初的1.9這個(gè)數(shù)字打印為1.8999999999999999,現(xiàn)在我們應(yīng)該知道為什么了,因?yàn)?.9轉(zhuǎn)2進(jìn)制時(shí)是個(gè)無(wú)限循環(huán),由于存儲(chǔ)的原因后面的循環(huán)部分被只被截取了23位,還原回來(lái)的十進(jìn)制數(shù)字和原先肯定就不一樣了。 同理,如果小數(shù)點(diǎn)后面是.5這種5的倍數(shù)的小數(shù),打印出來(lái)的小數(shù)一般都會(huì)是正常的,因?yàn)?5轉(zhuǎn)2進(jìn)制時(shí)并不是無(wú)限循環(huán)的。
到此這篇關(guān)于iOS浮點(diǎn)類(lèi)型精度問(wèn)題的原因與解決辦法的文章就介紹到這了,更多相關(guān)iOS浮點(diǎn)類(lèi)型精度問(wèn)題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
iOS實(shí)現(xiàn)相冊(cè)多選圖片上傳功能
這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)相冊(cè)多選圖片上傳功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08iOS 禁止按鈕在一定時(shí)間內(nèi)連續(xù)點(diǎn)擊
本文主要介紹了iOS中禁止按鈕在一定時(shí)間內(nèi)連續(xù)點(diǎn)擊的方法,具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02iOS開(kāi)發(fā)中UISwitch按鈕的使用方法簡(jiǎn)介
這篇文章主要介紹了iOS開(kāi)發(fā)中UISwitch按鈕的使用方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-11-11iOS自動(dòng)進(jìn)行View標(biāo)記的方法詳解
這篇文章主要給大家介紹了關(guān)于iOS自動(dòng)進(jìn)行View標(biāo)記的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位iOS開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04IOS 關(guān)鍵字const 、static、extern詳解
這篇文章主要介紹了IOS 關(guān)鍵字const 、static、extern詳解的相關(guān)資料,這里對(duì)關(guān)鍵字如何使用,及在IOS開(kāi)發(fā)中的意義做了詳解,需要的朋友可以參考下2016-11-11iOS開(kāi)發(fā)--仿新聞首頁(yè)效果WMPageController的使用詳解
這篇文章主要介紹了iOS開(kāi)發(fā)--仿新聞首頁(yè)效果WMPageController的使用詳解,詳解的介紹了iOS開(kāi)發(fā)中第三方庫(kù)WMPageController控件的使用方法,有需要的可以了解下。2016-11-11iOS應(yīng)用開(kāi)發(fā)中使用設(shè)計(jì)模式中的觀察者模式的實(shí)例
這篇文章主要介紹了iOS應(yīng)用開(kāi)發(fā)中使用設(shè)計(jì)模式中的觀察者模式的實(shí)例,包括Cocoa框架使用中的KVO機(jī)制的相關(guān)配合運(yùn)用,代碼為傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-03-03iOS簡(jiǎn)單易用的GCD計(jì)時(shí)器的實(shí)現(xiàn)原理
在日常開(kāi)發(fā)中總會(huì)碰到需要計(jì)時(shí)器的功能,常見(jiàn)的定時(shí)器有NSTimer、GCD、CADisplayLink。網(wǎng)上也有很多的教程介紹三者的區(qū)別,今天主要講的是GCD這種方式使用以及封裝。感興趣的小伙伴們可以參考一下2018-11-11