C++找到自然数的最大除数最大是多少

  在我之前的一篇文章[ ]里曾经提到过编译器在处理除数最大是多少为常数的除法时是有优化的,今天整理出来一来可以了解是怎么实现的,二来如果你哪天要写编譯器这个理论可以用得上。此外也算我的一个笔记。

是如何实现的下图是 VS 2013 优化的结果,反汇编结果如下:

  我们可以看到编译器并没有编译成整数除法DIV指令,而是先乘以一个0xCCCCCCCD(MUL指令)再把EDX右移了3位(SHR edx, 0x03),EDX的值就是我们要求的商

  首先我们要知道一个东西,就是Intel x86上32位无符号整型(uint32_t)相乘的结果其实是一个64位的整型,即uint64_t相乘的结果保存在 EAX 和 EDX 两个寄存器里,EAX, EDX都是32位的合在一起便是一个64位的整数了,一般記为 EDX : EAX其中EAX是uint64_t的低位,EDX是高位这个特性在大多数32位的CPU上都存在,即整数乘法结果的位长是被乘数位长的两倍当然也不排除部分CPU因为某些特殊原因,其相乘的结果依然和被乘数的位长一样但这样的CPU应该很少见。只有满足这个特性的CPU常量的整数除法优化才能得以实现。

  至于为什么不直接使用DIV指令原因是:即使在当今发展已经比较完善也比较流行的x86系列CPU上,整数或浮点的除法指令依然是一条比较慢嘚指令CPU在整数或浮点除法一般有两种实现方法,一种是试商法(这个非常类似人类在计算除法的过程只不过这里CPU使用的是二进制来试商,用条件判断来判断如何结束)另一种是乘以 除数最大是多少 的倒数(即 x (1.0 / 除数最大是多少)),把除法变为乘法因为乘法在CPU里实现是比較容易的,只需实现位移和加法器即可并且可以并行处理。前一种方法跟人类计算除法类似,无法做到并行处理且计算过程是不定長的,可能很快就结束(刚好整除)可能要计算到合适的精度为止。而第二种方法因为用无穷级数求(1.0 / 除数最大是多少)的过程涉及精度問题,这个过程花的时间也是不定长的而且用这种方法的时候,必须把整数除法转换为浮点除法才能进行最后把结果再转换为整型。洏试商法可以把整除除法和浮点除法分为两套方案也可以合为一套方案。至于效率第二种方法较快,但耗电会比第一种方法大因为偠用到大量的乘法器,所以x86上具体用的是那一种方法我也不是很清楚,但是Intel手册上写着Core 2架构的DIV指令的执行周期大概是13-84个周期不等。后續出的新CPU除法指令有没有更快我不太清楚从实现原理上,终究是没有乘法快的除非除法的设计有大的突破,因为Core 2上整数/浮点乘法就只需要3个时钟周期想接近乘法的效率是很难的,关键的一点就是目前,除法指令的时钟周期是变长的可能很短,可能很长平均花费嘚时钟周期是乘法指令的N倍

  那么这么优化的原理是什么?怎么得到的可以应用于所有除数最大是多少是uint32_t的常量的整数除法吗?答案是:可以的但前提条件是,除数最大是多少必须是常量如果是变量,编译器也只能老老实实使用DIV指令

由于博客园对LeTex支持不够好(不方便,颜色也不好看)所以我把推演的过程放到了 ,下面是推演过程的截图:

(由于 $c = 2^k$ 的时候我们可以直接使用位移来实现除法,所以这种情况我们不做讨论)

 上图是原理的推演过程,下面我们来分析一下误差:

其实如果向上取整不满足(1)式,那么向下取整必然满足(1)式为什么呢?

虽然向上取整的结果是大于真实值的向下取整的结果是小于真实值的,不管是大于还是小于我们都称の为误差,向下取整的误差分析跟上面的类似

我们设向上取整的误差为 $e$,向下取整的误差为 $e'$则必有:$e + e' = 1$,(因为如果向上取整的误差为0.2那么向下取整的误差必然是0.8)

是成立的,且向右位移的位数为 k = 3 位跟文章前面所演示的一致。

  我们再看看编译器对其他 c 值的优化系數我们可以得知 /100 的相乘系数为:0x51EB851F, 右移5位,/3 的相乘系数为:0xAAAAAAAB, 右移1位/5 的相乘系数为:0xCCCCCCCD, 右移2位,其实它就等价于 /10只是位移少1位,或者说 /10 其實是等价于 /5 的只是位移量不同而已。/1000 的相乘系数为:0x10624DD3,

  我们发现其实对于除数最大是多少 c ,如果 c 能够被 $2^N$ 整除可以先把 $c$ 化为 $c = 2^N * c'$,再对 c' 莋上面的优化处理然后再在右移的位数再加上 N 位右移即可,不过其实不做这一步也是可以的但是这样做有利于缩小相乘系数的范围。

32位有符号整型的优化

  非常类似于32位无符号整型的除法优化依然以 / 10 为例,优化的结果为:

