C#: 如何在整数上进行简单的四舍五入数学运算?

19
我希望将一个方程的结果四舍五入到最接近的整数。例如:

137 * (3/4) = 103

考虑以下不正确的代码。

int width1 = 4;
int height1 = 3;

int width2 = 137;
int height2 = width2 * (height1 / width1);

在C#中执行“整数”运算的正确方式是什么?

我真的必须要这样做吗:

int height2 = (int)Math.Round(
      (float)width2 * ((float)height1 / (float)width1)
   );

7
很遗憾,一年过去了我仍然需要参考这个问题才能弄清楚如何在C#中进行数学运算。在Delphi中,我只需要输入"137 * 3/4",它就可以工作了。但在C#中却不行,C#会强制你先祈求编译器之神才能做你想要的事情。 - Ian Boyd
12个回答

17

如上所述,在进行除法之前,您应该先进行乘法运算。无论如何,您可以仅对被除数进行强制转换并编写表达式:

int height2 = (int)Math.Round(width2 * (height1 / (float)width1));

3

首先进行乘法运算(只要它们不太可能溢出),然后进行除法运算。

在整数运算中,3/4 == 0(整数运算不会在除法上四舍五入,而是截断)。

如果您真的需要四舍五入,则必须使用定点、浮点或模数进行操作。


经过一年的反思,你能解释一下为什么我应该先进行乘法吗? - Ian Boyd
1
乘法会将数字向左移位,除法则向右移位。如果乘法不导致溢出,则不会丢失精度。由于除法会丢失位数(如果除以2,则会丢失1位,如果除以16,则会丢失4位等),因此高度可能会丢失精度。 - plinth

3
int height2 = (width2 * height1) / width1;

此解决方案仅向下舍入,如果需要向上舍入则不会执行。例如,如果width1 =(width2 * height1 + 1),那么(width2 * height1)/ width1的结果为0,而不是1。换句话说,如果除数略大于被除数,则整数除法将产生0,而带有舍入的浮点除法将产生1。 - Catch22
@Catch22 - 是的,那是一个很好的观点。带有浮点转换的版本是更好的答案。 - Jeffrey L Whitledge

2
我认为这是最优雅的做法:
Math.round(myinteger * 0.75);

使用0.75而不是3/4会隐式转换为double / float类型,为什么不使用提供的默认函数呢?


Math.round(...) 返回的是 double 类型而不是 int 类型(因此需要进行更多的类型转换)http://msdn.microsoft.com/zh-cn/library/75ks3aby.aspx - craastad

1

永远不要将数据类型转换为浮点型,因为它的精度甚至比32位整数还要低。如果你要使用浮点数,请始终使用双精度(double)而不是单精度(float)。


既然我最终会将其舍入为32位整数,那么双精度浮点数在小数点后第8位有有效数字,而单精度浮点数没有,这是否重要? - Ian Boyd
79.99999999比79.99999999999差吗? - Ian Boyd
是的。32位整数大约有9个数字,但32位浮点数只有大约7个数字。还要记住:偶尔会给出略微错误答案的代码是最糟糕的错误类型,比总是得到完全错误答案要糟糕得多! - rwallace
2
你能举出任何例子,其中将单精度浮点数转换为32位整数会得到错误的答案,而将双精度浮点数转换为32位整数则是正确的吗? - Ian Boyd
@IanBoyd:16777216.0f + 1.0f等于多少? - supercat
1
@IanBoyd:你试过了吗?除非你使用的是将“float”转换为“double”的语言,否则16777216.0f + 1.0f的结果为16777216.0f,因为在16777216.0f和16777218.0f之间没有“float”值。 - supercat

1

我将通过不添加任何代码来修复错误的代码:

float width1 = 4;
float height1 = 3;

float width2 = 137;
float height2 = width2 * (height1 / width1);

对于可能包含小数的变量,应该使用浮点数。这包括从比例计算出的高度。如果有问题,您始终可以稍后转换为整数。


1
原始值是整数,只能包含整数。问题是如何进行数学计算。如果我进行浮点数运算,我会问这个问题。(实际上,不是真的,因为FP数学不会有这个问题) - Ian Boyd

1

我刚刚偶然看到这个问题。Aaron的回答对我来说几乎是正确的。但是如果你的问题是“现实世界”问题,我非常确定你需要指定midpointrounding。因此,我的答案基于Aaron的代码,是:

int height2 = (int)Math.Round(width2 * (height1 / (float)width1),MidpointRounding.AwayFromZero);

要看到差异,请在控制台中运行该代码

    Console.WriteLine((int)Math.Round(0.5));
    Console.WriteLine((int)Math.Round(1.5));
    Console.WriteLine((int)Math.Round(2.5));
    Console.WriteLine((int)Math.Round(3.5));
    Console.WriteLine((int)Math.Round(0.5, MidpointRounding.AwayFromZero));
    Console.WriteLine((int)Math.Round(1.5, MidpointRounding.AwayFromZero));
    Console.WriteLine((int)Math.Round(2.5, MidpointRounding.AwayFromZero));
    Console.WriteLine((int)Math.Round(3.5, MidpointRounding.AwayFromZero));
    Console.ReadLine();

如果需要了解详情,您可以查看this文章。


1
六年(四舍五入)后,这是我的贡献-一个小技巧我早就学会了,并惊讶于没有其他人在这里提到过。
这个想法是通过在除法之前将除数的一半加到分子上来进行四舍五入。
    int height2 = (width2 * height1 + width1 / 2) / width1;

实际上,在像 OP 这样除数是变量的情况下,我不一定建议这样做。相反,使用 Math.Round() 可能更容易理解。

但在除数是常量的情况下,我会使用这个技巧。因此,不是使用

    int height2 = width2 * height1 / 4;  // No rounding

我会使用

    int height2 = (width2 * height1 + 2) / 4;

这是一个更典型的例子。
  private static Size ResizeWithPercentage(Size oldSize, int resizePercentage)
  {
     return new Size((oldSize.Width * resizePercentage + 50) / 100, 
                     (oldSize.Height * resizePercentage + 50) / 100);
  }

另一个可能性是将这个技巧与dongilmore和supercat提出的想法相结合,即不指定或隐含除以二,而是将分子和分母都乘以二。
    int height2 = (width2 * height1 * 2 + width1) / (width1 * 2);

在除数为奇数或可能为奇数的情况下,这会给出更好的答案。


0

Convert的转换总是四舍五入:

int height2 = Convert.ToInt32(width2 * height1 / (double)width1);

0
为了进一步解释Jeffery的消息,由于在截断所需小数位数方面通常比溢出32位整数的概率要高(且乘法和除法是可交换的),因此通常应先进行乘法再进行除法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接