无符号数减法溢出问题

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net       linuxfocus.blog.chinaunix.net

今天再次遇到一个C语言的细节问题,并且发现自己以前的理解不正确,然后总结了一下,写出本文。请看下面的代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>


  3. int main()
  4. {
  5.     unsigned int a = 1;
  6.     unsigned int b = -1;

  7.     printf("a is 0x%X\n", a);
  8.     printf("b is 0x%X\n", b);

  9.     printf("a-b is 0x%X\n", a-b);

  10.     return 0;
  11. }
它的结果为:
  1. is 0x1
  2. is 0xFFFFFFFF
  3. a-is 0x2
其中最后的a-b的结果为0x2,不是我之前期望的。因为a为1,而b为无符号数中的最大数,它的结果怎么会是2呢?

如果有朋友认为结果是2的原因,是因为1-(-1)=1+1=2。那么我只能说恭喜你,虽然你的推导与结果相同,但是想法是错误的。因为这里是无符号数,不是有符号数,所以这里没有-1。

经过和一些朋友的讨论,我又写了一些测试代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>


  3. int main()
  4. {
  5.     unsigned short a = 1;
  6.     unsigned short b = -1;

  7.     printf("a is 0x%X\n", a);
  8.     printf("b is 0x%X\n", b);

  9.     printf("a-b is 0x%X\n", a-b);

  10.     return 0;
  11. }
这里只是将int改为short型,结果则是:
  1. is 0x1
  2. is 0xFFFF
  3. a-is 0xFFFF0002
先分析一下这个结果,看看是否对于之前的问题有所帮助。a-b的时候,按照c标准会将其变为int型,即(int)a-(int)b=1-65535=-65534=0xFFFF0002。而%x打印的是无符号整数,结果自然是0xFFFF0002。

第二份代码与第一份代码,不同之处,只是a和b的类型不同。在第一份代码中,a-b的时候,因为其类型为无符号整数,所以这里不会发生整数提升,仍然是无符号数的减法。而1-0xFFFFFFFF应该等于-0xFFFFFFFE。而这个数值实际上可以理解为超出了无符号数unsigned int的表示范围——注意,这里表达的并不准确,因为unsigned int的范围是0~0xFFFFFFFF。也就是说unsigned int是无法表示-0xFFFFFFFE的数值的。

为了证明这一点,我们可以扩大一下a和b的类型:
  1. #include <stdlib.h>
  2. #include <stdio.h>


  3. int main()
  4. {
  5.     unsigned int a = 1;
  6.     unsigned int b = -1;

  7.     printf("a is 0x%X\n", a);
  8.     printf("b is 0x%X\n", b);

  9.     printf("a-b is 0x%llX\n", (unsigned long long)a-(unsigned long long)b);

  10.     return 0;
  11. }
其结果为:
  1. is 0x1
  2. is 0xFFFFFFFF
  3. a-is 0xFFFFFFFF00000002
这与我们前面的分析吻合。

从上面的分析看出,这个简单的无符号整数的减法操作,引出了这么多的东西。如果说前面的分析或者推导有些难以理解的话,还有一种理解方式,但是我不知道正确与否,合适与否。这个a-b的值一定要满足a-(a-b)=b。当a=1,而b等于0xFFFFFFFF时,所以这个(a-b)一定为2。——你不要觉得这样的等式一定成立,在计算机的编程中,这种算术运算的反运算,是有可能不成立的。

今天有看了看二进制补码以及减法的实现——依然是通过加法实现,感觉对这个问题似乎可以理解的更深。但是目前还是有一层窗户纸,没有捅破,就差一点了。
  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个Verilog实现的64位有符号减法器,当计算结果溢出时会取最大值: ```verilog module signed_subtractor( input signed [63:0] a, input signed [63:0] b, output reg signed [63:0] result ); always @(*) begin if(a >= 0 && b < 0 && $signed(a - b) < 0) begin result = {$signed({32'h7FFFFFFF, 32'hFFFFFFFF}) - b[31:0], $signed({32'hFFFFFFFF, 32'hFFFFFFFF}) - b[63:32]}; end else if(a < 0 && b >= 0 && $signed(a - b) >= 0) begin result = {$signed({32'h80000000, 32'h0}) - b[31:0], $signed({32'h0, 32'h0}) - b[63:32]}; end else begin result = a - b; end end endmodule ``` 该模块接收两个有符号的64位输入a和b,将它们相减,并将结果存储在名为result的64位有符号输出中。当计算结果溢出时,将使用最大值进行替换。 具体实现中,当a为正且b为负且a-b的结果为负时,表示a-b的结果溢出,此时将result设置为最大值。最大值由两个32位值组成,分别为0x7FFFFFFF和0xFFFFFFFF。当a为负且b为正且a-b的结果为非负时,也表示a-b的结果溢出,此时将result设置为最小值。最小值由两个32位值组成,分别为0x80000000和0x00000000。否则,将计算结果存储在result中。 注意,在这个实现中,我们使用了$signed函无符号值转换成有符号值。这是因为在Verilog中,如果使用两个有符号进行减法运算,结果将是一个无符号。因此,我们需要将结果再次转换成有符号,以便进行比较和处理溢出。同时,我们使用了花括号来组合两个32位值,以生成一个64位值。 这个模块可以用于任何需要进行64位有符号减法运算并在溢出时取最大值的Verilog设计中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值