如何在size_t上使用余数运算符得到负余数?

时间:2016-07-01 13:12:24

标签: c++ implicit-conversion size-t

请考虑以下代码示例:

#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_tunsigned long long会怎么样?同样的问题。

您如何以便携和健壮的方式解决此问题?

2 个答案:

答案 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 longT -> int

正如评论中所指出的,使用std::llabs代替std::abs非常重要,因为如果aint的最小可能值,则删除该符号将溢出。在获胜之前将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,即可获得正确的结果。

导致未定义行为的唯一情况是astd::numeric_limits<long long>::min(),因为删除了符号溢出a,导致未定义的行为。可能还有另一种方法来实现这个功能,我会考虑它。