这一篇探讨的是“负数位运算的右移操作”,涉及到数据的源码、反码、补码的转换操作。属于C语言基础篇


先看例子

#include <stdio.h>
int main(void) {
//正数的位右移
//补码0000 0101
int x = +5;
//正数补码右移两位后
//补码0000 0001
printf("+5>>2 = %d\n", x>>2); //+5>>2 = 1 //负数的位右移
//补码1111 1011
int y = -5;
//负数补码右移两位后
//补码1111 1110
printf("-5>>2 = %d\n", y>>2); //-5>>2 = -2
}

输出结果。


好了,现在来解释一下这个输出结果是怎么来的。

在讨论负数的右移之前,我们先要了解一下什么是原码、什么是反码、什么是补码

任何一个数据都有其唯一对应的原码、反码以及补码。计算机对于数据的处理都是以补码形式来进行的(至于为什么要这样就又可以展开一整篇文章来说明了,这里就不深入讨论了)。而且正数和负数的原反补码的转换规则是不一样的。

对于正数来说,其原、反、补码都是相同的,都是该数据的二进制形式。例如+5的原、反补码均为0000……0101(其中0的个数由该数据的类型以及计算机操作系统的位数决定)。其最高位为0表示正数。

对于一个有符号数(既不加unsigned修饰的数据类型)来说,其最高位便是它的符号位。由于有符号数的最高位为符号位,这就使得char类型有符号数的取值范围为-128~+127而不是0~256。

对于负数来说,其原码是在其正数原码的基础上,最高位改为1以表示负数。所以-5的原码为1000……0101。负数的反码是在保持源码符号位不变的情况下其余位取反。而负数的补码是其反码加1。


现在,我们知道了原码、反码、补码都是些什么了,那么这个例子的结果就很好分析了。

首先是 +5>>2 = 1

+5

原码 0000 …… 0101

补码 0000 …… 0101

>>2(正数右移高位补0)

补码 0000 …… 0001

原码 0000 …… 0001

= 1

然后是 -5>>2 = -2

-5

原码 1000 …… 0101

反码 1111 …… 1010 负数的反码是保留符号位不变源码取反

补码 1111 …… 1011 补码是反码加1

>>2 (负数右移高位补1)

补码 1111 …… 1110

反码 1111 …… 1101 补码转反码减1

源码 1000 … 0010 负数反码转源码保留符号位不变取反

= -2


以上就是有符号数的右移操作了,随便说一下“有符号数的左移”以及“负数的无符号右移”(没有“无符号左移”这个说法,因为左移是在低位补0,而符号位在高位,左移之后补的数据不能影响最终的符号)

有符号数的左移

无论是有符号的正数还是负数,其左移都是在其补码的基础上面左移,而且低位都是补0。看到这里,你是否就意识到了一个问题,“如果正数的补码在左移的过程中,刚好有一个1移到了最高位,那么是否就会变成了负数呢?”嗯,这种情况确实是会发生的。负数左移到一定值的时候也会变成正。

无符号右移

注意:在C语言中是没有“无符号右移”运算符的,在Java中用“>>>”表示,C语言中可以利用“((unsigned int)(-5))>>n”来实现

无论是正数还是负数,其无符号右移都是在其补码的基础上右移,高位补0。

例如

-5

原码 1000 …… 0101

反码 1111 …… 1010 负数的反码是保留符号位不变源码取反

补码 1111 …… 1011 补码是反码加1

>>>2(无符号右移,高位补0)

补码 0011 …… 1110

反码 (此时符号位已经变为0了,系统会当成正数来处理,原反补码均一样)

原码 0011 …… 1110

= 这个不好说,得看操作系统的位数(在我这里int为32位,

结果为:107374182==>00111111 11111111 11111111 11111110)


题外话(纯属瞎扯,感兴趣的可以看下)

为什么我们要讨论数据的移位操作呢?关于这点,我想要说一些不太恰当的题外话。

