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

How to get negative remainder with remainder operator on size_t?

本文关键字:余数 运算符 size      更新时间:2023-10-16

考虑以下代码示例:

#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,这是一个依赖于实现的无符号类型,并且因为使用二进制操作符发生的隐式类型转换导致numsigned int转换为无符号size_t(更多信息在这里);这将导致负值变为正值,并使操作结果混乱。

可以考虑通过显式强制转换到int来解决这个问题:

num % (int)str.length()

这可能有效,但不能保证,例如在长度大于int最大值的字符串的情况下。可以使用更大的类型来降低风险,比如long long,但是如果size_tunsigned long long呢?同样的问题。

您将如何以可移植和健壮的方式解决这个问题?

从c++ 11开始,您可以将length的结果强制转换为std::string::difference_type

解决"但是如果尺寸太大怎么办?":

这不会发生在64位平台上,即使你在一个较小的平台上:你最后一次有一个字符串占用了总内存的一半以上是什么时候?除非你正在做非常具体的事情(你会知道),使用difference_type是很好的;别打鬼了。

或者,只使用int64_t,这当然足够大了。(虽然可能在一些32位处理器上循环一个比int32_t慢,我不知道。对于单个模数运算来说,这并不重要)

有趣的事实:甚至一些著名的委员会成员也认为在标准库中乱扔无符号类型是错误的,参见参考资料这个面板在9:50,42:40,1:02:50)

在c++ 11之前,带有负值的%符号是实现定义的,对于定义良好的行为,使用std::div加上上面描述的强制转换之一。

我们知道

-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已经从std::llabs(a)"unsigned"了,所以值是不变的(即没有溢出/下溢)。

由于上述属性,如果a是负的,将结果与-1相乘,得到正确的结果。


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