作为一名数学家,我对C衍生语言之一的一个小恶习是
(-1) % 8 // comes out as -1, and not 7
fmodf(-1,8) // fails similarly
最佳解决方案是什么?
C++允许使用模板和操作符重载,但这两者对我来说都是比较深奥的领域。欢迎提供示例。
作为一名数学家,我对C衍生语言之一的一个小恶习是
(-1) % 8 // comes out as -1, and not 7
fmodf(-1,8) // fails similarly
最佳解决方案是什么?
C++允许使用模板和操作符重载,但这两者对我来说都是比较深奥的领域。欢迎提供示例。
(-1) % 8 == -1
这样的事实。唯一可以依赖的是 (x / y) * y + ( x % y) == x
。但余数是否为负数是由具体实现定义的。mod(-1,8)
的结果为7,而 mod(13,-8)
则为-3。int mod(int a, int b)
{
if(b < 0) //you can check for b == 0 separately and do what you want
return -mod(-a, -b);
int ret = a % b;
if(ret < 0)
ret+=b;
return ret;
}
INT_MIN / -1
(在二进制补码实现中)造成问题。根据旧规范,-32768%-1
可能必须评估为-65536
(也不在16位类型的范围内,呃!)以满足标识。 - Ben Voigt这是一个处理正数或负数整数或小数值的C函数,适用于两个运算数。
#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)
从数学角度来看,这肯定是最优雅的解决方案。但是我不确定它在处理整数时是否强大。有时候在将int -> fp -> int转换时会出现浮点误差。
我正在使用这段代码处理非整数s,并使用另一个函数处理整数。
注意:需要捕获N = 0!
测试代码:
#include <math.h>
#include <stdio.h>
float mod(float a, float N)
{
float ret = a - N * floor (a / N);
printf("%f.1 mod %f.1 = %f.1 \n", a, N, ret);
return ret;
}
int main (char* argc, char** argv)
{
printf ("fmodf(-10.2, 2.0) = %f.1 == FAIL! \n\n", fmodf(-10.2, 2.0));
float x;
x = mod(10.2f, 2.0f);
x = mod(10.2f, -2.0f);
x = mod(-10.2f, 2.0f);
x = mod(-10.2f, -2.0f);
return 0;
}
(注意:您可以直接在CodePad上编译和运行它:http://codepad.org/UOgEqAMA)
输出:
fmodf(-10.2, 2.0) = -0.20 == 失败!
10.2 mod 2.0 = 0.2
10.2 mod -2.0 = -1.8
-10.2 mod 2.0 = 1.8
-10.2 mod -2.0 = -0.2
floor()
。此外,当您转换为浮点数时,可能会失去精度:尝试 (float)1000000001/3
,您会惊讶于结果! - cmaster - reinstate monicaa
参数的fmod()
函数的不良用法。 - Kingsley%
标记为余数运算符,而不是模运算符。int modulo(int x,int N){
return (x % N + N) %N;
}
(((x < 0) ? ((x % N) + N) : x) % N)
我假设N
是正数并且可以在x
的类型中表示。您喜欢使用的编译器应该能够优化此操作,使其最终成为汇编中的一个模运算。
int x=-9001; unsigned int N=2000;
,它给出的结果是2295,而不是999。 - Hubert Kario(x < 0) ? (x % N + N) : (x % N)
。 - Chris Nolet以下是一篇基于微软研究论文及其参考文献的关于一个旧问题的新答案。
请注意,从C11和C++11开始,div
的语义变为朝零截尾(参见[expr.mul]/4
)。此外,对于D
除以d
,C++11保证商qT
和余数rT
满足以下条件:
auto const qT = D / d;
auto const rT = D % d;
assert(D == d * qT + rT);
assert(abs(rT) < abs(d));
assert(signum(rT) == signum(D) || rT == 0);
signum
将其参数映射为 -1、0、+1,具体取决于其参数是 <、== 还是 > 0(有关源代码,请参见 this Q&A)。
通过截断除法,余数的符号等于被除数 D
的符号,即 -1 % 8 == -1
。C++11 还提供了一个 std::div
函数,根据截断除法返回一个带有成员 quot
和 rem
的结构体。
还有其他可能的定义,例如所谓的向下取整除法可以根据内置的截断除法来定义。
auto const I = signum(rT) == -signum(d) ? 1 : 0;
auto const qF = qT - I;
auto const rF = rT + I * d;
assert(D == d * qF + rF);
assert(abs(rF) < abs(d));
assert(signum(rF) == signum(d));
d
的符号。在Haskell和Oberon等语言中,有内置的向下取整除法运算符。在C++中,您需要编写一个使用上述定义的函数。auto const I = rT >= 0 ? 0 : (d > 0 ? 1 : -1);
auto const qE = qT - I;
auto const rE = rT + I * d;
assert(D == d * qE + rE);
assert(abs(rE) < abs(d));
assert(signum(rE) >= 0);
assert(signum(rT) == signum(D));
can definitely fail. Correct statement: signum(rT)
is a member of the set { 0
, signum(D)
}, or as an assert assert(rT == 0 || signum(rT) == signum(D));
- Ben VoigtD = 10
和 d = 5
- Ben Voigt对于数学家来说,最好的解决方案¹是使用Python。
C++运算符重载与此无关。您无法为内置类型重载运算符。您想要的只是一个函数。当然,您可以使用C++模板将该函数实现为所有相关类型的1个代码片段。
标准C库提供了fmod
,如果我记得名字正确的话,用于浮点类型。
对于整数,您可以定义一个C++函数模板,它始终返回非负余数(对应于欧几里得除法)......
#include <stdlib.h> // abs
template< class Integer >
auto mod( Integer a, Integer b )
-> Integer
{
Integer const r = a%b;
return (r < 0? r + abs( b ) : r);
}
...只需编写mod(a, b)
,而不是a%b
。
在这里,类型Integer
需要是有符号整数类型。
如果您希望具有常见的数学行为,其中余数的符号与除数的符号相同,则可以执行以下操作:
template< class Integer >
auto floor_div( Integer const a, Integer const b )
-> Integer
{
bool const a_is_negative = (a < 0);
bool const b_is_negative = (b < 0);
bool const change_sign = (a_is_negative != b_is_negative);
Integer const abs_b = abs( b );
Integer const abs_a_plus = abs( a ) + (change_sign? abs_b - 1 : 0);
Integer const quot = abs_a_plus / abs_b;
return (change_sign? -quot : quot);
}
template< class Integer >
auto floor_mod( Integer const a, Integer const b )
-> Integer
{ return a - b*floor_div( a, b ); }
… 在相同的限制条件下,Integer
是有符号类型。
¹ 因为 Python 的整数除法向负无穷取整。
r
的结果必须使 a = r + b*(a/b)
成立。无论整数除法如何实现,b*something
都是 b
的倍数。即使为负数,这使得 r
成为有效的模数结果。您可以将 abs(b
) 加到它上面,它仍然是一个有效的模数结果。 - Cheers and hth. - Alf如果要使用不带分支和只有1个模运算的解决方案,您可以执行以下操作:
// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}
哦,我也讨厌这种百分号设计……
你可以通过以下方式将除数转换为无符号数:
unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider
result = (offset + dividend) % divider
偏移量应该尽可能靠近模数的(-INT_MIN)倍,这样加减它不会改变模数。请注意,它具有无符号类型,结果将为整数。不幸的是,它不能正确地转换值INT_MIN...(-offset-1),因为它们会导致算术溢出。但是,在使用常数除法时,此方法每个操作仅需要单个附加算术(和无条件语句),因此在类DSP应用程序中可用。
有一种特殊情况,即除数为2的N次方(整数的二次幂),可以使用简单的算术和按位逻辑计算模数,如下所示:
dividend&(divider-1)
例如
x mod 2 = x & 1
x mod 4 = x & 3
x mod 8 = x & 7
x mod 16 = x & 15
更常见且不那么棘手的方法是使用此函数获取模数(仅适用于正除数):
int mod(int x, int y) {
int r = x%y;
return r<0?r+y:r;
}
如果结果为负数,这只是正确的结果。
还有一个技巧:
(p%q + q)%q
这很简短,但使用了两个通常很慢的%-s。
(-1) & 8 == 7
- Henricus V.(-1) & 8 == 7
?(-1) & 8
只会产生0或8,而不是7。也许你的意思是(-1) & (8-1)
? - chux - Reinstate Monica