【问题标题】:Overflow and underflow in unsigned integers无符号整数的上溢和下溢
【发布时间】:2020-10-16 02:19:16
【问题描述】:

假设,我正在尝试减去 2 个无符号整数:

247 = 1111 0111
135 = 1000 0111

如果我们减去这两个二进制数,我们得到 = 0111 0000

这是下溢吗,因为我们现在只需要 7 位?或者它是如何工作的??

【问题讨论】:

  • 这能回答你的问题吗? What are arithmetic underflow and overflow in C?
  • 这是关于有符号整数
  • 不,选择的答案专门使用无符号整数
  • 为什么这篇文章被标记为 CSS 溢出属性?
  • 502 是 1 1111 0110,而不是 1111 0111。

标签: c integer-overflow underflow


【解决方案1】:

每当b 大于a 时,就会发生无符号减法c = a - b 中的下溢。

但是,这有点循环定义,因为有多少种机器执行a < b比较是通过使用环绕算法减去操作数,然后根据两个操作数和结果检测溢出。

另请注意,在 C 中我们不谈论“溢出”,因为没有错误条件:C 无符号整数提供了硬件中常见的环绕算法。

因此,鉴于我们有回绕算法,我们可以检测是否在减法中发生回绕(或溢出,取决于观点)。

我们需要的是来自a、b 和c 的最重要的位。我们称它们为A、B 和C。根据这些,溢出V 是这样计算的:

A B C | V
------+--
0 0 0 | 0
0 0 1 | 1
0 1 0 | 1
0 1 1 | 1
1 0 0 | 0
1 0 1 | 0
1 1 0 | 0
1 1 1 | 1

这简化为

A'B + A'C + BC

换句话说,无符号减法c = a - b 中的溢出发生在任何时候:

  1. a的msb为0,b的msb为1;
  2. 或者a的msb为0,c的msb为1;
  3. 或者b的msb为1,c的msb也为1。

减去 247 - 135 = 112 显然不会溢出,因为 247 大于 135。应用上面的规则,A = 1,B = 0 和 C = 0。表格的 1 1 0 行有一个 0 V列:没有溢出。

【讨论】:

    【解决方案2】:

    通常,“下溢”是指计算的理想数学结果低于该类型可以表示的值。如果在无符号算术中从 5 中减去 7,理想的数学结果将是 -2,但无符号类型不能表示 -2,因此运算会下溢。或者,在可以表示从 -128 到 +127 的数字的 8 位有符号类型中,从 -100 中减去 100 理想情况下会产生 -200,但这不能在类型中表示,因此运算会下溢。

    在 C 中,无符号算术被称为不会下溢或上溢,因为 C 标准定义了要使用模算术而不是实数算术执行的操作。例如,对于 32 位无符号算术,从 5 中减去 7 将产生 4,294,967,294(十六进制,FFFFFFFE16),因为它包含模 232 = 4,294,967,296。尽管如此,人们在讨论这些操作时可能会使用术语“下溢”或“上溢”,意在指代数学问题而不是定义的 C 行为。

    换句话说,对于您用于算术的任何类型,都有一些类型可以表示的下限 L 和一些上限 U。如果运算的理想数学结果小于 L,则运算下溢。如果运算的理想数学结果大于U,则运算溢出。 “下溢”和“溢出”意味着操作已经超出了类型的范围。 “溢出”也可以用来指任何超出类型边界的行为,包括在低方向。

    这并不意味着需要更少的位来表示结果。当从 111101112 中减去 100001112 时,结果 011100002 = 11100002 在范围内,所以没有上溢或下溢。它需要更少的比特来表示这一事实是无关紧要的。

    (注意:对于整数算术,“下溢”或“上溢”是相对于绝对边界 L 和 U 定义的。对于浮点算术,这些术语有一些不同的含义。它们可能是相对于结果的大小定义的,忽略符号,它们是相对于格式的有限非零范围定义的。浮点格式可能能够表示 0,则各种有限的非零数,然后是无穷大。在 0 和格式可以表示的最小非零数之间的某些结果被称为下溢,即使它们在技术上处于可表示的数字范围内,即从 0 到无穷大。类似地,超过最大可表示有限数的某些结果被称为溢出,即使它们在可表示范围内,因为它们小于无穷大。)

    【讨论】:

      【解决方案3】:

      长话短说,当你有以下情况时会发生这种情况:

      unsigned char n = 255; /* highest possible value for an unsigned char */
      
      n = n + 1; /* now n is "overflowing" (although the terminology is not correct) to 0 */
      printf("overflow: 255 + 1 = %u\n", n);
         
      n = n - 1; /* n will now "underflow" from 0 to 255; */
      printf("underflow: 0 - 1 = %u\n", n);
         
      n *= 2; /* n will now be (255 * 2) % 256 = 254; 
              /* when the result is too high, modulo with 2 to the power of 8 is used */ 
              /* for an 8 bit variable such as unsigned char; */
      printf("large overflow: 255 * 2 = %u\n", n);   
         
      n = n * (-2) + 100; /* n should now be -408 which is 104 in terms of unsigned char. */
                             /* (Logic is this: 408 % 256 = 152; 256 - 152 = 104) */
      printf("large underflow: 255 * 2 = %u\n", n);
      

      结果是(使用 gcc 11.1 编译,标志 -Wall -Wextra -std=c99):

      overflow: 255 + 1 = 0
      underflow: 0 - 1 = 255
      large overflow: 255 * 2 = 254
      large underflow: 255 * 2 = 104
      

      现在是科学版本:上面的 cmets 仅代表正在发生的事情的数学模型。为了更好地了解实际发生的情况,以下规则适用:

      1. 整数提升:

      小于 int 的整数类型在操作时被提升 对他们进行。如果原始类型的所有值都可以 表示为 int,较小类型的值转换为 一个整数;否则,将其转换为无符号整数。

      所以当计算机执行n = 255; n = n + 1; 时,内存中实际发生的情况是: 首先,右侧被评估为一个 int(有符号),因为根据整数提升的规则,结果适合一个有符号的 int。所以表达式的右边变成了二进制:0b00000000000000000000000011111111 + 0b00000000000000000000000000000001 = 0b00000000000000000000000100000000(一个 32 位整数)。

      1. 截断

      32 位 int 在分配时丢失了最高有效的 24 位 返回一个 8 位数字。

      所以,当0b00000000000000000000000100000000被赋值给变量n时,它是一个无符号字符,32位值被截断为8位值(只复制最右边的8位)=> n变成@ 987654326@.

      每个操作都会发生同样的事情。右侧的表达式计算为有符号整数,而不是被截断为 8 位。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-02-28
        • 2012-02-29
        • 2013-04-10
        • 2011-08-12
        • 1970-01-01
        • 2022-01-07
        • 2014-12-09
        相关资源
        最近更新 更多