热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

放弃所谓“右移优化除法”行为

TheDifferencebetweenDivisionandArithmeticRightShiftinginC你是否有听说过有符号数不能使用右移操作()来代替除法?这篇短文

The Difference between Division and Arithmetic Right Shifting in C

你是否有听说过有符号数不能使用右移操作(>>)来代替除法? 这篇短文会向你证明它,并尝试向你解释为什么。当然,如果你没有听说过,那么从现在开始,记住它!


Foundation: Logical Shift .vs. Arithmetic Shift

若你现在有二进制数x=1110B,对其施加右移操作,请问高位填0还是填1?

逻辑移位不管造成的影响,总是用0来填充移位操作产生的空缺。但是这样简单的想法在一些情况总会出错。例如若上述x是有符号数,那么简单的填0就会造成错误,起码正负号出错了。

算数移位支持有符号数的移位操作,在移位后使用符号位进行填充,结合补码的表示方法,就能实现正确的负数移位操作。

总结来说:在有符号的场景下,使用算数位移;如果你能保证移位操作是无符号的,那么用逻辑位移也无妨.

x86汇编代码中,shr代表逻辑右移指令,sar代表算数右移指令,我们可以通过以下C代码及其反汇编的结果来更好的理解逻辑移位和算数移位:

#include
#include

signed int x = -3;
unsigned int y = 3;
int main()
{
x >>= 1;
y >>= 1;
return 0;
}

x:
.long -3
y:
.long 3
main:
push rbp
mov rbp, rsp
mov eax, DWORD PTR x[rip]
sar eax
mov DWORD PTR x[rip], eax
mov eax, DWORD PTR y[rip]
shr eax
mov DWORD PTR y[rip], eax
mov eax, 0
pop rbp
ret

https://godbolt.org/z/K4M4Ko4c7


Demo to Prove the Subject

在我作为一个初级程序员的认知中,/2>>1是等价的,甚至一起还听说过后者能够优化代码的效率。但是今天我要告诉你, Definitely wrong!

或许在遥远的古代,我们使用位移操作真的能够对代码进行加速,但是当下编译器已经足够聪明,如果你真的动手反汇编"/2"的代码,那么你就会知道编译器已经替你优化为了位移操作。

更糟糕的是,我们要避免使用移位操作来实现除法或者乘法,不仅仅是因为这两者等价,实际上,他们并不是等价的!并且会造成错误!

Let me show you a little demo.

考虑如下的C语言代码:

#include
#include
signed int x = -3;
signed int y = -3;
int main()
{
x >>= 1;
y /= 2;
return 0;
}

他们的汇编代码是相同的吗?这里还是拿X86汇编举例:

; Following is ‘x >>= 1’
mov eax, #-3 ;x
sar eax
mov x, eax
; Following is ‘y/= 2’
mov eax, #-3 ;y
mov edx, eax
shr edx, 31
add eax, edx
sar eax
mov y, eax

注意:以上的汇编代码省去了一些我认为无关紧要的操作,并不是完全正确的,但是足够表达他们的差别了。

可以看出,除法比移位多了一步shr edx, 31过程,下面会探讨这个。

还有一件使你震惊的事件,x, y的值最终是不同的!是的,正是因为那条看似“多余”的shr指令。


Why does This happen?

首先,我们可以确定的一件事是:编译器真的帮我们将除法操作优化为移位。所以,再也不要说你的代码中使用>>来替代除法是为了增加执行效率了。

让我们来解释下为什么两者的结果是不同的。

首先,sar指令在x86指令集中表示算数右移,这个是我们熟悉的,那么-3进行算数右移后的结果就是-2. 意味着>>是向负无穷舍入的.

那么除法操作又是在干什么呢? 它是将原值加上其符号位.Demo中使用的数据类型是32位int.

shr edx, 31
add eax, edx

这样做必然改变了原值啊,动手算一下就会知道,-3/2的结果为-1. 并且只有负奇数会受影响,对于正数,其符号为0;对于负偶数,其补码的最低位必为0,刚加上的1会被下一步的算数右移丢弃,不对高位产生影响。

Aha, 差别就是向负无穷舍弃还是向0舍弃,一时间竟然不知道哪个是正确的了。


What Should We Do?

根据最新的[C语言标准草案](ISO/IEC 9899:201x (open-std.org)) 6.5.7章节,负数的右移操作是implementation-defined,即取决于具体的实现:


The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.


因此,理论上它依赖于实现。所以我们在实际应用中为了程序的可移植性,应当避免对有符号数使用移位操作。除非你能确定它的值一定是非负数,在此情况下,请将它用无符号类型来声明。

对于除法操作,标准中的6.5.5章节规定了,除法操作总是向0舍入. 非常好!


When integers are divided, the result of the / operator is the algebraic quotient with any fractional part discarded.


检查你的代码,恢复所有的“优化”乘除法的行为吧!



推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
author-avatar
手机用户2702938540
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有