具有整数溢出条件的整数的反转

Posted

技术标签:

【中文标题】具有整数溢出条件的整数的反转【英文标题】:Reverse of a Integer with integer overflow condition 【发布时间】:2017-10-28 23:45:07 【问题描述】:
long long  reverse(long long x) 
    long long reversednum=0;
    int sign=1;
    if(x<0)
        sign=-1;
    x=abs(x);

    while(x)
    
        reversednum=reversednum*10;
        reversednum=reversednum+(x%10);
        x=x/10;
    

    return (reversednum<INT_MAX || reversednum>INT_MIN)?reversednum*sign:0;

我的大多数测试用例都得到了满足,除了这个 1534236469 返回输出 1056389759。我看到了很多建议并将其更改为 long long。但仍然产生相同的结果。希望有人能帮助我。提前致谢!! 输入假定为 32 位有符号整数。当反转整数溢出时,我的函数应该返回 0

【问题讨论】:

有符号整数溢出未定义;您必须在 执行操作之前检查溢出。 INT_MAXlong long 也来自不同的整数域。 @ensc:很可能 OP 的 long longints 更宽,并且不会溢出他的值。 即使我使用 LLONG_MAX 和 LLONG_MIN 也会显示相同的结果。 return (reversednum&lt;INT_MAX || reversednum&gt;INT_MIN)?reversednum*sign:0; 应该是return (reversednum&lt;INT_MAX &amp;&amp; reversednum&gt;INT_MIN)?reversednum*sign:0; 【参考方案1】:

您正在使用 INT_MAX 和 INT_MIN,而您可能应该使用 LLONG_MAX 和 LLONG_MIN

long long  reverse(long long x) 
long long reversednum = 0;
int sign = 1;
if (x<0)
    sign = -1;
x = abs(x);

while (x)

    reversednum = reversednum * 10;
    reversednum = reversednum + (x % 10);
    x = x / 10;


return (reversednum<LLONG_MAX || reversednum>LLONG_MIN) ? reversednum*sign : 
0;


int main()
    long long int i;
    scanf_s("%lld", &i);
    printf("%lld",reverse(i));
    scanf_s("%d", &i);

已在值 1232 和您的给定值上进行了测试,结果显示不佳 (1534236469)。

【讨论】:

但我的代码也适用于 1232 和其他小值,但它不满足输入 1534236469 @AnandhKishan,您必须接近极限(以 10 为底,仅接近 LLONG_MAX 极限的 1% 才会遇到麻烦。请参阅我的答案。【参考方案2】:

在反转之前检查数字是相当复杂的,所以:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

long  long reverse(long  long x)

  long  long reversednum = 0;

  // just in case
  // please be aware that the minimum size for "int" is 16-bit!
  // you might think about changing it to an actual number if you
  // really need 32-bit
  long  long int_max = INT_MAX;
  long  long int_min = INT_MIN;

  int sign = 1;

  // abs() would have been wrong for "long long", it should have been "llabs()"
  // also: you already checked if x is negative, no need for "abs()" here
  if (x < 0)
    sign = -1;
    x = -x;
  

  while (x) 
    reversednum = reversednum * 10;
    reversednum = reversednum + (x % 10);
    x = x / 10;
  

  // you want "reversednum" to be in the range int_min < reversednum < int_max
  // hence the need to check both limits
  return (reversednum < int_max
          && reversednum > int_min) ? reversednum * sign : 0;


int main(int argc, char **argv)

  long  long x;

  if (argc != 2) 
    fprintf(stderr, "Usage: %s integer\n", argv[0]);
    exit(EXIT_FAILURE);
  

  errno = 0;
  x = strtoll(argv[1], NULL, 10);
  if ((errno == ERANGE && (x == LLONG_MAX || x == LLONG_MIN))
      || (errno != 0 && x == 0)) 
    fprintf(stderr, "Input %s caused an error in converting: %s\n", argv[1],
            strerror(errno));
    exit(EXIT_FAILURE);
  

  printf("IN  : %lld\n", x);
  x = reverse(x);
  printf("OUT : %lld\n", x);

  exit(EXIT_SUCCESS);

正如 cmets 中所说,但也值得在此重复:限制 int_* 的最低保证只有 16 位 (ISO/IEC 9899:2011 5.2.4.2.1)!

【讨论】:

【参考方案3】:

我确定您必须在 printf 中使用 %ld...使用 %lld!简单的错误:)

