朝着给定的数字前进

时间:2011-10-20 11:28:16

标签: algorithm rounding

有人可以提供代码(任何语言都会这样做,但我会编写.Net语言和VB6)用于将以5结尾的数字舍入给定数字的算法吗?

RoundTo(double value,double toWards,int 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);
  }

感谢!!!

6 个答案:

答案 0 :(得分:2)

嗯,这似乎可以完成这项工作,至少我的测试是'绿色',就是这样。我并不特别喜欢最后再次回合的必要性,因为重新加入圆值可以再次产生典型的双打,如.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

答案 1 :(得分:2)

直接编码

我不确定我是否完全理解这个问题,所以我做了一些假设 假设你正在向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;      
}

答案 2 :(得分:2)

在下面更新@ 8bitwide的解决方案。

编辑:为了处理浮点表示中的错误,我将value2 == 0.5替换为可以进行模糊比较的isHalf(value2)函数。这听起来不错,因为你的数字来自最多数千个低精度值的计算(基于我参加过的桥牌锦标赛)。

也就是说,如果出现数字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;      
}

答案 3 :(得分:2)

我认为这里的所有解决方案都太复杂了。您的问题似乎是,当您正好处于中点时,您希望能够控制舍入的方向。您可以通过乘以然后除以10的整数幂来减少N位小数点尾随的问题,因此对于“5”在小数点之后的情况就足以解决这个问题。如果你想将数字x舍入,那么例如0.5向上舍入为1,你只需要

result = floor(x + 0.5);

如果要将x舍入为0.5以使0.5向下舍入为0,则只需执行

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,因此非常强大。这样可以处理明显的负数而不需要任何额外的调整,并且可以正确地处理无穷大等。

答案 4 :(得分:1)

关于我们的讨论:

  

[...]这是一个隐藏的需求,我自己不确定如何处理它:Round(4.5500001,4,1)== 4.5问题是,当一个双重真的应该是4.55(因此回合(4.55,4,1)= 4.5)这样的数字有时可以表示为4.550000001。我想摆脱这些尾随的位,但我不确定如何处理这个问题。在我自己的算法中,我删除的位数比我需要的逗号后面的位数多4个位置。

     

它用于对锦标赛进行评分,逗号后面第二位的舍入误差可能意味着冠军和第二名之间的差异。

我认为你的软件不是时间关键的:那就是---你不会每秒执行数百万次计算,而且如果它有点太慢,那么软件就不会这样。 unusuable。

为了避免浮点数或双打的二元性质的舍入问题,我建议使用十进制系统进行所有相关计算。当然,计算速度比使用二进制系统慢几倍,但它应该能够精确计算。

在Visual Basic 6中有一个名为Currency的类型,但它是一个定点变量:它在点后总是保持4位数(十进制)。 VB.NET引入了Decimal  这不是固定的,但也适用于十进制系统。

我不确切知道它支持哪些数学运算,但我很确定所有基本运算都在那里。使用更复杂的(对数,指数,三角函数)可能需要一些破坏性的强制转换,但我希望你不需要在桥中:)

一旦进入十进制世界,您应该能够轻松实现舍入功能(例如xan提供的功能),而不会出现任何舍入问题。


我可以建议的替代方案 - 在任何地方使用整数。如果您始终只关心 - 例如 - 点后4位数,只需将所有值乘以10000并使用这些“增强”值执行计算。执行乘法时请注意。

答案 5 :(得分: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;
}