为什么C#中的整数除法会返回一个整数而不是浮点数?

175

有人知道为什么C#中的整数除法会返回一个整数而不是浮点数吗?这背后的想法是什么?(它只是C/C++的遗留问题吗?)

在C#中:

float x = 13 / 4;   
//== operator is overridden here to use epsilon compare
if (x == 3.0)
   print 'Hello world';

这段代码的结果将会是:

'Hello world'

严格来说,整数除法并不存在(按照定义,除法是一种产生有理数的操作,而整数只是有理数中的一个很小的子集合)。


64
因为这是“整数”除法,而不是“浮点数”除法。 - Hunter McMillen
在VB.Net中,它的实现方式是不同的,以自然数学的方式实现,其中除法运算的结果都是无理数。 - BanditoBunny
4
我认为你的意思是“有理数”。参见维基百科:除两个整数可能会有余数。为了完成余数的除法,需要将数字系统扩展到包括分数或更普遍地称为有理数。 - crashmstr
6
这就是我不喜欢语言之间“抄袭语法”的原因。我的VB思维认为“C#是.NET”,而不是“C#像C”。我犯了错误,但在这种情况下,我更倾向于使用VB的方式。如果它们已经费力地在使用未初始化的简单类型时生成编译器错误(在C中甚至没有警告),那么为什么不在您将整数除法赋给浮点数时发出警告呢? - darda
1
其他编程语言在实数整数除法方面有不同的运算符。例如,13 / 4 = 3.25,而13 div 4 = 3 - Ian Boyd
8个回答

129

虽然新手程序员经常犯这样一个错误,即执行整数除法时实际上他们想要使用浮点数除法,但在实际操作中,整数除法是一种非常常见的操作。如果你认为人们很少使用它,并且每次进行除法运算时都需要记住转换为浮点数,那么你就错了。

首先,整数除法要快得多,因此如果您只需要一个整数结果,那么应该使用更高效的算法。

其次,有许多算法使用整数除法,如果除法的结果总是浮点数,则必须每次四舍五入结果。 例如,改变数字的基数就是一种使用整数除法的例子。 计算每个数字涉及到数字的整数除法以及余数,而不是数字的浮点除法。

由于这些(和其他相关原因),整数除法的结果是整数。 如果您想获得两个整数的浮点除法,则只需记住将其中一个转换为 double/float/decimal 即可。


6
在VB.Net中,.Net架构师做出了另一个决定:/表示浮点数除法,\表示整数除法,因此这种表现有一定的不一致性,除非你考虑C++遗留问题。 - BanditoBunny
4
在编译时,你可以确定 / 运算符将执行整数除法还是浮点数除法(除非你使用动态实现)。如果你在一行代码中做了太多的事情以至于难以判断它执行的操作数是整数还是浮点数类型,建议将其拆分为几行,这样更容易判断。这样做对未来阅读你的代码的人可能会很有帮助。 - Servy
7
我个人认为,我总是不得不想一想我正在除以哪些变量,这对我来说是有问题的,我认为这是浪费我的注意力。 - BanditoBunny
8
由于这将是一项重大的突破性变革,会导致大量程序无法运行,因此我可以非常自信地说,C# 永远不会这样做。这种事情要么从语言诞生之初就需要考虑,要么永远不会发生。 - Servy
3
MSBasic在.NET出现之前就一直有\和/。请不要说我们希望像Python一样有尽可能多的破坏性变化。几乎每个Python项目都需要特定版本才能工作,这是一场噩梦。 - PRMan
显示剩余9条评论

89

请查看 C# 规范。有三种类型的除法运算符:

  • 整数除法
  • 浮点数除法
  • 小数除法

在您的情况下,我们使用整数除法,并应用以下规则:

该除法将结果向零舍入,而且结果的绝对值是两个操作数商的绝对值小于等于最大整数。当两个操作数具有相同的符号并且为零或正时,结果为零或正;当两个操作数具有相反的符号时,结果为零或负。

我认为 C# 使用这种类型的整数除法的原因是硬件——整数除法更快、更简单。


哪些编程语言返回浮点结果?@SergeyBerezovskiy - Ilaria
@Ilaria Delphi。 - SHIN JaeGuk
2
正如@SHINJaeGuk所说,Delphi默认的除法运算符/返回浮点结果。此外,Delphi还有一个特殊的除法运算符div,它返回整数结果:5 / 2 = 2.5 5 div 2 = 2 - WilliamK

46

每种数据类型都可以重载每个运算符。如果分子和分母都是整数,整数类型将执行除法操作,并返回整数类型。如果要进行浮点数除法,则必须在除法前将一个或多个数字强制转换为浮点类型。例如:

int x = 13;
int y = 4;
float x = (float)y / (float)z;

或者,如果您使用字面意义:

float x = 13f / 4f;

记住,浮点数并不精确。如果你在意精度,应该使用类似于十进制类型的东西。


