有符号数和无符号数的转换及思考

1 有符号数和无符号数的表示


三者的最高位均为符号位.

我以前一直没弄明白的是为何8位补码的表示范围是-128~127, 今天查阅了相关资料,于此记下。

仍然以8位为例:

原码

原码的表示范围-127~-0, +0~+127, 共256个数字。

正0的原码是0000 0000, 负0的原码是1000 0000, 有正0负0之分, 不符合人的习惯, 待解决.

反码

除符号位, 原码其余位取反而得
+0:0000 0000,-0:1111 1111 仍然有正0负0之分。

补码

在反码的基础上加1而得

对原码的两种0同时末位加1

+0:0000 0000,-0:0000 0000(因为溢出导致8位全0)

消除了正0负0之别, 如此一来, 便节省出一个数值表示方式1000 0000, 不能浪费, 用来表示-128, -128特殊之处在于没有相应的反码原码。也可以这样考虑:

-11111 1111
-21111 1110(在-1的基础上减1,直接将补码减1即可)
-31111 1101(在-2补码基础上减1,以下类似)
-41111 1100
……
-1271000 0001
-1281000 0000

如此以来:8位补码表示范围是-128~+127因为0只有一种形式所以,仍然是256个数

若8位代表无符号数, 则表示范围是 : 0~255, 这就是为什么高级语言讲到数据类型,

比如C++中的short类型时(16位长)说其表示范围是:-32768~+32767,而unsigned short表示的范围则是:0~65535

2 有符号和无符号数的表示



在计算机中无符号数用原码表示, 有符号数用补码表示

w位补码表示的值为:

xw12w1+w2i=1xi2i

(xi为补码的第i位, i从0开始)

最高位 也称符号位,1表示负数,0表示正数,符号位为0时,和无符号数的表示是相同的,以下是4位补码的示例:

0101=023+122+021+120=5

1101=123+122+021+120=3

w位的补码表示的数值范围是[-2w-1, 2w-1-1]

如4位的补码表示的最小值是-8(1000), 最大值是7(0111).

只有理解了有符号数的补码表示, 才能真正理解无符号数和有符号数的转换、有符号数的截断和溢出等问题.

3 陷阱


在C语言中,如果一个运算包含一个有符号数和一个无符号数,那么C语言会隐式地将有符号数转换为无符号数,这对于标准的算术运算没什么问题,但是对于 < 和 > 这样的关系运算符来说,它会出现非直观的结果,这种非直观的特性经常会导致程序中难以察觉的错误

看下面的例子:

int strlonger(char *s, char *t)
{
    return strlen(s) - strlen(t) > 0;
}

strlonger的陷阱

上面的函数看起来似乎没什么问题, 实际上当s比t短时,函数的返回值也是1, 为什么会出现这种情况呢?

原来strlen的返回值类型为size_t,C语言中将size_t定义为unsigned int,当s比t短时,strlen(s) - strlen(t)为负数,但无符号数的运算结果隐式转换为无符号数就变成了很大的无符号数.

为了让函数正确工作,代码应该修改如下 :

return strlen(s) > strlen(t);

2002年, 从事FreeBSD开源操作系统项目的程序员意识到,他们对getpeername函数的实现存在安全漏洞.代码的简化版本如下:

//void *memcpy(void *dest, void *src,  size_t n);

#define KSIZE 1024
char kbuf[KSIZE];


int copy_from_kernel(void *user_dest, int maxlen)
{
    int len = KSIZE < maxlen ? KSIZE : maxlen;
    memcpy(user_dest, kbuf, len);
    retn len;
}

你看出了问题所在吗?

4 扩展、截断和溢出


4.1 转换


当数据类型转换时,同时需要在不同数据大小,以及无符号和有符号之间转换时,C语言标准要求先进行数据大小的转换,之后再进行无符号和有符号之间的转换.

C语言中的强制类型转换保持二进制位值不变,只是改变解释位的方式

看以下代码:

