2011-10-20 22 views
2

有人可以提供代碼(任何語言都可以,但我寫的.Net語言和VB6)的算法將數字以5結尾給定數字嗎?向給定數字回合

RoundTo(雙精度值,朝加倍,整數numberOfDigitsBehindComma)

RoundTo(1.25,1,1)= 1.2 RoundTo(1.25,2,1)= 1.3

RoundTo(1.26,1, 1)= 1.3 RoundTo(1.24,2,1)= 1.2

請包括負數解決方案。

編輯:似乎有很多關於我的要求的混亂,我會填寫所產生的代碼必須滿足的所有斷言。我的解決方案是這樣。

[TestMethod] 
    public void RoundTowards() 
    { 
     double x=3.44;double y=3.45;double z=4.45; 
     double a = 3.51; double b = 4.5001; double c = -1.14; double d = -1.15; 
     var mean=4; 
     Assert.AreEqual(3.4,x.RoundTowards(mean,1)); 
     Assert.AreEqual(3.5, y.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.4, z.RoundTowards(mean, 1)); 
     Assert.AreEqual(3.5, a.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.5, b.RoundTowards(mean, 1)); 
     mean = 5; 
     Assert.AreEqual(3.4, x.RoundTowards(mean, 1)); 
     Assert.AreEqual(3.5, y.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.5, z.RoundTowards(mean, 1)); 
     Assert.AreEqual(3.5, a.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.5, b.RoundTowards(mean, 1)); 
     mean = 3; 
     Assert.AreEqual(3.4, x.RoundTowards(mean, 1)); 
     Assert.AreEqual(3.4, y.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.4, z.RoundTowards(mean, 1)); 
     Assert.AreEqual(3.5, a.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.5, b.RoundTowards(mean, 1)); 
     Assert.AreEqual(Math.Round(-1.1,4),Math.Round(c.RoundTowards(mean, 1),4)); 
     Assert.AreEqual(Math.Round(-1.1,4),Math.Round(d.RoundTowards(mean, 1),4)); 
     mean = -2; 
     Assert.AreEqual(Math.Round(3.4,4),Math.Round(x.RoundTowards(mean, 1),4)); 
     Assert.AreEqual(Math.Round(3.4,4),Math.Round(y.RoundTowards(mean, 1),4)); 
     Assert.AreEqual(Math.Round(4.4,4),Math.Round(z.RoundTowards(mean, 1),4)); 
     Assert.AreEqual(Math.Round(3.5,4),Math.Round(a.RoundTowards(mean, 1),4)); 
     Assert.AreEqual(Math.Round(4.5,4),Math.Round(b.RoundTowards(mean, 1),4)); 
     Assert.AreEqual(Math.Round(-1.1, 4), Math.Round(c.RoundTowards(mean, 1), 4)); 
     Assert.AreEqual(Math.Round(-1.2, 4), Math.Round(d.RoundTowards(mean, 1), 4)); 

    } 

    [TestMethod] 
    public void RoundTowardsTowardZero() 
    { 
     double x = 3.45; double y = -3.45; 
     double a = -3.551; double b = 4.551; double c = 4.5500001; double d = 4.5501; 
     var mean = 0; 
     Assert.AreEqual(3.4, x.RoundTowards(mean, 1)); 
     Assert.AreEqual(-3.4, y.RoundTowards(mean, 1)); 
     Assert.AreEqual(-3.6, a.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.6, b.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.5, c.RoundTowards(mean, 1)); 
     Assert.AreEqual(4.6, d.RoundTowards(mean, 1)); 

    } 

    [TestMethod] 
    public void Test14_55() 
    { 
     Assert.AreEqual((14.55).RoundTowards(9, 1) ,14.5); 
     Assert.AreEqual((14.55).RoundTowards(15,1), 14.6); 
    } 

    [TestMethod] 
    public void Test14_5499999() 
    { 
     Assert.AreEqual((14.54999999).RoundTowards(9, 1) ,14.5); 
     Assert.AreEqual((14.54999999).RoundTowards(15,1), 14.6); 
    } 

謝謝!

回答

2

我認爲這裏的所有解決方案都太複雜。你的問題似乎是,當你恰好處於中點時,你想要控制四捨五入的方向。您可以通過乘以然後除以10的整數次冪來減少小數點後面有N位數的問題,因此在「5」恰好在小數點後面的情況下就足以解決這個問題。如果你想舍入數字x,例如0.5向上取整爲1,你只是做

result = floor(x + 0.5); 

如果你想使0.5向下取整爲0圓X,你只是做

result = ceil(x - 0.5); 

這些工作,因爲如果x = 0.5, floor(x + 0.5)= floor(1)= 1,ceil(x-0.5)= ceil(0)= 0。要看到其他數字總是正確的舍入,

x = 0.4: floor(x + 0.5) = floor(0.9) = 0 
     ceil(x - 0.5) = ceil(-0.1) = 0 
