点击关注公众号,回复“2T”获取2TB学习资源!

互联网架构师后台回复 2T 有特别礼包

上一篇:推荐一个可以减少开发量50%的插件!

博雯 发自 凹非寺
量子位 | 公众号 QbitAI

取整求个无符号整数的平均值,居然也能整出花儿来?

这不,微软大神Raymond Chen最近的一篇长文直接引爆外网技术平台,引发无数讨论:

无数人点进去时无比自信:不就是一个简单的相加后除二的小学生编程题吗?

unsigned average(unsigned a, unsigned b)
{return (a + b) / 2;
}

但跟着大神的一路深挖,却逐渐目瞪狗呆……

没那么简单的求平均值

先从开头提到的小学生都会的方法看起,这个简单的方法有个致命的缺陷:

如果无符号整数的长度为32位,那么如果两个相加的值都为最大长度的一半,那么仅在第一步相加时,就会发生内存溢出

也就是average(0x80000000U, 0x80000000U)=0。

不过解决方法也不少,大多数有经验的开发者首先能想到的,就是预先限制相加的数字长度,避免溢出。

具体有两种方法:

1、当知道相加的两个无符号整数中的较大值时,减去较小值再除二,以提前减少长度

unsigned average(unsigned low, unsigned high)
{return low + (high - low) / 2;
}

2、对两个无符号整数预先进行除法,同时通过按位与修正低位数字,保证在两个整数都为奇数时,结果仍然正确。

(顺带一提,这是一个被申请了专利的方法,2016年过期)

unsigned average(unsigned a, unsigned b)
{return (a / 2) + (b / 2) + (a & b & 1);
}

这两个都是较为常见的思路,不少网友也表示,自己最快想到的就是2016年专利方法

同样能被广大网友快速想到的方法还有SWAR(SIMD within a register):