虽然对于嵌入式的开发,伦理上来说应该会经常涉及对于数据的位操作才对。曾经有个老师是这么对我说的“学单片机其实就是在学位操作”,但是我对于这句话的解读却不是太赞成,我觉得应该这么说才更符合现在的开发环境,“学单片机其实就是在学习控制位操作。”为什么这么说呢?我不太严谨的说一下自己的看法吧!虽然,移位操作在AT89S51的开发中表现的很明显。但是如今AT89S51的应用场景已经越来越少了。移位操作在STM32的开发中就表现的不太明显,由于在STM32的开发过程中,我们大多数时候都是用库函数来完成,利用了别人已经封装好的函数来开发。而且这些库函数大多数情况下还不仅仅是封装一层。这虽然大大提高了程序员开发时的方便性,不过也造成了初学者对于自己写的程序是如何指导芯片正常工作的中间过程模糊不清。如果你不用库函数来操作,效率又太低,所以大多数开发人员都还是用现成的库函数来开发。移位操作用的较少。

移位操作在底层的开发中(特别是汇编语言)用的很多,但在在应用层上面应用的比较少了。要求的掌握程度几乎处于“知道有这么一回事,接触到的时候,能想的起来知道它干了什么就行” 的地步。很多开发人员就只在刚刚开始学习编程的时候以及去面试做笔试的时候接触过数据的移位操作,之后就再也用不上了。但是对于类似数据移位的这种应用范围比较窄的知识点,我想要说的是,希望大家在学习的过程中多留一个心眼,不要觉得不重要不常用就不去重视。因为这些知识点往往能在某些特定场景下面有奇效。就例如移位操作的妙用。


在介绍这个妙用之前,我们需要知道一个前提。

计算机在处理数据的时候,处理加、减、乘、除所需要的时间是不一样的,其中加减所需要的时间和移位操作几乎是一样的可以忽略不计,但是乘法需要的时间却是加法的十到二十倍。而除法所需要的时间几乎是加法的二十到三十倍。具体是多少,这个不好说,在不同的机器上面是不一样的。但可以确定的是,乘除所需要的时间总是比加减移位多。特别是除法。

在刚刚开始接触数据的移位操作这个知识点的时候,老师就已经和我们说过移位操作的结果和原数据之间的关系。

其关系大概如下:

对于正数在不溢出的情况下的左移和右移(由于负数的左移和右移后得到的数据和原数据之间的关系不明显,所以仅仅讨论正数)

左移:

左移n位后的数据 = 原数据乘2n

右移:

右移n位后的数据 = 原数据除2n

一开始了解到这个知识点的时候,我对此是很不屑的,因为实在是太局限了,即便移位操作比乘除快很多,但是这个乘的数值或除的数值必须是2^n也太鸡肋了吧。而且如今计算机的运算速度这么快,这点运输速度之间的差异实在不算什么。

但是在后来我看的一个例子中,利用移位操作却有奇效。在这个例子中,有两个关键因素使得移位操作比乘除运算好。一个是对于运算结果精度要求不高,另外一个是运算的数据量巨大。这个例子展开来说,又是一整篇文章了。感兴趣的话可以看一下这篇文章。虽然在这个例子中,移位操作并不能真正解决最终问题。但是,却可以给我们一个启发“在某些场景下,一些平时不受待见的冷门知识点,却出奇的会很好用”。


原博客始发于CSDN,在如今博客界的转载抄袭泛滥的环境下,原创不易,点个赞再走呗。以下是博客首页的链接。


零BUG是原则性问题。