2
只需将一个术语设为浮点数即可进行浮点除法。+1 表示提到此点。 - Xynariz
显然,您在学习和使其不过于复杂以理解的上下文中对精度的陈述是正确的。由于我们需要尽可能精确地完成工作,我仍然想澄清一下精度问题:根据IEE 754-1985标准,您可以获得精确的结果(尽管这通常不是情况)。当计算值在之前已经被准确呈现,并且结果“简单地”是2的幂的总和时,您可以获得精确的结果。即使在这些特殊情况下依赖于该精度也不是最佳实践。 - L. Monty
精度增加:当结果接近1或-1时,获得精确结果的概率显着提高。可能有点令人困惑的是,由于存在无限数量的数字和有限数量的可以准确表示的结果,因此这种概率仍然保持为0。 :) - L. Monty
1
@L.Monty感谢您提出这个问题。自从我写下这个答案以来,我对浮点数有了更多的了解,您所说的观点是公正的。从技术上讲,我仍然认为我的陈述“浮点数不精确”是可以接受的,因为仅仅因为某些时候可以准确并不意味着它作为一个整体就是精确的。就像人们常说的,一个坏掉的时钟一天会有两次正确,但我永远不会称之为精密仪器。实际上,我很惊讶您更在意我建议十进制类型是精确的这一部分。 - Steven Doggart
十进制数和浮点数一样都存在不精确的问题,只不过浮点数是基于二进制,而十进制数是基于十进制。例如,十进制类型无法准确地保存1/3的精确值。 - Steven Doggart

14

可能有用:

double a = 5.0/2.0;   
Console.WriteLine (a);      // 2.5

double b = 5/2;   
Console.WriteLine (b);      // 2

int c = 5/2;   
Console.WriteLine (c);      // 2

double d = 5f/2f;   
Console.WriteLine (d);      // 2.5

请尽量为您的答案添加一些解释。 - NetStarter
最后一个表达式将产生2.5,而不是2 - Lasse V. Karlsen
是的,拼写错误。谢谢。 - erenozten

13

如果你没有使用任何后缀,字面量 134 将被解释为整数:

手册

如果字面量没有后缀,它将具有以下类型中其值可以表示的第一种类型:intuintlongulong

因此,由于您将 13 声明为整数,所以会执行整数除法:

手册

对于形如 x / y 的操作,将应用二进制运算符重载来选择特定的运算符实现。操作数将转换为所选运算符的参数类型,结果的类型是运算符的返回类型。

下面列出了预定义的除法运算符。 运算符都计算 x 和 y 的商。

整数除法:

int operator /(int x, int y);
uint operator /(uint x, uint y);
long operator /(long x, long y);
ulong operator /(ulong x, ulong y);

因此会发生向下取整:

该除法将结果向零取整,且结果的绝对值是小于两个操作数商的绝对值的最大整数。当两个操作数具有相同的符号并且为零或正数时,结果为零或正数;当两个操作数具有相反的符号并且为零或负数时,结果为零或负数。

如果您执行以下操作:

int x = 13f / 4f;

你会收到一个编译器错误,因为浮点数除法(使用/运算符和13f)的结果是一个浮点数,不能隐式转换为整数。

如果你想要执行浮点数除法,你需要将结果声明为浮点数类型:

float x = 13 / 4;

请注意,您仍将除以整数,这些整数将被隐式转换为浮点型:结果将是3.0。要显式声明操作数为浮点型,请使用f后缀(13f4f)。


2
+1 表示你可以得到一个浮点数的答案,但仍然进行整数除法。此外,我看到另一种常见的强制使用浮点数除法的方法是将除法的第一个项乘以 1.0 - Xynariz

9

这只是一个 基本操作

还记得学除法时的情景吗?一开始我们解决的是 9/6 = 1 余 3

9 / 6 == 1  //true
9 % 6 == 3 // true

/-运算符与%-运算符结合使用来检索这些值。

8
结果将始终是分子和分母范围更大的类型。例外情况是byte和short,它们会产生int(Int32)。
var a = (byte)5 / (byte)2;  // 2 (Int32)
var b = (short)5 / (byte)2; // 2 (Int32)
var c = 5 / 2;              // 2 (Int32)
var d = 5 / 2U;             // 2 (UInt32)
var e = 5L / 2U;            // 2 (Int64)
var f = 5L / 2UL;           // 2 (UInt64)
var g = 5F / 2UL;           // 2.5 (Single/float)
var h = 5F / 2D;            // 2.5 (Double)
var i = 5.0 / 2F;           // 2.5 (Double)
var j = 5M / 2;             // 2.5 (Decimal)
var k = 5M / 2F;            // Not allowed

浮点类型和十进制类型之间不存在隐式转换,因此它们之间的除法是不允许的。您需要显式地强制转换并决定您想要哪一个(与浮点类型相比,十进制类型具有更高的精度和更小的范围)。


1
作为了解你正在获取的小技巧,你可以使用var,这样编译器会告诉你要期望的类型:
int a = 1;
int b = 2;
var result = a/b;

你的编译器会告诉你,result 在这里将是 int 类型。


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