整数除法与浮点数除法 -> 谁负责提供结果?

7

我已经使用C++编程一段时间了,但突然有一个疑问,想要向Stackoverflow社区澄清。

当一个整数被另一个整数除时,我们都知道结果是一个整数,同样地,一个浮点数被另一个浮点数除也是一个浮点数。

但是谁负责提供这个结果呢?是编译器还是DIV指令?


1
你是在除以常量还是变量? - James Black
6个回答

12

这取决于你的架构是否 DIV 指令。如果你的架构同时具有整数和浮点数除法指令,编译器将为代码指定的情况发出正确的指令。语言标准指定了类型提升的规则以及在每种可能的情况下应该使用整数或浮点数除法。

如果你只有一个整数除法指令或仅有一个浮点除法指令,则编译器将内联一些代码或生成调用数学支持库来处理除法。除法指令通常很慢,所以大多数编译器会尽可能地优化它们(例如,用移位指令替换,或预计算编译时常量的除法结果)。


4
硬件除法指令几乎从不包括整数和浮点数之间的转换。如果你有除法指令(它们有时会被省略,因为除法电路很大且复杂),它们几乎肯定是“整数除以整数,产生整数”和“浮点数除以浮点数,产生浮点数”。同时,输入和输出通常也都是相同大小的。
编译器负责在这些基本操作之上构建源代码中编写的任何操作。例如,在C语言中,如果你将一个浮点数除以一个整数,编译器将发出一个int-to-float转换,然后是float除法。
(异常情况确实存在。我不知道,但我认为VAX可能会有“浮点数除以整数”类型的指令。Itanium实际上没有除法指令,但它的“除法助手”用于浮点数,你必须在浮点除法的基础上伪造整数除法!)

关于你所说的“古怪异常”,你可能刚刚在写这个答案时使用了其中一个。x86(或更精确地说是x87)用于“将浮点数除以整数”的指令称为FIDIV。 - slacker
1
这还不是 x86 荒谬之处的唯一体现,令 VAX 相形见绌 :). - slacker
哈!嗯,那应该是x87。他们在那个东西上什么事情都不按照正常方式做。 - zwol

3

编译器将根据所使用变量的类型在编译时决定需要使用哪种形式的除法 - 最终,某种形式的DIV(或FDIV)指令将被引入。


1
除非变量是编译时常量,在这种情况下,编译器将“承担责任”,并且不会生成任何指令。 - Potatoswatter
@Potatoswatter:并非完全如此;编译器必须评估整数常量表达式,可以评估其他常量表达式。即 float x = 1.0/3.0 可能会被编译器评估,也可能会产生FDIV - 由编译器决定。 - MSalters

2
你的问题并没有太多意义。DIV指令本身并不会做 任何事情。无论你大声喊叫,甚至试图贿赂它,它都不会对 任何事情 负责。
当你用编程语言 [X] 编写程序时,实现你在源代码中描述的程序是 [X] 编译器的唯一责任
如果需要除法运算,编译器决定如何实现除法。如果你的目标 CPU 有 DIV 指令,那么编译器可能会生成 DIV 指令的操作码。如果两个操作数在编译时已知,则可能通过在编译时预计算除法并直接将结果插入到程序中来完成。或者,编译器可能会生成一系列指令,这些指令共同 模拟 除法。
但这完全由编译器决定。除非按照 C++ 标准解释,否则你的 C++ 程序 没有任何作用。如果将其解释为纯文本文件,它不会 执行任何操作。如果编译器将其解释为 Java 程序,它将无法识别并拒绝它。
DIV 指令并不知道 C++ 标准的任何内容。另一方面,C++ 编译器是 为了理解 C++ 标准并根据其进行代码转换 而编写的。
编译器永远负有 全部责任

1

C++标准中最重要的规则之一是“仿佛”规则:

本国际标准中的语义描述定义了一个参数化的非确定性抽象机。本国际标准对符合要求的实现结构没有任何要求。特别地,它们不需要复制或模拟抽象机的结构。相反,符合要求的实现需要模拟(仅)下面解释的抽象机的可观察行为。

这意味着与您的问题相关的是,无论哪个组件执行除法,只要完成即可。它可以通过DIV机器码执行,如果处理器没有适当的指令,则可以通过更复杂的代码执行。

它还可以:

  1. 如果适当且可能更快,则使用位移操作替换操作。
  2. 如果在编译时可以计算出,例如在处理x / y时可以显示y始终为1,则使用文字替换操作或赋值替换操作。
  3. 如果可以在编译时显示它将始终是整数除以零,则使用异常抛出替换操作。

如果编译器在编译时能够推断商始终为零,那么它是否可能拒绝编译?或者一个完全符合规范的 C 程序是否可以包含像 x=1/(x-x) 这样的指令,只要这样的指令实际上从未执行过? - supercat
@supercat: 这是完全合法的。是对零除以执行操作导致未定义行为。 - slacker
1
@supercat 如果你想编写一个总是出错的程序,那是你的事情!毕竟没有错误,很难教会人们如何处理它们(尽管在某些语言中,你必须做一些像 if(true == false) 这样的事情来欺骗编译器让你编译)。对于编译器产生非标准化警告,这肯定是合理和有用的。 - Jon Hanna
1
...重要的是,如果一位无可挑剔的语言专家说“它会表现如此”,那么从外部来看,它必须按照专家的说法去做。它是如何做到的取决于编译器。那些“未定义”的部分是专家会说“我不知道它会做什么”的地方(实际上他可能会对糟糕的代码发牢骚,你知道专家们都是怎样的),但“定义”只适用于“外部”视图。这在允许进行优化方面非常重要,事实上,STL 的很大一部分就依赖于此以避免性能问题。 - Jon Hanna
编译器也可以将 x/2.5 替换为 x * 0.4。实际上,这是很常见的,因为乘法通常比除法更快。 - MSalters

0

实际上

C99标准定义了“当整数相除时,/运算符的结果是代数商,任何小数部分都被舍弃。”并在脚注中补充说,“这通常被称为‘向零截断’”。

历史

从历史上看,语言规范负责定义其运算符。

Pascal 定义其运算符,使使用/进行除法运算始终返回一个real(即使您用它来除以2个整数),如果要除以整数并获得整数结果,则使用div运算符。(Visual Basic也有类似的区别,并使用\运算符进行整数除法运算,返回整数结果。)

在C中,决定通过将整数操作数之一转换为float来进行相同的区分,如果您想要浮点结果。在许多源自C的语言中,按照您描述的方式处理整数与浮点类型已成为惯例。我怀疑这种惯例可能起源于Fortran。


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