x = 0.6: floor(x + 0.5) = floor(1.1) = 1 
     ceil(x - 0.5) = ceil(0.1) = 1 

所以整個代碼變爲:

double RoundTo(double value, double towards, int digits) { 
    double mult = pow(10, digits); /* to handle variable number of digits */ 
    bool downwards = (towards < value); 
    value *= mult; /* scale */ 
    value = (downwards ? ceil(value - 0.5) /* round midpoint downwards */ 
        : floor(value + 0.5)); /* round midpoint upwards */ 
    return value/mult; /* scale back */ 
} 

該解決方案還卸載的全過程,以實際的數學庫和CPU的ALU,因此是非常健壯。這處理顯然負數,沒有任何額外的調整,並與infinities等正常工作。

+0

謝謝,這個解決方案最像我自己的,但更簡潔。它只適用於最後五個存儲爲499999999999或5000000001的情況,這在處理浮點變量時很常見。我通過在理想的舍入位置後面提供了一個小的加/減四個位置來解決這個問題。但我不確定這是否是最好的方法。 – Dabblernl

+0

我接受了你的答案,即使我需要解決上述問題。我希望你會有時間對此發表評論。我改變了你的算法,我把+/-的+/-值改成了.50001。現在所有測試都是綠色的。 – Dabblernl

+0

嗨,是的,浮點數是處理困難的野獸。我不會添加任何調整因素,因爲+ 0.50001會得到0.499990000的不正確舍入。如果你不能容忍任何錯誤,你不應該使用浮點數(因爲它們從來不是確切的),但是例如, 10個鹼基的定點數字,例如將數字表示爲對(a,b),其中兩者都是整數,並且所表示的數值是(a + b/1000000),例如。 –

2

那麼,這似乎是做的工作,至少我的測試是'綠色',就是。我特別不喜歡的必要性圓一次到底的,再增加值的四捨五入可以再次生出典型的雙打像0.99999999999999999999,在.1

<Extension()> 
    Public Function RoundTo(ByVal value As Double, mean As Double, digitsBehindComma As Integer) As Double 
     Dim correctedValue = value - mean 
     Dim increasedValue = correctedValue * Math.Pow(10, digitsBehindComma) 
     Dim trailingDigitCorrection = Math.Sign(correctedValue) * Math.Pow(10, -4) 'Safeguard against a trailing bit (i.e. 1.50000000001) 
     Dim halfAddition = Math.Sign(correctedValue) * 0.5 
     Dim division = 10^digitsBehindComma 
     Dim sum = increasedValue - trailingDigitCorrection + halfAddition 

     Dim result = Fix(increasedValue - addition + halfAddition)/division 
     Return Math.Round(result + mean, digitsBehindComma) 

    End Function 
+0

它會正常工作4.55輪朝着1和4.55輪朝着10? (不確定,因爲我不是一個VB程序員) – CygnusX1

+0

是的,先生;-) trailingDigitCorrection照顧。現在我能得到這個答案的第二個滿意答案嗎? ;-) – Dabblernl

2

在編碼代替直接C

我不確定我是否完全理解了這個問題,所以我做了一些假設 假設你是1.235的四捨五入,我假設你只想要5的問題,如果你在5之前舍入到小數位。所以

因此RoundTo(1.235,2,1)= 1.2但是RoundTo(1.235,2,2)= 1.24和RoundTo(1.235,1,2)= 1.23

適用於負數,它不是計算密集程度最低的解決方案,但應易於理解和修改。

#include <cstdlib> 
#include <iostream> 
#include <math.h> 

double round(double value, double toWards, int numberOfDigitsBehindComma) 
{ 
    double value1 = floor(value * pow(10,numberOfDigitsBehindComma)); 
    double value2 = floor(value * pow(10,numberOfDigitsBehindComma + 1)) - value1 * 10; 
    if (fabs(value2) > 5 || (fabs(value2) == 5 && toWards > value)) 
    { 
     value1++; 
    } 
    double value3 = value1/pow(10,numberOfDigitsBehindComma); 
    return value3;  
} 
+0

我對使用情況感到困惑,於是我認爲toWards參數意味着在結尾5的情況下繞過這個數字。所以14.55回合到9(向下)。 – 8bitwide

+0

使用你的algortithm我的測試在以下斷言失敗:Round(4.551,0,1)== 4.6;圓(14.55,9,1)== 14.6;輪(-2.45,-2,1)== - 2.4; – Dabblernl

+0

@Dabblernl爲什麼4.551到4.6時會舍入0? – Gareth

2

正在更新@ 8bitwide的解決方案。

編輯:處理浮點表示中的錯誤,我用isHalf(value2)替換value2 == 0.5函數,可以做一個模糊比較。聽起來這樣可以達到你的目的,因爲你的數字來自最多數千個低精度值的計算(基於我參加的橋牌比賽)。

也就是說,如果出現數字4.5500000000001,那肯定是4.55的表示,而不是實際的數字4.5500000000001。

