【问题标题】:Converting string to unsigned int returns the wrong result将字符串转换为无符号整数会返回错误的结果
【发布时间】:2013-08-04 23:02:46
【问题描述】:

我有以下字符串:

sThis = "2154910440";

unsigned int iStart=atoi(sThis.c_str());

结果是

iStart = 2147483647

有人看到我的错误吗?

【问题讨论】:

  • 我注意到你的结果是 2^31 - 1。我认为这是 atoi() 使用最高位作为符号位的问题。也许仔细阅读 atoi() 文档以了解它对标志的作用?

标签: c++ type-conversion


【解决方案1】:

atoi 将字符串转换为int。在您的系统上,int 为 32 位,其最大值为 2147483647。您尝试转换的值超出此范围,因此 atoi 的返回值未定义。我猜你的实现在这种情况下会返回 int 的最大值。

您可以改用 atoll,它返回一个 long long,保证至少为 64 位。或者,您可以使用 stoi/stol/stoll 系列或他们的 unsigned counterparts 中的函数,它实际上会以异常的形式提供有关超出范围值(和无效值)的有用错误报告。

就个人而言,我喜欢boost::lexical_cast。尽管它看起来有点麻烦,但它可以在更一般的上下文中使用。您可以在模板中使用它,只需转发类型参数,而不必进行专门化

【讨论】:

  • OP 没有说他使用的是 C++11。
  • @Kyle_the_hacker:嗯,现在是 2013 年,所以除非他另有说明,否则我将继续假设我的答案。
  • @BenjaminLindley 有点忽视现实。
  • @JamesKanze:说实话,即使 OP 明确声明他不能使用 C++11,我也不会遗漏 C++11 的信息。无论如何,我确实提供了 C++98/03 替代方案,那有什么问题?
  • @tmighty:你的编译器部分支持 C++11,你的标准库确实有我提到的函数族。
【解决方案2】:

您应该改用<cstdlib> 中的std::strtoul,它专为无符号数设计,范围更大,报告错误更好。

如果您想使用std::string 进行输入并使用异常处理错误,请使用std::stoul。一个简短而高效的实现如下:

#include <string>
#include <stdexcept>
inline unsigned int stoui(const std::string& s)
{
    unsigned long lresult = stoul(s, 0, 10);
    unsigned int result = lresult;
    if (result != lresult) throw std::out_of_range();
    return result;
}

这将比istringstream 快得多,文化不变(因此在不寻常的语言环境中运行时不会发生意外的行为变化),完全可移植,并且使用第三个参数,您可以支持不同的数字基数甚至执行检测0x0 前缀。

但是unsigned int 不一定大到足以容纳你的价值,所以使用unsigned long,然后你就不需要上面的包装器了。

【讨论】:

  • 我同意,strtoul 更便携
  • 据我所知 unsigned long 在某些平台和操作系统上与 unsigned int 不同。在 x86_64 架构的 Linux 中,它的大小应该不同。
  • @Sergey:是的,这就是为什么strtoul 是更好的选择。然后可以将强制转换前后的值与unsigned int 进行比较,看是否过大。
  • @BenVoigt:我不明白。为什么在降级到 32 位之前获得潜在的 64 位值并进行额外检查比直接解析为无符号 32 更好?我在这里只看到从 64 位转换为 32 位和编译器警告后潜在的“奇怪”数据损坏。
  • @Sergey:你需要 unsigned long 来保存这个值。此外,当您在答案中完全没有提供任何错误处理时,您抱怨我建议的错误检测逻辑,我也觉得很奇怪。
【解决方案3】:

atoi 返回一个带符号的 int,在您的平台上它的最大值为 2^31-1

无论您将该结果分配给什么,它都会受到返回类型的限制。

C++ 流可以读取无符号整数。

std::istringstream reader(sThis);
unsigned int val;
reader >> val;

【讨论】:

    【解决方案4】:

    不要忘记,您始终可以编写自己的函数来完全完成您想要的操作。

    此代码适用于介于 -9223372036854775806 (2^63+1) 和 9223372036854775807 (2^63-1) 之间的任何数字

    类似这样的:

    long long int myAtoi ( string str ) {
        long long int value = 0;
    
        for (int i = 0; i < str.size(); i++) {
    
            if (str[i] != '-') {
                value *=  10;
                value += (int) ((str[i]) - '0');
            }
        }
    
    
        if (str.size() > 0 && str[0] == '-')
            return -value;
        else
            return value;
    }
    

    【讨论】:

    • 鉴于此函数对减号的处理过于宽松,并且会通过返回 -1033589 来处理类似“-----10----33---5-8-9-”的字符串。
    【解决方案5】:

    无符号整数通常是 C++ 中的 32 位值,最大值为 4,294,967,295。 因此,2,154,710,440 可以表示为无符号整数。但是, atoi 转换为带符号且最大值为 2,147,483,647 的 int - 因此您的字符串溢出了值范围,这就是您的答案不正确的原因。您可以使用 atoll 将您的字符串转换为至少 64 位的 long long。 整数大小在 C++ 中取决于编译器。通常最好包含头文件 stdint.h 然后使用 uint32_t 或 uint64_t 等等,这样你就知道你正在处理的大小。

    【讨论】:

      【解决方案6】:

      您可以使用 atol 将字符串转换为 long int 。 要了解更多信息,请参阅 Linux 中的 man atol。

      原型

      #include <stdlib.h>
      long atol(const char *nptr);
      

      【讨论】:

        【解决方案7】:

        不幸的是,C++ 没有用于解析 unsigned int 的嵌入式实现,这真的很奇怪。

        这是一个可以帮助你的代码:

        #include <stdint.h>
        #include <sstream>
        
        inline unsigned int stoui(const std::string& s)
        {
            std::istringstream reader(s);
            unsigned int val = 0;
            reader >> val;
            return val;
        }
        
        // This may be not the same as stoui on some platforms:
        inline uint32_t stoui32(const std::string& s)
        {
            std::istringstream reader(s);
            uint32_t val = 0;
            reader >> val;
            return val;
        }
        

        【讨论】:

        • 这是因为没有人需要它。 strtoul 非常适合解析任何有效的 unsigned int 值,并且检测溢出只需要一个额外的比较。简而言之,这样做比您提供的损坏(完全缺乏错误检测)代码效率高出几个数量级。
        • 顺便说一句,反对an existing answer
        • @BenVoigt:您提出的解决方案不好。不幸的是,我无法将代码示例放在这里,所以我更新了对主要问题的回复。
        • 不,这不是我的建议。
        • 我编辑了我的答案以显示我的意思是“然后可以将强制前后的值与 unsigned int 进行比较以查看它是否太大”
        【解决方案8】:

        此代码将使用 C++11 对其进行转换:

        std::string sThis = "2154910440";
        unsigned int iStart = static_cast<unsigned int>(std::stoul(sThis));
        

        std::stoul 将返回一个大于unsigned intunsigned long

        static_cast 会将其转换为正确的类型。

        【讨论】:

        • 一个unsigned long 可能大于unsigned int - 但也允许相同大小。
        • static_cast 会不会抛出错误,类似于@BenVoigt 的解决方案?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-08-19
        • 1970-01-01
        • 2013-03-10
        • 1970-01-01
        • 2012-11-12
        • 2011-06-14
        相关资源
        最近更新 更多