四舍五入_从四舍五入谈起
起源
前幾天改了同事遺留的一個四舍五入的缺陷,頗有探索的價值。問題簡化如下:
總邀約人數11人,已完成6人,邀約完成率應顯示為55%,實際顯示54%
廢話不多說翻代碼:
C#:int CalcPercentageInt(int a, int b)
{
if (b == 0 || a == 0) return 0;
int result = 100 * a / b;
return result;
}
簡短的一句代碼有諸多想法:
1.四舍五入的代碼應該放到前端去做,能稍微減輕服務器壓力。
int做輸入參數,要考慮精度損失問題。
3.輸出參數為什么是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);
}
執行以上運算結果是55,是對的。然后他就提測了,結果被打回來了。
總邀約人數7人,已完成2人,邀約完成率應顯示為29%,實際顯示28%
然后缺陷轉到我名下了,我改為如下版本:
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;
}
經單元測試如下都通過:
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型除法小數精度問題C#和javascript采取的策略是不一樣的,以及C#中double和decimal的處理方式也有所不同。
我們再來看下C++:
int main()
{
int a = 2;
int b = 7;
cout << "a/b的結果是:" << a / b << endl;
//輸出:0
cout << "(double)a/b 的結果是:" << (double)a / b << endl;
//輸出:0.285714(注意這個小數位長度只有6個)
//cout << "(double)a / b" << (decimal)a / b;//C++默認沒有decimal類型?
}
針對這些亂象,我們要從一個浮點數的標準IEEE754說起。
IEEE754
IEEE二進制浮點數算術標準(IEEE 754)是20世紀80年代以來最廣泛使用的浮點數運算標準,為許多CPU與浮點運算器所采用。這個標準定義了表示浮點數的格式(包括負零-0)與反常值(denormal number)),一些特殊數值(無窮(Inf)與非數值(NaN)),以及這些數值的“浮點數運算符”;它也指明了四種數值舍入規則和五種例外狀況(包括例外發生的時機與處理方式)。
Ieee754-2019官方鏈接
下載IEEE-754-2019
該標準規定了計算機編程環境中二進制和十進制浮點算術的交換和算術格式以及方法。該標準規定了異常條件及其默認處理。可以完全以軟件,完全以硬件或以軟件和硬件的任何組合來實現符合該標準的浮點系統的實現。對于本標準規范部分中指定的操作,數值結果和例外情況由輸入數據的值,操作順序和目標格式唯一確定,所有這些操作均在用戶的控制之下。
相關標準的其他版本(包含已被取代)有:
IEEE 754-1985-二進制浮點算法的IEEE標準
IEEE 854-1987-獨立于基數的浮點算法的IEEE標準
IEEE 754-2008-浮點算法的IEEE標準
IEEE / ISO / IEC 60559-2020-ISO / IEC / IEEE國際標準-浮點運算
這里有一篇文章IEEE 754格式可以作為參考,解答一下疑惑。
問題
選擇一個標準方法用二進制數來表示浮點數時,需要考慮很多事情:
范圍:應該能支持很大范圍的正負數
精度:你能區別1.7和1.8之間的區別么?1.700001和1.700002呢,你應該記住多少小數位?
時間效率:您的解決方案是否使快速進行比較和算術運算變得容易?
空間注意:怎么極精確表示3的平方根,除非需要兆字節來存儲它
一對一的關系:如果每個浮點數只能以一種方式寫入,反之亦然,您的解決方案將簡單得多
IEEE 754 Form的開發人員最終采用的方法使用科學符號的思想??茖W記數法是表達數字的標準方法,它使數字易于閱讀和比較。我們最熟悉的是以10為底的數字的科學計數法。
您只需要將數字分為兩部分:值的范圍1<=N<10,冪為10,例如:
3498523 被寫成?
? 0.0432 被寫成?
用二進制數也是相似的思路,需要使用2的冪。只需將您的數字分解為大小在范圍內的值1 ≤ ? < 2,并且為2的冪。
-6.84 被寫成?
0.05 被寫成?
要創建位串(二進制串?),我們需要用下面的格式:
我們可以從中獲得三個關鍵信息:
第一部分:sign/符號,如果符號位為0,則代表正數,=1; 如果符號位為1,代表負數,$(-1)^0=-1; 否則為0;
第二部分:fraction/mantissa尾數我們總是把括號里的數字算作(1+某個分數)。因為我們知道1在那里,唯一重要的是分數,我們將把它寫成二進制字符串。
如果我們需要將二進制值轉換回以10為基數的值,我們只需將每個數字乘以其位值,如以下示例所示:
=0.5=0.25=0.625
第三部分指數/階?上一步獲得的2的冪只是一個整數。注意,該整數可以是正數或負數,分別取決于原始值是大還是小。我們需要存儲該指數-但是,使用兩者的補碼(帶符號的值的常用表示形式)會使這些值的比較更加困難。這樣,我們將一個稱為bias(偏差)的常數添加到指數中。通過在存儲指數之前對其進行偏置,我們將其置于更適合比較的無符號范圍內。
對于單精度浮點,將-127到+ 127范圍內的指數加上127以得到1到254范圍內的值(0和255具有特殊含義),從而對指數產生偏倚。
對于雙精度,將1022到+1023范圍內的指數加1023來獲得1到2046范圍內的值(0和2047具有特殊含義),從而對其產生偏差。
偏差和2的冪的和是實際上進入IEEE 754字符串的指數。請記住,指數=冪+偏差。(或者,冪=指數偏差)。該指數本身必須最終以二進制形式表示-但考慮到加上偏差后我們有一個正整數,則現在可以按常規方式完成此操作。
計算這些二進制值后,可以將它們放入32位或64位字段中。這些數字的排列方式如下:
image.png
通過以這種方式排列字段,以使符號位位于最高有效位位置,偏斜指數位于中間,然后尾數位于最低有效位-結果值實際上將正確排序以進行比較,無論是否它被解釋為浮點數或整數值。這樣可以使用定點硬件對浮點數進行高速比較。
有一些特殊情況:
零
符號位= 0; 有偏指數(階碼)=全部0位; 分數=全部0 位;-0和+0是不同的值,盡管它們相等正負無窮大(不知是否理解對?)
符號位=0 ,有偏指數(階碼)=全部1個位, 分數=全部0 位,表示正無窮大;
符號位=1,有偏指數(階碼)=全部1個位,分數=全部0 位,為負無窮大;NaN(非數字)
值NAN用于表示錯誤值。當指數字段為全零且帶零符號位或尾數不是1后跟零的尾數時,將表示此值。這是一個特殊值,可用于表示尚不包含值的變量。
Float,Double ,Decimal 有何區別?
Decimal,Double和Float變量類型在存儲值方面有所不同。精度是主要區別,其中float是單精度(32位)浮點數據類型,double是雙精度(64位)浮點數據類型,而Decimals(十進制)是128位浮點數據類型。
float/single -32位(7位數字)
double-64位(15-16位)
decimal-128位(28-29位有效數字)
主要區別在于Floats和Doubles是二進制浮點類型,而Decimal將值存儲為浮點小數點類型。因此,Decimal位數具有更高的精度,通常用于要求高度準確性的貨幣(金融)應用程序中。但是在性能方面,Decimal比雙精度和浮點型慢。
Decimal可以100%準確地表示十進制格式精度范圍內的任何數字,而Float和Double不能準確表示所有數字,即使數字在其各自格式精度范圍內。
將IEEE 754浮點轉換為二進制
示例:轉換為浮點型Floating?Point?Representation
ieee-standard-754-floating-point-numbers
Decimal vs Double vs Float
總結
以上是生活随笔為你收集整理的四舍五入_从四舍五入谈起的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python数据框循环生成_python
- 下一篇: Powerbi实现帕累托分析