如何使用 size_t 上的余数运算符获得负余数?
How to get negative remainder with remainder operator on 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_t
是 unsigned long long
怎么办?同样的问题。
您将如何以可移植且稳健的方式解决这个问题?
自 C++11 起,您只需将 length
的结果转换为 std::string::difference_type
。
地址 "But what if the size is too big?":
在 64 位平台上不会发生这种情况,即使您在较小的平台上也是如此:您最后一次实际拥有一个字符串占用超过总 RAM 一半的时间是什么时候?除非你正在做非常具体的事情(你会知道),否则使用 difference_type
就可以了;别打鬼了。
或者直接用int64_t
,肯定够大了。 (虽然在某些 32 位处理器上循环一个可能比 int32_t
慢,我不知道。不过对于那个单模运算来说无关紧要。)
(有趣的事实:甚至一些杰出的委员会成员也认为在标准库中乱放无符号类型是错误的,参考参见
this panel 在 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 long
和 T -> int
。
正如评论中指出的那样,使用 std::llabs
而不是 std::abs
很重要,因为如果 a
是 int
的最小可能值,则删除符号会溢出。之前将 a
提升为 long long
不会导致此问题,因为 long long
具有更大的值范围。
现在 static_cast<int>(std::llabs(a) % b)
将始终生成小于 a
的值,因此将其转换为 int
永远不会 overflow/underflow。即使 a
被提升为 unsigned long long
,也没关系,因为 a
已经是 std::llabs(a)
的 "unsigned",因此值不变(即没有 overflow/underflow).
因为上面说的属性,如果a
是负数,乘以-1
就可以得到正确的结果。
导致未定义行为的唯一情况是当 a
为 std::numeric_limits<long long>::min()
时,因为删除符号会溢出 a
,从而导致未定义行为。大概还有别的实现方式,我考虑一下。
考虑以下代码示例:
#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
怎么办?同样的问题。
您将如何以可移植且稳健的方式解决这个问题?
自 C++11 起,您只需将 length
的结果转换为 std::string::difference_type
。
地址 "But what if the size is too big?":
在 64 位平台上不会发生这种情况,即使您在较小的平台上也是如此:您最后一次实际拥有一个字符串占用超过总 RAM 一半的时间是什么时候?除非你正在做非常具体的事情(你会知道),否则使用 difference_type
就可以了;别打鬼了。
或者直接用int64_t
,肯定够大了。 (虽然在某些 32 位处理器上循环一个可能比 int32_t
慢,我不知道。不过对于那个单模运算来说无关紧要。)
(有趣的事实:甚至一些杰出的委员会成员也认为在标准库中乱放无符号类型是错误的,参考参见 this panel 在 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 long
和 T -> int
。
正如评论中指出的那样,使用 std::llabs
而不是 std::abs
很重要,因为如果 a
是 int
的最小可能值,则删除符号会溢出。之前将 a
提升为 long long
不会导致此问题,因为 long long
具有更大的值范围。
现在 static_cast<int>(std::llabs(a) % b)
将始终生成小于 a
的值,因此将其转换为 int
永远不会 overflow/underflow。即使 a
被提升为 unsigned long long
,也没关系,因为 a
已经是 std::llabs(a)
的 "unsigned",因此值不变(即没有 overflow/underflow).
因为上面说的属性,如果a
是负数,乘以-1
就可以得到正确的结果。
导致未定义行为的唯一情况是当 a
为 std::numeric_limits<long long>::min()
时,因为删除符号会溢出 a
,从而导致未定义行为。大概还有别的实现方式,我考虑一下。