为什么模数除法(%)仅适用于整数?


79

我最近遇到了一个问题,可以使用模数除法轻松解决,但是输入是浮点数:

给定一个周期性函数(例如sin)和只能在周期范围内计算它的计算机功能(例如[-π,π]),则使一个函数可以处理任何输入。

“显而易见的”解决方案是这样的:

#include <cmath>

float sin(float x){
    return limited_sin((x + M_PI) % (2 *M_PI) - M_PI);
}

为什么不起作用?我收到此错误:

error: invalid operands of types double and double to binary operator %

有趣的是,它确实适用于Python:

def sin(x):
    return limited_sin((x + math.pi) % (2 * math.pi) - math.pi)

20
π不等于3.14,实际上它不能表示为任何浮点类型。计算sin(x)较大的值x实际上需要一个非常困难的先验论证简化过程,而pi的任何有限逼近都无法实现。
R .. GitHub停止帮助ICE,

3
几乎可以肯定这是一项家庭作业,因此浮点错误不在该作业的范围之内,或者导致对更严格的数值分析的讨论。无论哪种方式,fmod都可能是教师要寻找的东西。
丹尼斯·齐克福斯

2
这不是功课,它只是想出了在阅读另一SO问题(东西stackoverflow.com/questions/6091837/...
布伦丹·朗

2
好吧,我的发言本来应该更精确一些。我的观点是,如果参数可以无限大地增长(不仅仅是双精度指数大小),则pi的任何有限近似都将不足。对于double,是的,pi的非常非常长的近似值就足够了。
R .. GitHub停止帮助ICE

1
@aschepler:我认为您不理解这个问题。
R .. GitHub停止帮助ICE,2013年

Answers:


76

因为“余数”的正常数学概念仅适用于整数除法。即生成整数商所需的除法。

为了将“余数”的概念扩展为实数,您必须引入一种新型的“混合”运算,该运算将为数操作数生成整数商。核心C语言不支持这种操作,但它是作为标准库函数以及C99中的函数提供的。(请注意,这些函数并不相同,并且具有一些特殊性。特别是,它们不遵循整数除法的舍入规则。)fmodremainder


7
从98标准中%的定义来看,“(a / b)* b + a%b等于a。” 对于浮点类型,(a/b)*b已经等于a[inofar,因为可以对浮点类型进行这样的声明],因此a%b永远不会特别有用。
丹尼斯·齐克福斯

1
@丹尼斯:的确,在代数领域中,余数始终为0。%我想,浮点运算符的最合适定义是a-(a/b)*b,它将为0或很小的值。
R .. GitHub停止帮助ICE,

7
@丹尼斯:您可以通过要求“ floor(a / b)* b + a%b = a”来轻松修正此公式。请注意,对于整数,floor(a / b)= a / b。
vog

C风格的整数除法使用截断法而不是下限,但该点仍然存在。
dan04

18
-1关于““余数”的常规数学概念仅适用于整数除法”,取模运算的数学概念也适用于浮点值,这是Donald Knuth在其经典著作中讨论的第一个问题之一。计算机编程艺术(第一卷)。即曾经是基础知识。如今,恕我直言,学生们没有得到他们所支付的教育。
干杯和健康。-Alf 2014年

52

您正在寻找fmod()

我想更具体地回答您的问题,在较旧的语言中,%运算符仅被定义为整数模除,而在较新的语言中,他们决定扩展运算符的定义。

编辑: 如果我要猜测为什么,我会说这是因为模块化算术的思想起源于数论,并且专门处理整数。


9
“较旧的语言”-APL的历史可以追溯到1960年代,其模运算符“ |” 适用于整数和浮点数据(也适用于标量,向量,矩阵,张量等)。如果将C的“%”模运算符与浮点数一起使用,则没有充分的理由不能执行与fmod相同的功能。
rcgldr 2014年

@rcgldr设计目标不需要浮点模。C的实现是为了编译Unix并限制OS所需的汇编语言。“ C是命令式程序语言。它被设计为使用相对简单的编译器进行编译,以提供对内存的低级访问,提供可有效映射到机器指令的语言构造,并且需要最少的运行时支持。” zh.wikipedia.org/wiki/C_(programming_language)–
哈珀

1
@harper-由于C使用与整数相同的语法包含浮点运算(例如加,减,乘和除),因此我不明白为什么它也不能使用相同的语法(%)包含模。是否选择包含它似乎是任意的。
rcgldr

16

我不能肯定地说,但我想这主要是历史性的。不少早期的C编译器根本不支持浮点。它是在以后添加的,甚至还没有完全添加-主要是添加了数据类型,并且语言支持了原始的操作,但是其他所有内容都留给了标准库。


1
+1是我在阅读清单时看到的第一个合理答案。实际上,现在阅读所有内容,这是唯一合理的答案。
干杯和健康。-Alf 2014年

我也迟来了+1。我曾经用C语言编写6809和Z80嵌入式系统。我没有办法提供包含c运行时库的空间。甚至不得不编写我自己的启动代码。浮点数是我买不起的奢侈品:)
理查德·霍奇斯

