從“四舍五入”談起

起源

前幾天改了同事遺留的一個四舍五入的缺陷钧椰,頗有探索的價值。問題簡化如下:

總邀約人數(shù)11人符欠,已完成6人嫡霞,邀約完成率應(yīng)顯示為55%,實際顯示54%

廢話不多說翻代碼:

 C#:
 int CalcPercentageInt(int a, int b)
 {
     if (b == 0 || a == 0) return 0;
     int result = 100 * a / b;
     return result;
 }

簡短的一句代碼有諸多想法:

1.四舍五入的代碼應(yīng)該放到前端去做希柿,能稍微減輕服務(wù)器壓力诊沪。

  1. int做輸入?yún)?shù),要考慮精度損失問題曾撤。
    3.輸出參數(shù)為什么是int, 又要考慮精度問題端姚,直接輸出格式化的好的百分比不就行了

后面又有同事改造成如下:

 int CalcPercentageInt(int a, int b)
 {
     if (b == 0 || a == 0) return 0;
     string result = ((double)a / b).ToString("f2"); ;
     return (int)(Convert.ToDouble(result) * 100);
 }

執(zhí)行以上運算結(jié)果是55,是對的盾戴。然后他就提測了,結(jié)果被打回來了兵多。

總邀約人數(shù)7人尖啡,已完成2人,邀約完成率應(yīng)顯示為29%剩膘,實際顯示28%

然后缺陷轉(zhuǎn)到我名下了衅斩,我改為如下版本:

int CalcPercentageIntBak(double a, double b)
 {
     if (b == 0 || a == 0) return 0;
     var result = Math.Round(a/b,2)*100 ;// 這一步 2/7 得到28.999999999999996
     return (int)result;
 }

這樣也不行,改為decimal可以了

int CalcPercentageInt(decimal a, decimal b)
 {
     if (b == 0 || a == 0) return 0;
     var result = Math.Round(a/b,2)*100 ;// 這一步 2/7 得到29.00M
     return (int)result;
 }

經(jīng)單元測試如下都通過:

 int m1 = CalcPercentageInt(2, 7);
 Assert.True(m1 == 29);
 ... 3到5省略
 m1 = CalcPercentageInt(6, 7);
 Assert.True(m1 == 86);
 m1 = CalcPercentageInt(2, 9);
 ... 3到7省略
 m1 = CalcPercentageInt(8, 9);
 Assert.True(m1 == 89);
 m1 = CalcPercentageInt(2, 11);
 Assert.True(m1 == 18);
 ... 3到9省略
 m1 = CalcPercentageInt(10, 11);
 Assert.True(m1 == 91);

雖然解決了問題怠褐,最合理的方案也許還是放到前端去計算,或者直接toString("f2")都比返回int到前端好些畏梆。用chrome演示如下(多么的省事!)

>((2/7)*100).toFixed('0')
"29"
>((3/7)*100).toFixed('0')
"43"
>((6/11)*100).toFixed('0')
"55"

再來對比下如下寫法:

//javascript
> var a=6,b=11;
  parseInt(a)/parseInt(b)
輸出:0.5454545454545454

> var a=2,b=7;
  parseInt(a)/parseInt(b)
輸出: 0.2857142857142857

//C#
var a=2;
var b=11;
var m1=a/b; 輸出:0
var m2=(double)a/b;輸出:0.2857142857142857
var m3=(decimal)a/b;輸出:0.2857142857142857142857142857
a=2;b=7;
(float)2/7;輸出:0.2857143
(double)2/7;輸出:0.2857142857142857
(decimal)2/7;輸出:0.2857142857142857142857142857
Math.Round((double)a/b,2) 輸出:0.29
Math.Round((double)a/b,2)*100奈懒,輸出:28.999999999999996 (上一步是0.29 奠涌,*100后變成了28.999999999999996,佛系吧)
Math.Round((decimal)2/7,2),輸出:0.29M
Math.Round((decimal)2/7,2)*100,輸出29.00(可以對比double,為什么磷杏?)

