请考虑以下代码示例:
#include <iostream>
#include <string>
int main()
{
std::string str("someString"); // length 10
int num = -11;
std::cout << num % str.length() << std::endl;
}
在http://cpp.sh上运行此代码后,我得到了5
,而我期望它是-1
。
我知道发生这种情况是因为str.length()
的类型是size_t
,这是一个依赖于实现的无符号,并且由于二进制运算符发生的隐式类型转换导致num
到从signed int
转换为无符号size_t
(更多here);
这会导致负值成为正值,并且会影响操作的结果。
可以考虑通过明确转换为int
来解决问题:
num % (int)str.length()
这可能有效,但不能保证,例如在长度大于int
的最大值的字符串的情况下。可以使用更大的类型来降低风险,例如long long
,但如果size_t
是unsigned long long
会怎么样?同样的问题。
您如何以便携和健壮的方式解决此问题?
答案 0 :(得分:3)
从C ++ 11开始,您只需将length
的结果转换为std::string::difference_type
。
要解决“但如果尺寸太大会怎么样?”:
这不会发生在64位平台上,即使你是一个较小的平台:你最后一次实际拥有占用总RAM超过一半的字符串是什么时候?除非你正在做特定的事情(你会知道),使用difference_type
就好了;戒掉鬼魂。
或者,只需使用int64_t
,这当然足够大。 (虽然在某些32位处理器上循环可能比int32_t
慢,但我不知道。但是对于单模运算来说无关紧要。)
(有趣的事实:甚至一些着名的委员会成员认为乱丢标准库与无符号类型是一个错误,供参考 this panel在9:50,42:40,1:02:50)
Pre C ++ 11,带有负值的%
的符号是实现定义的,对于定义良好的行为,使用std::div
加上上述的一个强制转换。
答案 1 :(得分:3)
我们知道
-a % b == -(a % b)
所以你可以这样写:
template<typename T, typename T2>
constexpr T safeModulo(T a, T2 b)
{
return (a >= 0 ? 1 : -1) * static_cast<T>(std::llabs(a) % b);
}
99.98%的案例不会溢出,因为考虑到这个
safeModulo(num, str.length());
如果将std::size_t
实施为unsigned long long
,则T2 -> unsigned long long
和T -> int
。
正如评论中所指出的,使用std::llabs
代替std::abs
非常重要,因为如果a
是int
的最小可能值,则删除该符号将溢出。在获胜之前将a
提升为long long
会导致此问题,因为long long
的值范围更广。
现在static_cast<int>(std::llabs(a) % b)
将始终生成小于a
的值,因此将其转换为int
将永远不会溢出/下溢。即使a
被提升为unsigned long long
,也无关紧要,因为a
已经&#34;未签名&#34;来自std::llabs(a)
,因此值不变(即没有溢出/下溢)。
由于上述属性,如果a
为负数,请将结果乘以-1
,即可获得正确的结果。
导致未定义行为的唯一情况是a
为std::numeric_limits<long long>::min()
,因为删除了符号溢出a
,导致未定义的行为。可能还有另一种方法来实现这个功能,我会考虑它。