如何安全地将 `unsigned long int` 转换为 `int`?

10
我有一个应用程序,它使用unsigned long int形式创建唯一的ids。这个应用程序需要这种精度。
但是,我必须将这些ids发送到仅允许int的协议中。接收协议的应用程序不需要这种精度。那么我的问题是:如何将unsigned long int转换为int,特别是当unsigned long int大于int时?

编辑:

协议仅支持int。 我想知道如何避免“翻滚问题”。
发送消息的应用程序需要长时间保证唯一性,而接收方只需要在短时间内知道唯一性。

1
如果id > max<int>,你会得到一个“随机”数字。这有点难以确定是否对你造成问题 - 在某种程度上取决于接收者... - MFH
@ MFH 我正在通过OSC协议将值发送到仅支持int的硬件。你回答了这个问题:“你会得到一个“随机”的数字” - Ross
@AndreyT 抱歉,你是正确的。我希望保持一些独特性到这个id。我希望我最后的编辑解释了这一点。 - Ross
如果协议不需要更大的范围,为什么应用程序需要呢? - Lightness Races in Orbit
这是三年前的事情了,但既然你问了,那些id是由应用程序中的一个库生成的。 - Ross
显示剩余7条评论
6个回答

16

这里有一个可能的方法:

#include <climits>
unsigned long int uid = ...;
int abbreviated_uid = uid & INT_MAX;
如果int是32位,例如,这将丢弃除UID的低序31位之外的所有内容。它仅产生非负值。
这会丢失原始uid中的信息,但您表示这不是问题。
但是您的问题不够明确,很难确定是否适合您的目的。

抱歉表述不够清晰。发送消息的应用程序需要长时间知道唯一性,而接收者只需要在短时间内知道唯一性。 - Ross

4
Boost有一个名为numeric_cast的函数:numeric_cast
unsigned long l = ...;
int i = boost::numeric_cast<int>(l);

如果转换会导致溢出,这将引发异常,这可能是你想要的,也可能不是。

3
Keith Thompson的 "& INT_MAX" 只有在需要确保 abbreviated_uid 非负时才是必要的。如果这不是一个问题,并且您可以容忍负ID,则一个简单的强制类型转换(C风格或static_cast())应该就足够了。好处是,如果 sizeof(unsigned long int)==sizeof(int),则二进制表示将在两端相同(如果您在接收端将其转换回 unsigned long int,则与发送端上的值相同)。
接收者是否向发送者发送关于 ID 的响应,原始发送者(现在是响应的接收者)需要将其与原始的 unsigned long int ID 匹配起来吗?如果是这样,您将需要一些额外的逻辑来将响应与原始 ID 匹配起来。如果是这样,请发表编辑,指明这种要求,我(或其他人)可以提出解决这个问题的方法。解决这个问题的一个可能的解决方案是将 ID 分解成多个 int 片段,并在另一端将其重构为完全相同的 unsigned long int 值。如果您需要帮助,我或其他人可以提供帮助。

顺便说一下,即使类型大小相同,在将无符号长整型强制转换为整型后,位表示并不一定保持不变。尽管在所有有用的C++实现中这可能是正确的。 - Steve Jessop
@SteveJessop - 对此我没有异议,从纯粹的角度来看,我同意。理论上,C++实现可以以不适合在本地硬件平台上进行直接计算的方式表示整数,但这将非常低效,因此我认为在几乎所有情况下,这更多是处理器(CPU)以及它如何存储有符号和无符号整数的问题。我曾经在许多2的补码系统上工作过,在这些系统中,将大小相同的整数在有符号和无符号之间进行转换不会改变其二进制表示,只会改变其解释方式。... - phonetagger
我从未在使用其他表示法(如符号和大小、1的补码等)的系统上工作过,但我猜测类型转换是类似的;即仅改变了二进制值的解释方式,因为与2的补码一样,正值无论被解释为有符号还是无符号,其表示方式都相同。你曾经在非2的补码系统上工作过吗? - phonetagger
不,我没有。我能想到唯一的实际原因是在一个符号-幅度系统中,负零是一个陷阱表示,(unsigned long)INT_MAX+1必须转换为其他值,对于1s'补码和ULONG_MAX也是如此。 - Steve Jessop

3

正如您所知,在一般情况下,理论上不能安全地将unsigned long int转换为int。但是,在许多实际感兴趣的情况下,当整数不太大时,的确可以这样做。

我可能会定义并使用以下内容:

struct Exc_out_of_range {};

int make_int(const unsigned long int a) {
    const int n = static_cast<int>(a);
    const unsigned long int a2 = static_cast<unsigned long int>(n);
    if (a2 != a) throw Exc_out_of_range();
    return n;
}

使用<limits>头文件也可以实现相同的解决方案,但我不确定它是否比上述方法更好。(如果代码在时间关键循环中,并且可移植性不是问题,则可以使用汇编语言编写代码,直接测试感兴趣的位或位,但除了作为汇编语言练习外,这将会很麻烦。)
关于性能,值得注意的是——除非使用,否则throw不会产生运行时负担。
@GManNickG建议从std::exception继承。我个人对此没有强烈的感觉,但这是很好的建议,受到赞赏,我认为没有理由不遵循它。您可以在此处阅读有关此类继承的更多信息。

不要创建不继承自std::exception的异常类型。在这种情况下,您可能需要使用std::domain_error - GManNickG

1

我遇到了这个问题,因为我需要一个将较大整数类型转换为较小类型的解决方案,即使会有信息丢失。

我使用模板想出了一个非常好的解决方案:

template<typename Tout, typename Tin>
Tout toInt(Tin in)
{
    Tout retVal = 0;

    if (in > 0)
        retVal = static_cast<Tout>(in & std::numeric_limits<Tout>::max());
    else if (in < 0)
        retVal = static_cast<Tout>(in | std::numeric_limits<Tout>::min());

    return retVal;
}

0
你可以尝试使用 std::stringstreamatoi()
#include <sstream>
#include <stdlib.h>
unsigned long int a = ...;
std::stringstream ss;
ss << a;
std::string str = ss.str();
int i = atoi(str.c_str());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接