從上面的對比可以知道int型除法小數(shù)精度問題C#和javascript采取的策略是不一樣的溜畅,以及C#中double和decimal的處理方式也有所不同。
我們再來看下C++:

//gcc version 9.2.0 (MinGW.org GCC Build-2)(2019年8月5號發(fā)布)
int main()
{
  int a = 2;
  int b = 7;
  cout << "a/b的結(jié)果是:" << a / b << endl;
  //輸出:0
  cout << "(double)a/b 的結(jié)果是:" << (double)a / b << endl;
  //輸出:0.285714(注意這個小數(shù)位長度只有6個)
  //cout << "(double)a / b" << (decimal)a / b;//C++默認(rèn)沒有decimal類型极祸?
}

針對這些亂象慈格,我們要從一個浮點數(shù)的標(biāo)準(zhǔn)IEEE754說起。

IEEE754

IEEE二進(jìn)制浮點數(shù)算術(shù)標(biāo)準(zhǔn)(IEEE 754)是20世紀(jì)80年代以來最廣泛使用的浮點數(shù)運算標(biāo)準(zhǔn)遥金,為許多CPU與浮點運算器所采用浴捆。這個標(biāo)準(zhǔn)定義了表示浮點數(shù)的格式(包括負(fù)零-0)與反常值(denormal number)),一些特殊數(shù)值(無窮(Inf)與非數(shù)值(NaN))稿械,以及這些數(shù)值的“浮點數(shù)運算符”选泻;它也指明了四種數(shù)值舍入規(guī)則和五種例外狀況(包括例外發(fā)生的時機與處理方式)。

Ieee754-2019官方鏈接
下載IEEE-754-2019
該標(biāo)準(zhǔn)規(guī)定了計算機編程環(huán)境中二進(jìn)制和十進(jìn)制浮點算術(shù)的交換和算術(shù)格式以及方法。該標(biāo)準(zhǔn)規(guī)定了異常條件及其默認(rèn)處理滔金∩猓可以完全以軟件,完全以硬件或以軟件和硬件的任何組合來實現(xiàn)符合該標(biāo)準(zhǔn)的浮點系統(tǒng)的實現(xiàn)餐茵。對于本標(biāo)準(zhǔn)規(guī)范部分中指定的操作科阎,數(shù)值結(jié)果和例外情況由輸入數(shù)據(jù)的值,操作順序和目標(biāo)格式唯一確定忿族,所有這些操作均在用戶的控制之下锣笨。
相關(guān)標(biāo)準(zhǔn)的其他版本(包含已被取代)有:

IEEE 754-1985-二進(jìn)制浮點算法的IEEE標(biāo)準(zhǔn)
IEEE 854-1987-獨立于基數(shù)的浮點算法的IEEE標(biāo)準(zhǔn)
IEEE 754-2008-浮點算法的IEEE標(biāo)準(zhǔn)
IEEE / ISO / IEC 60559-2020-ISO / IEC / IEEE國際標(biāo)準(zhǔn)-浮點運算

這里有一篇文章IEEE 754格式可以作為參考,解答一下疑惑道批。

問題

選擇一個標(biāo)準(zhǔn)方法用二進(jìn)制數(shù)來表示浮點數(shù)時错英,需要考慮很多事情:

  • 范圍:應(yīng)該能支持很大范圍的正負(fù)數(shù)
  • 精度:你能區(qū)別1.7和1.8之間的區(qū)別么?1.700001和1.700002呢隆豹,你應(yīng)該記住多少小數(shù)位椭岩?
  • 時間效率:您的解決方案是否使快速進(jìn)行比較和算術(shù)運算變得容易?
  • 空間注意:怎么極精確表示3的平方根璃赡,除非需要兆字節(jié)來存儲它
  • 一對一的關(guān)系:如果每個浮點數(shù)只能以一種方式寫入判哥,反之亦然,您的解決方案將簡單得多