unsigned average(unsigned a, unsigned b)
{return (a & b) + (a ^ b) / 2;// 变体 (a ^ b) + (a & b) * 2

以及C++ 20版本中的std: : midpoint函数。

接下来,作者提出了第二种思路

如果无符号整数是32位而本机寄存器大小是64位,或者编译器支持多字运算,就可以将相加值强制转化为长整型数据。

unsigned average(unsigned a, unsigned b)
{// Suppose "unsigned" is a 32-bit type and// "unsigned long long" is a 64-bit type.return ((unsigned long long)a + b) / 2;
}

不过,这里有一个需要特别注意的点:

必须要保证64位寄存器的前32位都为0,才不会影响剩余的32位值。

像是x86-64和aarch64这些架构会自动将32位值零扩展为64位值:

// x86-64: Assume ecx = a, edx = b, upper 32 bits unknownmov     eax, ecx        ; rax = ecx zero-extended to 64-bit valuemov     edx, edx        ; rdx = edx zero-extended to 64-bit valueadd     rax, rdx        ; 64-bit addition: rax = rax + rdxshr     rax, 1          ; 64-bit shift:    rax = rax >> 1;                  result is zero-extended; Answer in eax// AArch64 (ARM 64-bit): Assume w0 = a, w1 = b, upper 32 bits unknownuxtw    x0, w0          ; x0 = w0 zero-extended to 64-bit valueuxtw    x1, w1          ; x1 = w1 zero-extended to 64-bit valueadd     x0, x1          ; 64-bit addition: x0 = x0 + x1ubfx    x0, x0, 1, 32   ; Extract bits 1 through 32 from result; (shift + zero-extend in one instruction); Answer in x0

而Alpha AXP、mips64等架构则会将32位值符号扩展为64位值。

这种时候,就需要额外增加归零的指令,比如通过向左进位两字的删除指令rldicl:

// Alpha AXP: Assume a0 = a, a1 = b, both in canonical forminsll   a0, #0, a0      ; a0 = a0 zero-extended to 64-bit valueinsll   a1, #0, a1      ; a1 = a1 zero-extended to 64-bit valueaddq    a0, a1, v0      ; 64-bit addition: v0 = a0 + a1srl     v0, #1, v0      ; 64-bit shift:    v0 = v0 >> 1addl    zero, v0, v0    ; Force canonical form; Answer in v0// MIPS64: Assume a0 = a, a1 = b, sign-extendeddext    a0, a0, 0, 32   ; Zero-extend a0 to 64-bit valuedext    a1, a1, 0, 32   ; Zero-extend a1 to 64-bit valuedaddu   v0, a0, a1      ; 64-bit addition: v0 = a0 + a1dsrl    v0, v0, #1      ; 64-bit shift:    v0 = v0 >> 1sll     v0, #0, v0      ; Sign-extend result; Answer in v0// Power64: Assume r3 = a, r4 = b, zero-extendedadd     r3, r3, r4      ; 64-bit addition: r3 = r3 + r4rldicl  r3, r3, 63, 32  ; Extract bits 63 through 32 from result; (shift + zero-extend in one instruction); result in r3

或者直接访问比本机寄存器更大的SIMD寄存器,当然,从通用寄存器跨越到SIMD寄存器肯定也会增加内存消耗。

如果电脑的处理器支持进位加法,那么还可以采用第三种思路

这时,如果寄存器大小为n位,那么两个n位的无符号整数的和就可以理解为n+1位,通过RCR(带进位循环右移)指令,就可以得到正确的平均值,且不损失溢出的位。

带进位循环右移
// x86-32mov     eax, aadd     eax, b          ; Add, overflow goes into carry bitrcr     eax, 1          ; Rotate right one place through carry// x86-64mov     rax, aadd     rax, b          ; Add, overflow goes into carry bitrcr     rax, 1          ; Rotate right one place through carry// 32-bit ARM (A32)mov     r0, aadds    r0, b           ; Add, overflow goes into carry bitrrx     r0              ; Rotate right one place through carry// SH-3clrt                    ; Clear T flagmov     a, r0addc    b, r0           ; r0 = r0 + b + T, overflow goes into T bitrotcr   r0              ; Rotate right one place through carry

那如果处理器不支持带进位循环右移操作呢?

也可以使用内循环(rotation intrinsic):

unsigned average(unsigned a, unsigned b)
{
#if defined(_MSC_VER)unsigned sum;auto carry = _addcarry_u32(0, a, b, &sum);sum = (sum & ~1) | carry;return _rotr(sum, 1);
#elif defined(__clang__)unsigned carry;sum = (sum & ~1) | carry;auto sum = __builtin_addc(a, b, 0, &carry);return __builtin_rotateright32(sum, 1);
#else
#error Unsupported compiler.
#endif
}

结果是,x86架构下的代码生成没有发生什么变化,MSCver架构下的代码生成变得更糟,而arm-thumb2的clang 的代码生成更好了。

// _MSC_VERmov     ecx, aadd     ecx, b          ; Add, overflow goes into carry bitsetc    al              ; al = 1 if carry setand     ecx, -2         ; Clear bottom bitmovzx   ecx, al         ; Zero-extend byte to 32-bit valueor      eax, ecx        ; Combineror     ear, 1          ; Rotate right one position; Result in eax// __clang__mov     ecx, aadd     ecx, b          ; Add, overflow goes into carry bitsetc    al              ; al = 1 if carry setshld    eax, ecx, 31    ; Shift left 64-bit value// __clang__ with ARM-Thumb2movs    r2, #0          ; Prepare to receive carryadds    r0, r0, r1      ; Calculate sum with flagsadcs    r2, r2          ; r2 holds carrylsrs    r0, r0, #1      ; Shift sum right one positionlsls    r1, r2, #31     ; Move carry to bit 31adds    r0, r1, r0      ; Combine

微软大神的思考们

Raymond Chen1992年加入微软,迄今为止已任职25年,做UEX-Shell,也参与Windows开发,Windows系统的很多最初UI架构就是他搞起来的。

他在MSDN 上建立的blogThe Old New Thing也是业内非常出名的纯技术向产出网站。另外,搜索公众号互联网架构师后台回复“2T”,获取一份惊喜礼包。

这篇博客的评论区们也是微软的各路大神出没,继续深入探讨。

有人提出了新方法,在MIPS ASM共有36个循环:

unsigned avg(unsigned a, unsigned b)
{return (a & b) + (a ^ b) / 2;
}// lw      $3,8($fp)  # 5
// lw      $2,12($fp) # 5
// and     $3,$3,$2   # 4
// lw      $4,8($fp)  # 5
// lw      $2,12($fp) # 5
// xor     $2,$4,$2   # 4
// srl     $2,$2,1    # 4
// addu    $2,$3,$2   # 4

有人针对2016年专利法表示,与其用(a / 2) + (b / 2) + (a & b & 1)的方法,为啥不直接把 (a & 1) & ( b & 1 ) ) 作为进位放入加法器中计算呢?

还有人在评论区推荐了TopSpeed编译器,能够通过指定合适的代码字节和调用约定来定义一个内联函数,以解决“乘除结果是16位,中间计算值却不是”的情况。

只能说,学无止境啊。

原文:
https://devblogs.microsoft.com/oldnewthing/20220207-00/?p=106223

参考链接:
https://news.ycombinator.com/item?id=30252263

-End-

最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。

正文结束

推荐阅读 ↓↓↓

1.心态崩了!税前2万4,到手1万4,年终奖扣税方式1月1日起施行~

2.深圳一普通中学老师工资单曝光,秒杀程序员,网友:敢问是哪个学校毕业的?

3.从零开始搭建创业公司后台技术栈

4.程序员一般可以从什么平台接私活?

5.清华大学:2021 元宇宙研究报告!

6.为什么国内 996 干不过国外的 955呢?

7.这封“领导痛批95后下属”的邮件,句句扎心!

8.15张图看懂瞎忙和高效的区别!