分析发生了什么:

当你进入 1534236469 结果应该是函数正确生成的 9646324351(是的!)。

在二进制中,9646324351 写为 00000010 00111110 11110111 00111010 01111111 。现在您的编译器为 long 分配 4 个字节,为 long long 分配 8 个字节。因此,当您打印 long (应该是 long long 的地方)时,编译器将简单地从二进制文件中获取前 4 个字节并丢弃其余字节。这意味着您的编译器只需要 00111110 11110111 00111010 01111111 ,这相当于十进制的 1056389759。因此输出......所以这意味着你的功能和逻辑是正确的(phewwww....!!!!)但是你犯了一个愚蠢的错误。

【讨论】:

不,不是,9,646,324,351,这是正确的结果(不是你在答案中输入的那个,错过了前一个到最后一个数字 5)是 FAR超过 32 位数字的 32 位限制,即使是无符号数,也就是 4,294,967,296。您必须在发布之前重新考虑您的答案!!!您的问题无法解决二进制算术,因为您正在转换以 10 为基数的数字......并且乘以 10 可能导致数字溢出远低于 MAX_INTMAX_UINT 限制。 @Luis Colorado Dude...我自己写了 964632431...不是从终端复制的。所以可能是笔误。而且我 100% 确定我的答案是正确的。放开你的自我。甚至操作员也将其标记为正确。如果存在逻辑错误或者您有更好的解决方案。请赐教。 是的,这不是自我的问题。你写的号码根本没有失败,是 PO 写的失败了。由于我的回答中暴露的原因,它失败了,而不是因为我的自我。你是唯一一个提到别人的自我的人,相信我,你的答案是不正确的。对于一个在 32 位整数中失败的数字,您至少需要十位数字,而您写的数字只有九位。您已经编辑了您的回复(我明白了),所以再试一次,发现它失败了,尽管您使用了打印它的格式。 老兄.. 对不起,如果冒犯了自我部分,但这只是一个错字,我错过了 5。0kay 做一件事。 op提供的输出..将其转换为二进制。并将(您的输出)正确的输出转换为二进制。匹配前 4 个字节。你会明白为什么我是对的,为什么它迫使我说这个操作错误地使用了 %d 或 %ld 而不是 %lld。 long long 通常有 8 个字节的长度。 long 和 int 一般有 4 个。所以如果输出 long long 成 long 或 int... 只会考虑前 4 个字节。这就是编译器架构的基本概念。它的工作方式! @Luis Colorado 如果您仍然不满意,请告诉我。我有更多这样的例子(甚至更复杂)来证明我的观点。而且,如果一个人已经编程了很长时间,相信我,这个逻辑甚至不是逻辑。它相当于 2+2=4。它就是它的工作方式。真的很抱歉自我部分,顺便说一句,没有冒犯。【参考方案4】:

您的问题的问题是您假设问题出在符号位上(并且您不能用有效位溢出),但它不存在,您在接近符号位之前就遇到了错误。在分析您的代码后,在最后一次乘以 10 时会出现最终的无符号 32 位整数溢出(乘以 10,而不是乘以 2,因此每次乘法最多可以得到四个新位,从而更容易溢出) , 即使你做unsigned 算术(推荐这样你不需要玩符号)

只需修改一点代码以使用unsigned 算术,并在reverse 函数中对该版本的程序发出一些跟踪:

#include <stdio.h>
#include <stdlib.h>

unsigned  reverse(unsigned x, unsigned base)

    unsigned reversednum=0;

    while(x) 
        unsigned digit = x % base;
        reversednum *= base;
        reversednum += digit;
        x /= base;
        printf("reverse: digit=%u, reversednum=%u, x=%u, base=%u\n",
                digit, reversednum, x, base);
    

    return reversednum;