short int v = -12345;
unsigned short uv = (unsigned short)v;
printf("v = %d, uv = %u\n”, u, uv);

输出如下:

v = -12345, uv = 53191

由于-12345的16位补码表示与53191的16位无符号表示是完全一样的,所以会得到以上输出。

无符号数和有符号数之间的转换是一一对应的关系,w位的有符号数s转换无符号数u的对应关系为:

如4位有符号数7(0111)转换为无符号数也是7,而4位有符号数-1(1111)转换为无符号数是15。

类似地,w位的无符号数u转换为有符号数s的对应关系为:

如4位无符号数5(0101)转换为无符号数也是5,而4位无符号数13(1101)转换为无符号数为-3。

其实只要知道无符号数和有符号数对二进制位的解释方式,无需记住上述的对应关系,也能算出转换后的值。

4.2 扩展


  • 将无符号数转换为更大的数据类型时, 只需简单地在开头添加0,这种运算称为0扩展

  • 将有符号数转换为更大的数据类型需要执行符号扩展,规则是将符号位扩展至所需要的位数

同样对于如下的例子

//  如果是char, 那么系统认为最高位是符号位, 而int可能是16或者32位, 那么会对最高位进行扩展
signed char c7_1 = 0xff;
//  如果是unsigned char, 那么不会扩展.
unsigned char c7_2 = 0xff;
//  最高位若为0时, 二者没有区别, 若为1时,则有区别
printf("signed   %08x, %d\n", c7_1, c7_1);
printf("unsigned %08x, %d\n", c7_2, c7_2);

signed 0xffffffff, -1
unsigned 0xff, 255


* 如果是char, 那么系统认为最高位是符号位, 而int可能是16或者32位, 那么会对最高位进行扩展, 因此0xff在从signed char转换为unsigned char时认为最高位1是符号位, 进行了扩展, 被扩展为0xffffffff, 值为-1

  • 如果是unsigned char, 那么不会扩展, 0xff仍然成为0x000000ff, 值为255

最高位若为0时, 二者没有区别, 若为1时,则有区别

示例程序

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

///
int main(void)
{
    printf("=======test         -1=======\n");
    //  如果是char, 那么系统认为最高位是符号位, 而int可能是16或者32位, 那么会对最高位进行扩展
    signed char c7_1 = 0x00ff; //int i7_1 = (int)c7_1;
    //  如果是unsigned char, 那么不会扩展.
    unsigned char c7_2 = 0x00ff; //int i7_2 = (int)c7_2;
    //  最高位若为0时, 二者没有区别, 若为1时,则有区别
    printf("signed   %08x, %d\n", c7_1, c7_1);
    printf("unsigned %08x, %d\n", c7_2, c7_2);


    printf("\n=======test high bit 1=======\n");
    //  最高位为1
    signed char cc1 = 0x80;
    unsigned char cc2 = 0x80;
    printf("signed   %08x, %d\n", cc1, cc1);
    printf("unsigned %08x, %d\n", cc2, cc2);

    printf("\n=======test high bit 0=======\n");
    //  最高位为0
    signed char cc3 = 0x7f;
    unsigned char cc4 = 0x7f;
    printf("signed   %08x, %d\n", cc3, cc3);
    printf("unsigned %08x, %d\n", cc4, cc4);


    return EXIT_SUCCESS;
}

这里写图片描述

如将4位的二进制数1001(-7)扩展为8位的结果为11111001(-7).

4.3 截断


将一个大的数据类型转换为小的数据类型时,不管是无符号数还是有符号数都是简单地进行位截断

无符号数的数值大小可能因截断而变化,而有符号数不仅数值大小可能变化,符号位也可能发生改变,如8位二进制数00011001(25)转换为4位数截断的结果是1001(-7).

4.4 溢出


在进行整数的算术运算时,当结果变量的位数不足以存放实际实际结果的位数时,运算的结果就会因截断而产生溢出,如果4位二进制数运算1011(-5) + 1011(-5) = 10110(-10), 但如果结果也采用4位二进制存放就会截断为0110(6),产生溢出。

<think>好的,用户让我给一个无符号有符号的例子并对比。之前已经介绍过无符号加法,现在需要扩展到有符号的情况,并举例子对比。首先,我需要确定用户的知识背景,可能对补码不太熟悉,所以例子要简单明了。 用户可能是在学习计算机组成或编程,需要理解这两种加法在计算机中的不同处理方式。需要确保例子能清楚展示溢出判断的不同,以及结果的差异。 先想无符号的例子,比如8位,选一个会溢出的情况,比如255 + 1。然后有符号的例子,用补码表示,比如127 + 1,这样会溢出到-128,导致符号位变化,但实际结果是否正确? 要注意对比两者的溢出判断标准,无符号看最高位进位,有符号符号位是否不一致进位。同时,扩展方式的不同,无符号补零,有符号符号位。 可能需要用表格来对比,让信息更清晰。例子要具体,步骤分解,比如二进制相加过程,结果的解释,以及溢出标志的设置。还要验证结果是否正确,比如十进制计算是否符合。 另外,用户可能想知道为什么有符号的溢出判断更复杂,需要解释符号实际溢出的关系。确保例子中的数值在范围内,并且溢出情况明显。比如有符号的127加1变成-128,说明溢出后的错误结果,而二进制结果是否正确? 最后总结两者的关键差异,帮助用户记忆。可能还需要提醒实际应用中的注意事项,比如编程时如何处理溢出,避免错误。</think>好的!我们可以通过具体的**无符号数加法****有符号数补码)加法**案例,对比它们的计算过程、溢出判断以及结果差异。以下是详细分析: --- ### **一、无符号加法案例** **例:计算 $200_{10} + 100_{10}$(8位无符号数)** 1. **二进制表示**: - $200_{10} = 11001000_2$ - $100_{10} = 01100100_2$ 2. **二进制相加**: ``` 11001000 (200) + 01100100 (100) ---------------- 100101100 (300) ``` 3. **结果分析**: - **8位结果**:截断后为 $00101100_2 = 44_{10}$(错误,因为实际 $300 > 255$) - **溢出标志**:置1(最高位产生进位) --- ### **二、有符号数补码)加法案例** **例:计算 $100_{10} + 50_{10}$(8位有符号数补码表示)** 1. **补码表示**: - $100_{10} = 01100100_2$(正数直接转为二进制) - $50_{10} = 00110010_2$ 2. **二进制相加**: ``` 01100100 (100) + 00110010 (50) ---------------- 10010110 (-106的补码) ``` 3. **结果分析**: - **8位结果**:$10010110_2$(最高位为1,表示负数;转换为原码:$-106_{10}$) - **溢出标志**:置1(符号位进位与次高位进位不同) --- ### **三、关键对比** | **对比项** | **无符号加法** | **有符号加法(补码)** | |--------------------|-----------------------------------|---------------------------------------| | **数值范围** | $0 \leq x \leq 255$(8位) | $-128 \leq x \leq 127$(8位) | | **溢出判断** | 最高位进位即溢出 | 符号位进位 ≠ 次高位进位时溢出 | | **错误结果示例** | $200 + 100 = 44$(截断) | $100 + 50 = -106$(符号翻转) | | **扩展规则** | 高位补0 | 高位补符号位(符号扩展) | | **实际意义** | 仅处理非负整数 | 处理正负数,依赖补码统一加减法 | --- ### **四、深入解释** #### **1. 无符号加法溢出** - **原因**:结果超过 $2^n -1$(如8位最大为255)。 - **特征**:结果截断到低n位,数值“循环”回0附近。 - **验证**:$200 + 100 = 300$,但8位无法表示,实际结果为 $300 - 256 = 44$。 #### **2. 有符号加法溢出** - **原因**:结果超出 $-2^{n-1}$ 到 $2^{n-1}-1$(如8位范围为-128~127)。 - **特征**:符号位错误翻转(正+正=负,负+负=正)。 - **验证**:$100 + 50 = 150 > 127$,但补码结果错误地显示为-106。 #### **3. 扩展规则差异** - **无符号扩展**:高位补0(例如8位→16位:$200_{10} = 0000000011001000_2$)。 - **有符号扩展**:高位补符号位(例如8位负数$-106_{10}$扩展为16位:$1111111110010110_2$)。 --- ### **五、总结** - **无符号加法**:关注数值大小,溢出直接由最高位进位判断。 - **有符号加法**:依赖补码机制,溢出需判断符号位与次高位进位是否一致。 - **核心区别**: - 无符号数的“溢出”是数值超过上限; - 有符号数的“溢出”是数值超出正负范围,导致符号错误。 --- **思考题**:若用16位有符号数计算 $200_{10} + 100_{10}$,结果会溢出吗?为什么? (答案:不会,因为16位范围是-32768~32767,300在此范围内)
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值