使用整数溢出条件反转整数

时间:2017-05-28 16:09:27

标签: c math

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;
}

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

4 个答案:

答案 0 :(得分:1)

在反转之前检查数字非常复杂,所以:

#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);
}

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

答案 1 :(得分:1)

我相信你必须在printf中使用%ld ...使用%lld!简单的错误:)

分析发生的事情:

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

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

答案 2 :(得分:0)

您的问题的问题在于您假设问题出在符号位(并且您不能溢出有效位),并且它不存在,您在接近符号之前就会出错位。在分析了你的代码之后,在最后一次乘法中有一个最终的无符号32位整数溢出10(你乘以10而不是2,所以你可以在每次乘法中得到最多4个新位,使溢出更容易) ,即使你进行了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十位数字中的第一位数字不能超过{{1} } (如果是4,那么第二个不能超过4 ...或溢出,给出的数字大于无符号的最大可能值例如,所有大于1,000,000,000的数字在5或更多中完成,将从4中完成的数字组中溢出,所有以2或更多结束的数字将溢出,从24中完成的数字溢出,那些 在2中完成前一个数字大于924,所有内容都会溢出,......所以直到在基数中形成数字4进行计算。

这也适用于带符号的数字,因此您必须将函数的域限制为不溢出。由于最后一位数为MAX_UINT的所有十位数字都会溢出,因此对于第一个溢出的数字,我建议最大值5。对于不同的基础,你有不同的限制,所以你必须小心(我不应该允许大于或等于1,000,000,005的数字,因为你将开始得到数字的窗口溢出,穿插数字,不包括在32位未签名的情况下,第一个失败的数字是base^(floor(log(MAX_UINT)/log(base))) ==> 1,000,000,000,而对于有符号的数字则是1,000,000,005

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

刚刚完成

考虑一个与一组32位整数一起正常工作的函数,你应该像你那样做一个断言,但不是使用1,000,000,000,000,000,039INT_MAX,而是必须使用实际的边界您的号码限制。像

这样的东西
INT_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限制取决于编号基础的选举。

答案 3 :(得分:-1)

当您应该使用LLONG_MAX和LLONG_MIN时,您正在使用INT_MAX和INT_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)。