IEEE 754 Form的開發(fā)人員最終采用的方法使用科學(xué)符號的思想碉考∷疲科學(xué)記數(shù)法是表達(dá)數(shù)字的標(biāo)準(zhǔn)方法,它使數(shù)字易于閱讀和比較。我們最熟悉的是以10為底的數(shù)字的科學(xué)計數(shù)法侯谁。
您只需要將數(shù)字分為兩部分:值的范圍1<=N<10,冪為10锌仅,例如:

3498523 被寫成 3.498523 ×10^6
? 0.0432 被寫成 -4.32 ×10^{-2}

用二進(jìn)制數(shù)也是相似的思路,需要使用2的冪墙贱。只需將您的數(shù)字分解為大小在范圍內(nèi)的值1 ≤ ? < 2热芹,并且為2的冪。

-6.84 被寫成 -1.71 ×2^2
0.05 被寫成 1.6 × 2^{-5}

要創(chuàng)建位串(二進(jìn)制串惨撇?)剿吻,我們需要用下面的格式:

(-1)^{sign bit}(1+fraction)x2^{exponent - bias}

我們可以從中獲得三個關(guān)鍵信息:

  • 第一部分:sign/符號,如果符號位為0串纺,則代表正數(shù)丽旅,(-1)^0=1; 如果符號位為1,代表負(fù)數(shù)纺棺,$(-1)^0=-1; 否則為0榄笙;
  • 第二部分:fraction/mantissa尾數(shù)我們總是把括號里的數(shù)字算作(1+某個分?jǐn)?shù))。因為我們知道1在那里祷蝌,唯一重要的是分?jǐn)?shù)茅撞,我們將把它寫成二進(jìn)制字符串。
    如果我們需要將二進(jìn)制值轉(zhuǎn)換回以10為基數(shù)的值,我們只需將每個數(shù)字乘以其位值米丘,如以下示例所示:

0.1_{binary}=2^{-1}=0.5
0.01_{binary}=2^{-2}=0.25
0.101_{binary}=2^{-1}+2^{-3}=0.625

  • 第三部分指數(shù)/階 上一步獲得的2的冪只是一個整數(shù)剑令。注意,該整數(shù)可以是正數(shù)或負(fù)數(shù)拄查,分別取決于原始值是大還是小吁津。我們需要存儲該指數(shù)-但是,使用兩者的補碼(帶符號的值的常用表示形式)會使這些值的比較更加困難堕扶。這樣碍脏,我們將一個稱為bias(偏差)的常數(shù)添加到指數(shù)中。通過在存儲指數(shù)之前對其進(jìn)行偏置稍算,我們將其置于更適合比較的無符號范圍內(nèi)典尾。

對于單精度浮點,將-127到+ 127范圍內(nèi)的指數(shù)加上127以得到1到254范圍內(nèi)的值(0和255具有特殊含義)糊探,從而對指數(shù)產(chǎn)生偏倚钾埂。

對于雙精度,將1022到+1023范圍內(nèi)的指數(shù)加1023來獲得1到2046范圍內(nèi)的值(0和2047具有特殊含義)科平,從而對其產(chǎn)生偏差褥紫。

偏差和2的冪的和是實際上進(jìn)入IEEE 754字符串的指數(shù)。請記住匠抗,指數(shù)=冪+偏差故源。(或者污抬,冪=指數(shù)偏差)汞贸。該指數(shù)本身必須最終以二進(jìn)制形式表示-但考慮到加上偏差后我們有一個正整數(shù),則現(xiàn)在可以按常規(guī)方式完成此操作印机。

計算這些二進(jìn)制值后矢腻,可以將它們放入32位或64位字段中。這些數(shù)字的排列方式如下:
image.png

通過以這種方式排列字段射赛,以使符號位位于最高有效位位置多柑,偏斜指數(shù)位于中間,然后尾數(shù)位于最低有效位-結(jié)果值實際上將正確排序以進(jìn)行比較楣责,無論是否它被解釋為浮點數(shù)或整數(shù)值竣灌。這樣可以使用定點硬件對浮點數(shù)進(jìn)行高速比較。

