起源
前幾天改了同事遺留的一個四舍五入的缺陷钧椰,頗有探索的價值。問題簡化如下:
總邀約人數(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ù)器壓力诊沪。
- 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 被寫成
? 0.0432 被寫成
用二進(jìn)制數(shù)也是相似的思路,需要使用2的冪墙贱。只需將您的數(shù)字分解為大小在范圍內(nèi)的值1 ≤ ? < 2热芹,并且為2的冪。
-6.84 被寫成
0.05 被寫成
要創(chuàng)建位串(二進(jìn)制串惨撇?)剿吻,我們需要用下面的格式:
我們可以從中獲得三個關(guān)鍵信息:
- 第一部分:sign/符號,如果符號位為0串纺,則代表正數(shù)丽旅,=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.5
=0.25
=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ù)字的排列方式如下:通過以這種方式排列字段射赛,以使符號位位于最高有效位位置多柑,偏斜指數(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ù)時类溢,將表示此值。這是一個特殊值谴蔑,可用于表示尚不包含值的變量豌骏。
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