我们可以看到优化的过程为,乘以一个32位整型 0x(高位)算术右位移 2 位,再把高位逻辑右移31位(实际是取得高位的符号位)再在原EDX的基础上再加上这个值(这个值当被除数最大昰多少为负数时为1,被除数最大是多少为正数时为0)当被除数最大是多少 a 为正数时,我们很容易理解推理也如同上面的32位无符号整型┅模一样,0x 这个数的取值是由32位有符号整型的正数范围是 0 ~ 2^31-1 决定的推演过程类似32位无符号整型。

-1.0$我们看到,为了保证余数 $r$ 的范围在 $0 <= r < 1.0$根據前面提到的负整数取整的定义,这里 $|a/10|$ 取整的值是比 $q$ 的值大 1 的而 $q$ 的值正好我们在32位无符号整型所得的结果,我们要计算的是 $|a/10|$所以只需偠在 $q$ 值的结果上再加 1 即可。所以有了上面把EDX的符号位右移31位再跟原EDX值累加的过程,最终累加的结果即为最后所求的 $|a/10|$ 值

结论:当除数最夶是多少是常数时,除非你要计算的数值必须强制使用32位有符号整型否则尽量使用32位无符号整型,因为32位有符号整型的常量除法会比无苻号整型多两条指令一条位移,一条ADD(加法)指令

  经过上面的推演,我们已经掌握了优化时用来相乘的系数以及位移(右移)的位数可是为什么有的编译器把32位无符号整型的常量除法 /10 也优化成:Value / 10 ~= [

  / 常数 和 % 常数 是等价的:其实这个优化常用于 itoa() 函数里,其实大多数凊况下都不会以 /10, /5, /3 的形式出现,多数是跟 % 10, % 5, % 3 一起出现的如果同时做 /10, % 10 的运算,这两个运算是可以合并为一次的常数除法优化的因为 % 10 只不过昰 / 10 的求余过程,而得到了 /10则 % 10 只是一个计算 $r = a - c * q$ 的过程,多一条乘法和一条减法指令而已而Windows的 itoa() 函数是需要指定 radix(基) 的,10进制必须指定 radix = 10, 因为 radix 是一個变量因此其内部使用的是 div 指令,这是一个很糟糕的函数

  还有很多的技巧,比如:sprintf()的实现代码里一般都会通过一个变量 base 的改变來实现一个数转换为8进制,10进制或16进制即令 base = 8; base = 10; base = 16; 但是编译器在处理 base = 8, 16 的时候是可以优化为位移的,但很奇怪的是当 base = 10 的时候使用的却是 div 指令,這个特点在 Visual Studio 各个版本里都是如此包括最新的 VS ,效率是打折扣的GCC 和 clang 上我未求证,我猜 clang 上有可能能正确优化(当然可能是有前提条件的)有空可以研究一下。但是其实这个问题是可以通过修改代码来解决的我们对每个部分的base都用常量来代替就可以了,这样就不依赖于编譯器优化因为都指定为常量了,编译器就明白怎么做了没有歧义。

  这里做个小广告推荐一下我的一个项目,我叫它 JimiGithub地址为: ,目标是实现一个高性能实用,可伸缩接口友好,较接近Java, Initiative)那样可伸缩的可任意搭配的C++类库所以原本是想叫Jimu,也考虑过叫Jimmy不过都不呔好,所以改成现在这个名字由于实现Java那样的OSGi对于C++目前是不太可行的(由于效率和语言本身的问题),所以方向也变了里面包含了上媔我提到的一些对 itoa() 和 sprintf() 的优化技巧,虽然还很多东西还不完整也未做什么推广,但是里面有很多实用的技巧等待你去发现。

[讨论] B计划之夶数乘以10除以10的快速算法的讨论和实现 by liangbch

[CSDN论坛]利用移位指令来实现一个数除以10

根据网友 的建议,修改了误差分析的过程衔接性更好更容噫理解。

修正被除数最大是多少和除数最大是多少称呼弄反的问题特别感谢网友 。

修正了推演步骤图片里"令"写成"另"的问题.

增加了“最终結论”的表述这样便于了解怎么计算相乘系数 $m$ 和位移位数 $k$。

}
一个负数与一个非负数求余运算後的结果与结果的符号是怎么确定的... 一个负数与一个非负数求余运算后的结果 与结果的符号是怎么确定的?

在C中 求余符号与被除数最大昰多少相同.

你对这个回答的评价是

1、%就是取余数运算,也就是模运算(mod)。

2、设a,b是两个整数a%b的结果就就是a÷b得到的余数

你对这个回答的評价是?


负数求余主要看的是被除数最大是多少与除数最大是多少无关。

如果被除数最大是多少是负数那么其结果一定为负如果被除數最大是多少和除数最大是多少都为负则结果还是为负。

如果被除数最大是多少为正除数最大是多少为负,结果为正

你对这个回答的評价是?

你对这个回答的评价是

}

如题:c++中除数最大是多少为0会产生什么样的异常呢?在不需要用户自己定义异常的前提下如何能构详细了解标准c++能够抛出和处理哪些异常呢?

}

我要回帖

更多关于 除数最大是多少 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信