负数位运算的右移操作-C语言基础的更多相关文章

  1. Sword 位运算取余操作

    /* 位运算取余操作 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include ...

  2. 位运算 - 左移右移运算符 >>, <<, >>>

    1-左移运算符m<<n,表示把m左移n位.左移n位的时候,最左边的n位数将被丢弃,同时在最右边补上n个0.例如: 00001010<<2 = 00101000 10001010 ...

  3. C#位运算实际作用之操作整型某一位

    1.前言 前几天写了两篇关于c#位运算的文章 c#位运算基本概念与计算过程 C#位运算实际运用 在文中也提到了位运算的实际作用之一就是合并整型,当时引用了一个问题: C# 用两个short,一个int ...

  4. 面试必备:高频算法题终章「图文解析 + 范例代码」之 矩阵 二进制 + 位运算 + LRU 合集

    Attention 秋招接近尾声,我总结了 牛客.WanAndroid 上,有关笔试面经的帖子中出现的算法题,结合往年考题写了这一系列文章,所有文章均与 LeetCode 进行核对.测试.欢迎食用 本 ...

  5. Java二进制和位运算,这一万字准能喂饱你

    基础不牢,地动山摇.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众号[BAT的乌托 ...

  6. Java中的二进制及基本的位运算

    Java中的二进制及基本的位运算 二进制是计算技术中广泛采用的一种数制.二进制数据是用0和1两个数码来表示的数.它的基数为2,进位规则是"逢二进一",借位规则是"借一当二 ...

  7. Java位运算实现加减乘除

    一.加法 a+b 举例实现:13+9=22 13+9不考虑进位结果为12 只考虑进位结果为10 和刚好是22. 13二进制为1101,9二进制为1001. 不考虑进位结果为0100.算式为a^b 只考 ...

  8. 《Thinking in Java》位运算

    按位操作符: 首先先记住一件事,方便理解:是否对应正负对应10. 1.与(&):11得1,10得0,00得0. 2.或(|):11得1,10得1,00得0. 3.异或(^):11得0,10得1 ...

  9. Vus the Cossack and Strings(Codeforces Round #571 (Div. 2))(大佬的位运算实在是太强了!)

    C. Vus the Cossack and Strings Vus the Cossack has two binary strings, that is, strings that consist ...

  10. day02<Java语言基础+>

    Java语言基础(常量的概述和使用) Java语言基础(进制概述和二,八,十六进制图解) Java语言基础(不同进制数据的表现形式) Java语言基础(任意进制到十进制的转换图解) Java语言基础( ...

随机推荐

  1. linux下第三方库的配置和链接——以opencv为例

    安装OpenCV(可参考链接) 下载source到 /usr/local/路径下 新建 /build/ cmake 编译 添加库路径 vim /etc/ld.so.conf 该目录作用参考链接 输入: ...

  2. vue-用户管理系统

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. springboot返回前端对象null转为空字符串

    1 2 import com.fasterxml.jackson.core.JsonGenerator; 3 import com.fasterxml.jackson.core.JsonProcess ...

  4. mybatis-属性名和查询字段名不相同的解决方案

    1.使用别名 resultType可以将查询结果直接映射为实体bean对象的条件是,sql查询的字段名和实体bean的属性名一致,通过反射机制完成对象的创建. select tid id,tname ...

  5. HarmonyOS基础

    目录 自适应布局 自适应拉伸布局 自适应缩放 自适应延伸 组件多态 ArkUI开发框架 基础组件 Text组件和Span组件 参考 参考:harmonyos3: 鸿蒙ArkUI eTS教程配套源码 参 ...

  6. div css 页面中心弹窗窗口

    <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8&quo ...

  7. 30.zookeeper部署

    (一)Zookeeper基础知识.体系结构.数据模型 1 zookeeper是一个类似linux.hdfs的树形文件结构,zookeeper可以用来保证数据在(zk)集群之间的数据的事务性一致. 2 ...

  8. pycharm 默认添加# -*-coding: utf-8 -*-

    备忘 pycharm创建py文件时,默认输入表头信息 1.点击[File]-[Settins] 2.点击[Editor]-[File and Code Templates] 3.点击[Python S ...

  9. 2022-3-17内部群每日三题-清辉PMP

    1.一个项目预算为6000万美元,预计需要24个月才能完成.12个月后,该项目完成了60%,并使用了3500美元.那么预算和进度的状态如何? A.符合预算,并超前于进度 B.超出预算,但超前于进度 C ...

  10. 如何修改Mac文件默认打开方式?

    熟悉Mac电脑的用户都知道,在 OS X 中,Finder 存储的文件总会以指定的某个默认应用程序打开,比如图片类型的文件默认以「预览」打开.但由于经常需要使用图片编辑工具 PS打开图片类型的文件,每 ...