看完微软大神写的 求平均值代码,我意识到自己还是 too young 了相关推荐

  1. 看完微软大神写的求平均值代码,我意识到自己还是 too young 了

    博雯 发自 凹非寺 量子位 | 公众号 QbitAI取整求个无符号整数的平均值,居然也能整出花儿来? 这不,微软大神Raymond Chen最近的一篇长文直接引爆外网技术平台,引发无数讨论: 无数人点 ...

  2. 看完微软大神写的求平均值代码,我意识到自己还是too young了

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 博雯 发自 凹非寺 量子位 | 公众号 QbitAI 取整求个无符号整数的平均值,居然 ...

  3. 服务器运行功率 与额定功率 比例,看完变大神 几招轻松教你看懂电源铭牌

    电源是很多消费者装机的时候容易忽略的地方,装机硬件里CPU与主板是必须匹配的,打个比方,Intel酷睿i7的CPU有些可以装在X79的主板上,有些则只能安装在Z97.Z87等等主板上,所以必须按照相应 ...

  4. 腾讯T2大牛亲自教你!看了谷歌大神写的Spring源码笔记,不愧是大佬

    概述 毫无疑问,Spring Cloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术.不过大多数讲解还停留在对Spring Cloud功能使用的层面,其底层的很多原理,很多人可能并不知晓 ...

  5. C4D也能做3D动画模型?看完建模大神的作品,我也跪了

    今天与大家分享一组C4D做的角色建模. 与一般的建模设计师追求人物越写实越好不同,这组人物带有平面的风格,随性而俏皮. 其中服饰设计也参考了许多红毯造型,下面一起来看看吧▼ 作者的手稿线条随意,但是人 ...

  6. java大神请出来_求java大神,请分析以下代码,写出执行结果,并解释每行结果输出的原因。...

    求java大神,请分析以下代码,写出执行结果,并解释每行结果输出的原因.classPlate{publicPlate(){System.out.println("inPlateconstru ...

  7. Codeforces 793b B. Igor and his way to work 觉得大神写的3维bfs太复杂,突然发现这题是连连看算法。

    看大神写的dp[x][y][c]表示的(x,y)位置c方向的三维DFS, 这题说是转不超过两个弯,其实就是连连看. 扫描S和T左右上下达到的最大点. 左右上下都取S和T达到的最小范围. 1.用左右范围 ...

  8. ❤️MIT大神写给女神的Q版Python画图库—Cutecharts

    MIT大神写给女神的Q版Python画图库-Cutecharts 画图不好看?不可爱?不萌?本文二哥教大家来进行Q版绘图. [建议先点赞.再收藏] 还记得那是一个月黑风高的晚上,一位女同事让我给他讲解 ...

  9. 微软大神“玩”出新花样,求平均值代码还能这样写?

    编译 | 马超       责编 | 苏宓 出品 | CSDN(ID:CSDNnews) 近日,微软神级人物Raymond Chen在个人博客上,发布了一篇关于<如何计算平均值>的博文.这 ...

  10. 升职加薪必看!撸了郭霖大神写的Framework源码笔记,醍醐灌顶!

    前言 2020年,互联网裁员潮下,我度过了人生中最清晰的半年. 今年我本命年,熬过了程序员35的坎,却在36岁被公司破产来了当头一棒,领完了当月份的工资,老板,也是我哥们的老李走进我的办公室,没有迂回 ...

最新文章

  1. 利用Redis进行全页面缓存的简单Demo
  2. 行锁mysql怎么执行_Mysql调用什么情况会用到行锁与表锁
  3. myeclipse中配置spring xml自己主动提示
  4. 鸿蒙系统的全面开源,华为:打造全球的操作系统,鸿蒙今日全面开源!
  5. Swift - 使用NSURLSession同步获取数据(通过添加信号量)
  6. 2015生命之旅---第二站长沙杭州
  7. Gitter - 高颜值GitHub小程序客户端诞生记 1
  8. Android项目中的armeabi,armeabi-v7a和x86
  9. 分布式系统之paxos算法
  10. java开发微信服务号流程
  11. 【MySQL从删库到跑路】-1-安装MySQL-5.7
  12. 西门子200smart与电流表Modbus RTU通讯
  13. 通过selenium抓取新浪微博
  14. 黄金期货对比现货黄金有哪些优势
  15. 【转】人脸识别图像库
  16. 阿里云CentOS7挂载SSD云盘的方法
  17. POI Excel行高设置
  18. 计算机毕业设计JAVA汽车配件管理系统mybatis+源码+调试部署+系统+数据库+lw
  19. bootstrap网格系统,下拉菜单
  20. 管理3.0-培养和提升敏捷领导力摘抄和笔记

热门文章

  1. CrossOver for Mac 怎么用?
  2. COCOS CREATOR(TS)之按钮事件
  3. VR AR体验或成2017圣丹斯电影节“新主角”
  4. Oracle将Java EE移交Eclipse基金会
  5. vscode初次配置
  6. 第三方库之 - SDWebImage
  7. MySQL索引结构--由 B-/B+树看
  8. 外网不能访问图片的问题
  9. 利用SQL SERVER 2008 的XML字段类型实现扩展属性
  10. 在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序