日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

iOS浮點(diǎn)類型精度問題的原因與解決辦法_IOS

作者:夢回吹角連營 ? 更新時(shí)間: 2022-04-09 編程語言

前言

相信不少人(其實(shí)我覺得應(yīng)該是每個(gè)人)都遇到過一個(gè)問題,那就是當(dāng)服務(wù)端返回的JSON數(shù)據(jù)中出現(xiàn)了小數(shù)時(shí),客戶端用CGFloat去解析時(shí)總是會出現(xiàn)精度丟失的問題,尤其當(dāng)遇到敏感數(shù)據(jù)時(shí),這種精度丟失是完全不能被容忍的,本文會從簡單的解決方案和原理出發(fā),一起帶大家回顧一下這個(gè)其實(shí)大家以前都學(xué)過但是都忘的差不多了的小問題。

如何解決浮點(diǎn)型精度問題

四舍五入處理

//例如服務(wù)端返回了這么一個(gè)json
{
    "price":1.9
}

// 客戶端解析price后并且打印1.9
CGFloat price = model.price
NSLog(@"%f",price)
得到的結(jié)果是 1.8999999999999999

這個(gè)時(shí)候呢,作為一個(gè)咸魚開發(fā)者,可以寫下如下代碼:

NSLog(@"%.2f",price)
輸出:1.89
//不夠準(zhǔn)確,沒關(guān)系,還有辦法
NSLog(@"f%@",round(price*10)/10);
輸出1.9

當(dāng)然了,還有apple專門為精度問題提供的 NSDecimalNumber類型也可以解決這個(gè)問題。NSDecimalNumber的用法非常簡單。(至于怎么個(gè)簡單,請自行百度,別問我,我不百度也寫不出來)

更優(yōu)的解決方案

  • 那么問題來了,作為一個(gè)懶到一整年都不愿意寫文章的咸魚中的咸魚,我應(yīng)該選擇哪種方式去解決這個(gè)問題呢?

好的,重頭戲來了,接下來,我們上代碼!

//服務(wù)端必須返回字符串類型,如果服務(wù)端沒有照做: **請帶著錘子和煤氣罐去找后端開發(fā)人員解決**
@property (nonatomic,copy) NSString *price;

是的,我會選擇不解決這個(gè)問題,把皮球踢出去,這才是一個(gè)合格的開發(fā)者該做的事情嘛!

精度丟失的原因

解決浮點(diǎn)精度問題是一個(gè)方面,但是如此簡單的內(nèi)容不足以我水完一整篇文章,那么接下來,我們來講講為什么好好的數(shù)字解析出來,他咋就不準(zhǔn)確了呢?

可能很多人都能大概講出來精度丟失是因?yàn)楦↑c(diǎn)數(shù)存儲方式的問題,畢竟這玩意兒其實(shí)專業(yè)對口的筒子們在校的時(shí)候都學(xué)過,但是大家摸摸自己的小腦袋,嘿,是不是和我一樣?全忘光了?

而且鑒于總有些基礎(chǔ)很牛逼的面試官和剛好復(fù)習(xí)過這部分內(nèi)容的裝逼面試官就是喜歡挑這些小問題來刁難咱們這些老年摸魚程序員,下面我們就來復(fù)習(xí)一下這部分的知識吧。

浮點(diǎn)類型的存儲方式

浮點(diǎn)類型在計(jì)算機(jī)中的存儲方式是以科學(xué)計(jì)數(shù)法的方式來存儲的:

例如: 科學(xué)計(jì)數(shù)法表示小數(shù)
90.9 => 9.09 x 10^1
8.3  => 8.3 x 10^0

我們以8.3為例子,要存儲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無限循環(huán)

經(jīng)過上面一番復(fù)習(xí)我們知道8.3的二進(jìn)制可表示為 1000.0 1001 1001 1001... (1001無限循環(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é),在存儲時(shí)他的每個(gè)bit是如下分配的

第31位符號位 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 這種形式來表示,所以1可以省略,(這個(gè)時(shí)候就有人要抬杠了,為啥,那遇到0.aaa x 2^b這種咋辦呀! 答曰:b可以是負(fù)數(shù)啊),那么總共23位的數(shù)據(jù)實(shí)際可以表達(dá)的位數(shù)其實(shí)就是24位。

  • 24位可以表達(dá)的最大數(shù)字是 16777215 ,如果大于這個(gè)數(shù)就無法精確表示了。

當(dāng)然大于16777215一不定是完全不能精確表示的,比如16777216,他的二進(jìn)制表達(dá)形式是1后面帶1堆0. 因?yàn)樗?的整數(shù)次冪,那么后面全是0 所以23位能把該數(shù)字存儲下來,如果在23位0后面還出現(xiàn)了1就不行了,以此類推,16777215 后面還會有數(shù)字剛好可以滿足這種2的整數(shù)次冪的條件,也可以正確表達(dá)。

雖然大于16777215的數(shù)字也有部分可以精確表達(dá),但是我們談精度的話肯定就要精確了,那么能精確表達(dá)的數(shù)字就只有0-16777215之間的了,16777215之間我們數(shù)一下,一共是8位,但是由于最高位1開頭并不能全部包含,所以說精度應(yīng)該是7位有效數(shù)子。

另外提一下浮點(diǎn)數(shù)的表達(dá)范圍,這個(gè)范圍肯定是由指數(shù)來確定了,具體是多少我就不算了,大家有興趣的可以去算一算。

指數(shù)的存儲方式:移位存儲

可以看到指數(shù)部分一共是給了8個(gè)bit位,由于指數(shù)有正負(fù),那么假設(shè)我們第一位表示符號位,那么我們可以表示的數(shù)字范圍為 -127 ~ +127

那么能表示的范圍被分為兩部分:

  • 1 000000 0~ 1 111111 1 => -0 到 -127

  • 0 000000 0~ 0 111111 1 => +0 到 127

很明顯,如果這樣會出現(xiàn)一個(gè) -0 和一個(gè) +0,為了避免這個(gè)問題所以出現(xiàn)了移位存儲:即如果最高位不用來表示符號位,8個(gè)bit 可以存儲的范圍是 0-255,我們重新來規(guī)劃下兩個(gè)區(qū)間:

  • 00000000 - 01111111 => 0-127 減去127則表示-127-0之前的數(shù)字
  • 10000000 - 11111111 => 128-256 減去127則表示1-127之前的數(shù)字

這樣一來規(guī)避了+-0的問題, 上文中所說的8.3 轉(zhuǎn)化為小數(shù)后的指數(shù)位是3,那么3實(shí)際存儲的時(shí)130,也就是 10000010,我們更新一下實(shí)際存儲的表格

第31位符號位 23-30位(指數(shù)位) 0-22位(小數(shù)位)
1 10000010 .000 0 1001 1001 1001

double類型

double類型是8字節(jié)64位,其表示的位數(shù)和float所不同只有位數(shù)差別,其他都一樣,雙精度表示的位數(shù)如下:

第63位符號位 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è)無限循環(huán),由于存儲的原因后面的循環(huán)部分被只被截取了23位,還原回來的十進(jìn)制數(shù)字和原先肯定就不一樣了。 同理,如果小數(shù)點(diǎn)后面是.5這種5的倍數(shù)的小數(shù),打印出來的小數(shù)一般都會是正常的,因?yàn)?5轉(zhuǎn)2進(jìn)制時(shí)并不是無限循環(huán)的。

原文鏈接:https://juejin.cn/post/7047767024931438605

欄目分類
最近更新