将有符号 32 位整数与无符号 64 位整数相加
Sum signed 32-bit int with unsigned 64bit int
在我的申请中,我收到了两个 signed 32-bit int
并且我必须存储它们。我必须创建一种计数器,我不知道它什么时候会被重置,但我会经常收到很大的价值。因此,为了存储这些值,我决定使用两个 unsigned 64-bit int
.
以下可能是计数器的简单版本。
struct Counter
{
unsigned int elementNr;
unsigned __int64 totalLen1;
unsigned __int64 totalLen2;
void UpdateCounter(int len1, int len2)
{
if(len1 > 0 && len2 > 0)
{
++elementNr;
totalLen1 += len1;
totalLen2 += len2;
}
}
}
我知道如果将较小的类型强制转换为较大的类型(例如从 int 到 long)应该没有问题。然而,同时从 32 位表示传递到 64 位表示传递以及从有符号传递到无符号传递,对我来说是新事物。
看了一圈,我明白了 len1
应该从 32 位扩展到 64 位,然后应用符号扩展。因为unsigned int
和signen int
有相同的等级(Section 4.13),所以应该转换后者。
如果 len1
存储负值,从有符号传递到无符号将 return 一个错误的值,这就是我在函数开头检查正数的原因。然而,对于正值,有
我觉得应该没问题。
为清楚起见,我可以像这样重写 UpdateCounter(int len1, int len2)
void UpdateCounter(int len1, int len2)
{
if(len1 > 0 && len2 > 0)
{
++elementNr;
__int64 tmp = len1;
totalLen1 += static_cast<unsigned __int64>(tmp);
tmp = len2;
totalLen2 += static_cast<unsigned __int64>(tmp);
}
}
可能有一些我没有考虑过的副作用。
还有其他更好更安全的方法吗?
一点背景,仅供参考:二元运算符这种算术加法作用于相同类型的操作数(具体的 CPU 指令被翻译成取决于数字表示形式,两者必须相同指令操作数)。
当你写这样的东西时(使用 fixed width integer types 来明确):
int32_t a = <some value>;
uint64_t sum = 0;
sum += a;
如您所知,这涉及 implicit conversion,更具体地说
integral promotion according to integer conversion rank。
所以表达式 sum += a;
等价于 sum += static_cast<uint64_t>(a);
,所以 a
被提升为具有较低的等级。
让我们看看这个例子中会发生什么:
int32_t a = 60;
uint64_t sum = 100;
sum += static_cast<uint64_t>(a);
std::cout << "a=" << static_cast<uint64_t>(a) << " sum=" << sum << '\n';
输出为:
a=60 sum=160
一切如期而至。让我们看看添加负数会发生什么:
int32_t a = -60;
uint64_t sum = 100;
sum += static_cast<uint64_t>(a);
std::cout << "a=" << static_cast<uint64_t>(a) << " sum=" << sum << '\n';
输出为:
a=18446744073709551556 sum=40
结果如预期40
:这依赖于二进制补码整数表示(注意:无符号整数溢出不是未定义的行为)并且一切正常,当然只要你确保总和不会变成负数。
回到你的问题,如果你总是添加正数或者至少确保总和永远不会是负数,你不会有任何惊喜......直到你达到最大可表示值std::numeric_limits<uint64_t>::max()
(2 ^64-1 = 18446744073709551615 ~ 1.8E19)。
如果您继续无限期地添加数字,迟早会达到该限制(这对您的计数器 elementNr
也有效)。
您将通过每毫秒添加 2^31-1 (2147483647) 来溢出 64 位无符号整数,持续大约三个月,因此在这种情况下,建议检查:
#include <limits>
//...
void UpdateCounter(const int32_t len1, const int32_t len2)
{
if( len1>0 )
{
if( static_cast<decltype(totalLen1)>(len1) <= std::numeric_limits<decltype(totalLen1)>::max()-totalLen1 )
{
totalLen1 += len1;
}
else
{// Would overflow!!
// Do something
}
}
}
当我必须累加数字并且我对准确性没有特殊要求时,我经常使用 double
因为最大可表示值非常高 (std::numeric_limits<double>::max()
1.79769E+308
) 并且为了达到溢出,我需要在 1E+279 年内每皮秒添加 2^32-1=4294967295。
在我的申请中,我收到了两个 signed 32-bit int
并且我必须存储它们。我必须创建一种计数器,我不知道它什么时候会被重置,但我会经常收到很大的价值。因此,为了存储这些值,我决定使用两个 unsigned 64-bit int
.
以下可能是计数器的简单版本。
struct Counter
{
unsigned int elementNr;
unsigned __int64 totalLen1;
unsigned __int64 totalLen2;
void UpdateCounter(int len1, int len2)
{
if(len1 > 0 && len2 > 0)
{
++elementNr;
totalLen1 += len1;
totalLen2 += len2;
}
}
}
我知道如果将较小的类型强制转换为较大的类型(例如从 int 到 long)应该没有问题。然而,同时从 32 位表示传递到 64 位表示传递以及从有符号传递到无符号传递,对我来说是新事物。
看了一圈,我明白了 len1
应该从 32 位扩展到 64 位,然后应用符号扩展。因为unsigned int
和signen int
有相同的等级(Section 4.13),所以应该转换后者。
如果 len1
存储负值,从有符号传递到无符号将 return 一个错误的值,这就是我在函数开头检查正数的原因。然而,对于正值,有
我觉得应该没问题。
为清楚起见,我可以像这样重写 UpdateCounter(int len1, int len2)
void UpdateCounter(int len1, int len2)
{
if(len1 > 0 && len2 > 0)
{
++elementNr;
__int64 tmp = len1;
totalLen1 += static_cast<unsigned __int64>(tmp);
tmp = len2;
totalLen2 += static_cast<unsigned __int64>(tmp);
}
}
可能有一些我没有考虑过的副作用。 还有其他更好更安全的方法吗?
一点背景,仅供参考:二元运算符这种算术加法作用于相同类型的操作数(具体的 CPU 指令被翻译成取决于数字表示形式,两者必须相同指令操作数)。 当你写这样的东西时(使用 fixed width integer types 来明确):
int32_t a = <some value>;
uint64_t sum = 0;
sum += a;
如您所知,这涉及 implicit conversion,更具体地说
integral promotion according to integer conversion rank。
所以表达式 sum += a;
等价于 sum += static_cast<uint64_t>(a);
,所以 a
被提升为具有较低的等级。
让我们看看这个例子中会发生什么:
int32_t a = 60;
uint64_t sum = 100;
sum += static_cast<uint64_t>(a);
std::cout << "a=" << static_cast<uint64_t>(a) << " sum=" << sum << '\n';
输出为:
a=60 sum=160
一切如期而至。让我们看看添加负数会发生什么:
int32_t a = -60;
uint64_t sum = 100;
sum += static_cast<uint64_t>(a);
std::cout << "a=" << static_cast<uint64_t>(a) << " sum=" << sum << '\n';
输出为:
a=18446744073709551556 sum=40
结果如预期40
:这依赖于二进制补码整数表示(注意:无符号整数溢出不是未定义的行为)并且一切正常,当然只要你确保总和不会变成负数。
回到你的问题,如果你总是添加正数或者至少确保总和永远不会是负数,你不会有任何惊喜......直到你达到最大可表示值std::numeric_limits<uint64_t>::max()
(2 ^64-1 = 18446744073709551615 ~ 1.8E19)。
如果您继续无限期地添加数字,迟早会达到该限制(这对您的计数器 elementNr
也有效)。
您将通过每毫秒添加 2^31-1 (2147483647) 来溢出 64 位无符号整数,持续大约三个月,因此在这种情况下,建议检查:
#include <limits>
//...
void UpdateCounter(const int32_t len1, const int32_t len2)
{
if( len1>0 )
{
if( static_cast<decltype(totalLen1)>(len1) <= std::numeric_limits<decltype(totalLen1)>::max()-totalLen1 )
{
totalLen1 += len1;
}
else
{// Would overflow!!
// Do something
}
}
}
当我必须累加数字并且我对准确性没有特殊要求时,我经常使用 double
因为最大可表示值非常高 (std::numeric_limits<double>::max()
1.79769E+308
) 并且为了达到溢出,我需要在 1E+279 年内每皮秒添加 2^32-1=4294967295。