int main()

    char buffer[100];
    while ( fgets(buffer, sizeof buffer, stdin) ) 
        unsigned x = (unsigned) atol(buffer);
        printf("reverse(%u) => %u\n", x, reverse(x, 10));
     /* while */
 /* main */

使用您的输入执行:

$ reversed
23
reverse: digit=3, reversednum=3, x=2, base=10
reverse: digit=2, reversednum=32, x=0, base=10
reverse(23) => 32
1534236469
reverse: digit=9, reversednum=9, x=153423646, base=10
reverse: digit=6, reversednum=96, x=15342364, base=10
reverse: digit=4, reversednum=964, x=1534236, base=10
reverse: digit=6, reversednum=9646, x=153423, base=10
reverse: digit=3, reversednum=96463, x=15342, base=10
reverse: digit=2, reversednum=964632, x=1534, base=10
reverse: digit=4, reversednum=9646324, x=153, base=10
reverse: digit=3, reversednum=96463243, x=15, base=10
reverse: digit=5, reversednum=964632435, x=1, base=10  <-- after multipliying 
                                                       by base, unsigned 
                                                       overflows here to 
                                                        9,646,324,350 - 
                                                      2*4,294,967,296 == 
                                                        1,056,389,758
reverse: digit=1, reversednum=1056389759, x=0, base=10
reverse(1534236469) => 1056389759

您的代码的主要问题是您的号码(十位数字)中的最后一位数字可以是0..9,并且您的十位数字中的第一位数字不能超过4 (如果是4,那么第二个不能超过2...或者发生溢出,给出一个大于无符号最大可能值的数字)例如,所有大于1,000,000,000的数字以 5 或更多结束的数字将溢出,从以 4 结束的数字集合中,所有以 2 或更多结束的数字将从以 24 结束的数字中溢出, 在924 中完成,其前一个数字大于4,所有都将溢出,......所以直到在基数中形成数字MAX_UINT,您正在进行计算。

签名数字也会发生这种情况,因此您必须限制函数的域不溢出。由于最后一位为5 的所有十位数字都会溢出,我建议为第一个溢出的数字设置最大值1,000,000,005。对于不同的基数,您将有不同的限制,因此您必须小心(我不应该允许大于或等于 base^(floor(log(MAX_UINT)/log(base))) ==&gt; 1,000,000,000 的数字,因为您将开始获得溢出的数字窗口,并穿插不t --- 在 32 位无符号的情况下,第一个失败的数字是 1,000,000,005,而对于有符号的数字,它是 1,000,000,003)

对于 64 位 unsigneds,限制应为 1.0E19 (== 10^((int)(log((double)MAX_ULONG_LONG)/log(10.0)))),因为 10,000,000,000,000,000,002 是第一个失败的数字。对于signeds,限制应该是1.0E18,因为1,000,000,000,000,000,039应该是第一个失败的数字。

只是为了完成

要考虑使用一组 32 位整数正常工作的函数,您应该像您所做的那样做出断言,但不要使用 INT_MAXINT_MIN,您必须使用实际的数字边界限制.类似的东西

#include <stdint.h>
#include <assert.h>
int32_t reverse(int32_t x)

    assert(x > -1000000000 && x < 1000000000);
    int32_t reversednum = 0;
    while(x) 
        int digit = x % 10;
        reversednum *= 10;
        reversednum += digit;
        x /= 10;
     /* while */
    return reversednum;
 /* reverse */

甚至可以在运行时禁用生产代码的范围检查。 base 已固定为 10,因为这是您的函数,而 assert(3) 限制取决于编号基数的选择。

【讨论】:

以上是关于具有整数溢出条件的整数的反转的主要内容,如果未能解决你的问题,请参考以下文章

[LeetCode] 整数反转

数学与数字3:整数反转-溢出的统一处理法则

7.整数反转--数论(怎么判断溢出)

7. Reverse Integer(反转整数)

LeetCode刷题记录_反转整数

关于反转整数