12

%C和C ++中的模运算符定义为两个整数,但是,有一个fmod()函数可用于双精度。


4
这是OP问题的答案,但忽略了OP尝试执行的操作中的基本问题:sin(fmod(x,3.14))甚至对于的大值sin(fmod(x,M_PI))也不等于。实际上,这些值可能相差2.0。sin(x)x
R .. GitHub停止帮助ICE,

2
@R ..:是的,但这是一个不同的问题,尽管有很多关于该主题
Mark Elliot

@R-我修正了方程式以正确执行它。实际的方程式并不是重点(一旦我具有对其进行测试的功能,就很容易弄清楚)。
布伦丹·朗

不是%余数运算符而不是模运算符?
chux-恢复莫妮卡2014年

7

约束在标准中:

C11(ISO / IEC 9899:201x)§6.5.5乘法运算符

每个操作数应具有算术类型。%运算符的操作数应为整数类型。

C ++ 11(ISO / IEC 14882:2011)§5.6乘法运算符

*和/的操作数应为算术或枚举类型;%的操作数应具有整数或枚举类型。通常对操作数进行算术转换,并确定结果的类型。

解决方案是使用fmod,这正是%根据C99 Rationale§6.5.5乘法运算符首先将的操作数限制为整数类型的原因:

C89委员会拒绝将%运算符扩展为浮点类型,因为这种用法将重复fmod提供的功能



2

%运算符为您提供数字的REMAINDER(模数的另一个名称)。对于C / C ++,这仅针对整数运算定义。Python稍宽一些,它允许您将浮点数的其余部分用于可将其除以多少的次数:

>>> 4 % math.pi
0.85840734641020688
>>> 4 - math.pi
0.85840734641020688
>>> 

2
剩下的不是“模数的别称”!请参阅:stackoverflow.com/questions/13683563/… 或从math-pov:math.stackexchange.com/questions/801962/… 简单地说:模和余数对于正数相同,另一个例子是余数不不要让您绕过指南针(逆时针)。请更正,因为我节俭:P
投票

2

%当您尝试查找两个都是类型Float或的数字的余数时,该运算符在C ++中不起作用Double

因此,您可以尝试使用/中的fmod函数,也可以使用以下代码行来避免使用该头文件:math.hcmath.h

float sin(float x) {
 float temp;
 temp = (x + M_PI) / ((2 *M_PI) - M_PI);
 return limited_sin((x + M_PI) - ((2 *M_PI) - M_PI) * temp ));

}


1

“模算术的数学概念也适用于浮点值,这是唐纳德·努斯在他的经典著作《计算机编程的艺术》(第一卷)中讨论的第一个问题。也就是说,它曾经是基础知识。”

浮点模运算符定义如下:

m = num - iquot*den ; where iquot = int( num/den )

如图所示,浮点数上的%运算符的无操作似乎与标准有关。CRTL提供“ fmod”(通常也“剩余”)以对fp编号执行%。两者之间的区别在于它们如何处理中间的“ iquot”舍入。

“剩余”使用最接近取整,“ fmod”使用简单的截断。

如果您编写自己的C ++数值类,则没有任何事情可以通过包含重载的运算符%来修改无操作传统。

最好的祝福

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.