向给定数字舍入

发布于 2024-12-10 20:13:40 字数 3035 浏览 0 评论 0原文

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

RoundTo(双精度值, 双精度 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);
  }

谢谢!!!

Can someone please provide code (any language will do, but I write the .Net languages and VB6) for an algorithm that rounds numbers ending with 5 towards a given number?

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

Include a solution for negative numbers please.

EDIT: There seems to be a lot of confusion about my requirements I will fill in all the Assertions that the resulting code has to meet. My solution does so.

  [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);
  }

Thanks!!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

盗琴音 2024-12-17 20:13:40

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

result = floor(x + 0.5);

如果你想对 x 进行舍入,以便 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,因此非常稳健。这可以处理明显的负数,无需任何额外的调整,并且可以正确处理无穷大等。

I think all the solutions here are too complicated. Your problem seems to be that you want to be able to control the direction of rounding when you are exactly at the midpoint. You can reduce the problem of having N digits trailing the decimal point just by multiplying and then dividing by an integer power of 10, so it's enough to fix this for the case where the "5" is right after to the decimal point. If you want to round number x so that e.g. 0.5 is rounded upwards to 1, you just do

result = floor(x + 0.5);

If you want to round x so that 0.5 is rounded downwards to 0, you just do

result = ceil(x - 0.5);

These work because if x = 0.5, floor(x + 0.5) = floor(1) = 1, and ceil(x - 0.5) = ceil(0) = 0. To see that other numbers are rounded always correctly,

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

so the whole code becomes:

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 */
}

This solution also offloads the whole process to the actual mathematics library and your CPU's ALU, and is therefore very robust. This handles obviously negative numbers without any extra tweaking, and works correctly with infinities etc.

慈悲佛祖 2024-12-17 20:13:40

用直接 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;      
}

Coded in straight C

I'm not sure I completely understood the question, so I made some assumptions
Say you are rounding 1.235, I assumed you only wanted the 5 to matter if you where rounding to the decimal place before the 5. So

So RoundTo(1.235,2,1) = 1.2 but RoundTo(1.235,2,2)= 1.24 and RoundTo(1.235,1,2) = 1.23

Works for negative numbers, It is not the least computationally intensive solution, but should be easy to understand and modify.

#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;      
}
小草泠泠 2024-12-17 20:13:40

下面更新@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;      
}

Updating @8bitwide's solution below.

EDIT: to deal with error in floating point representations, I replaced value2 == 0.5 with isHalf(value2) function that can do a fuzzy compare. Sounds like that is OK for your purposes since your numbers come from computations on at most thousands of low-precision values (based on bridge tournaments I've attended).

That is, if the number 4.5500000000001 occurs, it is surely a representation of 4.55 instead of the actual number 4.5500000000001.

The test case includes 4.5500001. double has about 15 digits of accuracy, so if you're getting numbers accurate to only 7 digits, something is very wrong with your calculations.

#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;      
}
凉栀 2024-12-17 20:13:40

好吧,这似乎可以完成工作,至少我的测试是“绿色”的。我不太喜欢最后再次舍入的必要性,因为重新添加舍入的值可能会再次产生典型的双精度数,例如 .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

Well, this seems to do the job, at least my tests are 'green', that is. I do not particularly like the necessity to round once more in the end as, the re-addition of the value to round can again give birth to typical doubles like .99999999999999999999, in stead of .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
沫雨熙 2024-12-17 20:13:40

关于我们的讨论:

[...] 这是一个隐藏的要求,我自己不确定如何处理它: Round(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 引入了十进制
它不是固定的,但也适用于十进制系统。

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

一旦进入十进制世界,您应该能够轻松实现舍入函数(例如xan 提供的),没有任何舍入问题。


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

In relation to our discussion:

[...] it is a hidden requierement that I am myself unsure about how to deal with it: Round(4.5500001,4,1)==4.5 The problem is that when a double really should be 4.55 (und thus Round(4.55,4,1) =4.5) such a number sometimes can be represented as 4.550000001 . I want to get rid of such trailing bits but am unsure about how toe deal with that problem. In my own algorithm I remove the trailing digits 4 places further than the number of digits behind the comma that I need.

It is used to score bridge-tournaments, a rounding error at the second digit behind the comma can mean the difference between a championship and second place.

I presume your software is not time-critical: that is --- you don't perform the computation million of times per second, and it is not the case that if it goes a bit too slow, the software will be unusuable.

To avoid the rounding problems coming from the binary nature of floats or doubles, I would suggest using decimal system for all your relevant calcuations. Of course, the computation will be few times slower than when using binary system, but it should get your computation exact.

In Visual Basic 6 there is a type called Currency, but it is a fixed-point variable: it holds always 4 digits (in decimal) after the dot. VB.NET introduces the Decimal
which is not fixed, but works in decimal system as well.

I don't know exactly which mathematical operations it supports, but I am pretty sure that all basic ones are there. Using more complex ones (logarithms, exponents, trigonometric functions) may require some damaging casts, but I hope you don't need that in bridge :)

Once in the world of decimal, you should be able to easily implement the rounding function (e.g. the one provided by xan), without any rounding problems.


An alternative I can suggest -- use integers everywhere. If you always care only about - say - 4 digits after the dot, just multiply all values by 10000 and perform your computation with those "augmented" values. Just pay attention when you perform multiplication.

所谓喜欢 2024-12-17 20:13:40

这个婴儿效率有点低,也许它在极端情况下会表现不佳,但它似乎对四个测试点做了正确的事情。

#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;
}

This baby is bit inefficient, maybe it will misbehave on the corner cases, but it appears to do the right thing for the four test points.

#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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文