測試用例包括4.5500001。 double大約有15位數字的準確性,所以如果您只能將數字精確到只有7位數字,那麼您的計算就會出現錯誤。

#include <cstdlib> 
#include <iostream> 
#include <math.h> 

bool isHalf(double x) 
{ 
    return abs(x - 0.5) <= 1e-10; // or whatever degree of fuzziness suits you 
} 

double round(double value, double toWards, int numberOfDigitsBehindComma) 
{ 
    double value0 = value * pow(10,numberOfDigitsBehindComma); 
    double value1 = floor(value0); 
    double value2 = value0 - value1; // 0 <= value2 < 1 
    if (value2 > 0.5 || isHalf(value2) && toWards > value)) 
    { 
     value1++; 
    } 
    double value3 = value1/pow(10,numberOfDigitsBehindComma); 
    return value3;  
} 
+1

這裏只有一個Assertion失敗了,但這是一個隱藏的需求,我自己不確定如何處理它:Round(4.5500001,4,1)== 4.5問題是,當雙精度值應該是4.55時Round(4.55,4,1)= 4.5)這樣的數字有時可以表示爲4.550000001。我想擺脫這樣的尾隨位,但我不確定如何解決這個問題。在我自己的算法中,我刪除尾數數字比我需要的逗號後面的數字還要多4個位置。 – Dabblernl

+0

你根本就不能將4.55表示爲雙精度(或其他以二進制形式工作的浮點數)!二進制系統中的4.55是100.10001100(1100)。類似於小數點的三分之一是0.333(3)。如果你在任何一點切入,你可以得到略低於4.55或略高於4.55的值(取決於舍入策略)。 Precise 4.55根本不能出現在任何使用二進制系統的計算機上。 – CygnusX1

+0

@ CygnusX1:考慮到4.54999999999或4.55000000001爲4.55 – Dabblernl

1

關於我們的討論:

[...]這是一個隱藏requierement,我自己不知道如何對付它:圓形(4.5500001,4,1)= = 4.5問題是,如果一個double值真的應該是4.55(因此Round(4.55,4,1)= 4.5),那麼這個值有時可以表示爲4.550000001。我想擺脫這樣的尾隨位,但我不確定如何解決這個問題。在我自己的算法中,我刪除尾數數字比我需要的逗號後面的數字還要多4個位置。

它被用來評分橋牌比賽,在逗號後面的第二個數字處的舍入誤差可能意味着冠軍與第二名之間的差異。

我認爲你的軟件不是時間關鍵的:那就是---你不要每秒執行數百萬次的計算,而且如果它有點太慢,軟件將不可用。

爲了避免來自浮點數或雙精度的二進制性質的舍入問題,我建議使用十進制進行所有相關的計算。當然,計算會比使用二進制系統慢幾倍,但它應該使您的計算精確。

在Visual Basic 6中,有一種稱爲Currency的類型,但它是一個定點變量:它總是在點後面保留4位數字(十進制)。 VB.NET引入了Decimal 這是不固定的,但工作在十進制系統。

我不確切知道它支持哪些數學運算,但我確信所有基本運算都在那裏。使用更復雜的(對數,指數,三角函數)可能需要一些破壞性的強制轉換,但我希望你不需要在橋上:)

一旦進入十進制的世界,你應該能夠輕鬆地實現舍入函數(例如xan提供的函數),沒有任何舍入問題。


另一個我可以建議 - 到處使用整數。如果您始終只關心 - 比如說 - 在點後面的4位數字,只需將所有值乘以10000,然後使用這些「增加」值執行計算。在執行乘法時請注意。

0

這個寶寶有點低效,也許它會在拐角的情況下行事不端,但它似乎爲四個測試點做了正確的事情。

#include <stdio.h> 
#include <math.h> 

double roundto (double val, double towards, unsigned ndigit, double expected) 
{ 
double up, down, mult, res; 
int dir; 
dir = (val == towards) ? 0 : (val > towards) ? -1 : 1; 

mult = pow(10, ndigit); 
down = floor (val * mult)/mult; 
up = ceil (val * mult)/mult; 
if (val-down == up-val) {;} 
else dir = (val-down < up-val) ? -1 : 1; 
res = dir > 0 ? up : down; 

/* 
fprintf (stderr, "Val=%f Expected=%f: dir=%d Mult=%f Down=%f Up=%f Res = %f\n" 
    , val, expected, dir, mult, down, up, res); 
*/ 

return res; 
} 

int main(void) 
{ 
double result; 

result = roundto(1.25, 1, 1, 1.2); 
printf ("Result = %f\n", result); 

result = roundto(1.25, 2, 1, 1.3); 
printf ("Result = %f\n", result); 


result = roundto(1.26, 1, 1, 1.3); 
printf ("Result = %f\n", result); 

result = roundto(1.24, 2, 1, 1.2); 
printf ("Result = %f\n", result); 

return 0; 
}