有一些特殊情況:


  • 符號位= 0; 有偏指數(shù)(階碼)=全部0位; 分?jǐn)?shù)=全部0 位;-0和+0是不同的值秆麸,盡管它們相等

  • 正負(fù)無窮大(不知是否理解對初嘹?)
    符號位=0 ,有偏指數(shù)(階碼)=全部1個位沮趣, 分?jǐn)?shù)=全部0 位屯烦,表示正無窮大;
    符號位=1,有偏指數(shù)(階碼)=全部1個位驻龟,分?jǐn)?shù)=全部0 位温眉,為負(fù)無窮大;

  • NaN(非數(shù)字)
    值NAN用于表示錯誤值翁狐。當(dāng)指數(shù)字段為全零且?guī)Я惴栁换蛭矓?shù)不是1后跟零的尾數(shù)時类溢,將表示此值。這是一個特殊值谴蔑,可用于表示尚不包含值的變量豌骏。


    image.png

    image.png

    image.png

    image.png

Float,Double ,Decimal 有何區(qū)別?

Decimal隐锭,Double和Float變量類型在存儲值方面有所不同窃躲。精度是主要區(qū)別,其中float是單精度(32位)浮點數(shù)據(jù)類型钦睡,double是雙精度(64位)浮點數(shù)據(jù)類型蒂窒,而Decimals(十進(jìn)制)是128位浮點數(shù)據(jù)類型。
float/single -32位(7位數(shù)字)
double-64位(15-16位)
decimal-128位(28-29位有效數(shù)字)
主要區(qū)別在于Floats和Doubles是二進(jìn)制浮點類型荞怒,而Decimal將值存儲為浮點小數(shù)點類型洒琢。因此,Decimal位數(shù)具有更高的精度褐桌,通常用于要求高度準(zhǔn)確性的貨幣(金融)應(yīng)用程序中衰抑。但是在性能方面,Decimal比雙精度和浮點型慢荧嵌。

Decimal可以100%準(zhǔn)確地表示十進(jìn)制格式精度范圍內(nèi)的任何數(shù)字呛踊,而Float和Double不能準(zhǔn)確表示所有數(shù)字,即使數(shù)字在其各自格式精度范圍內(nèi)啦撮。

將IEEE 754浮點轉(zhuǎn)換為二進(jìn)制
示例:轉(zhuǎn)換為浮點型
Floating Point Representation
ieee-standard-754-floating-point-numbers
Decimal vs Double vs Float

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谭网,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子赃春,更是在濱河造成了極大的恐慌愉择,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件织中,死亡現(xiàn)場離奇詭異锥涕,居然都是意外死亡,警方通過查閱死者的電腦和手機狭吼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門层坠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搏嗡,你說我怎么就攤上這事窿春±唬” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵旧乞,是天一觀的道長蔚润。 經(jīng)常有香客問我,道長尺栖,這世上最難降的妖魔是什么嫡纠? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮延赌,結(jié)果婚禮上除盏,老公的妹妹穿的比我還像新娘。我一直安慰自己挫以,他們只是感情好者蠕,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掐松,像睡著了一般踱侣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上大磺,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天抡句,我揣著相機與錄音,去河邊找鬼杠愧。 笑死待榔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的流济。 我是一名探鬼主播锐锣,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼袭灯!你這毒婦竟也來了刺下?” 一聲冷哼從身側(cè)響起绑嘹,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤稽荧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后工腋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姨丈,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年擅腰,在試婚紗的時候發(fā)現(xiàn)自己被綠了蟋恬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡趁冈,死狀恐怖歼争,靈堂內(nèi)的尸體忽然破棺而出拜马,到底是詐尸還是另有隱情,我是刑警寧澤沐绒,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布俩莽,位于F島的核電站,受9級特大地震影響乔遮,放射性物質(zhì)發(fā)生泄漏扮超。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一蹋肮、第九天 我趴在偏房一處隱蔽的房頂上張望出刷。 院中可真熱鬧,春花似錦坯辩、人聲如沸馁龟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屁柏。三九已至,卻和暖如春有送,著一層夾襖步出監(jiān)牢的瞬間淌喻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工雀摘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留裸删,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓阵赠,卻偏偏與公主長得像涯塔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子清蚀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容