美文网首页程序员
Java和Python中的整数除法,取余,舍入

Java和Python中的整数除法,取余,舍入

作者: 曾会玩 | 来源:发表于2015-10-26 15:20 被阅读857次

    关于除法,你也许觉得没什么值得谈论的,毕竟小学的时候体育老师就教过我们了。然而对于编程中使用的除法,我觉得还是有很多值得注意的细节的。为什么我想深究一下?因为我日常主要使用Java和Python编程,而它们的除法在细节上有很多不同之处,全是坑啊…所以接下来我也将着重于Java和Python,但是相信我,就算你不用Java和Python,也会有所收获的。

    1.整数除法

    对两个不能整除的整数做除法,就要面对舍入的问题。和大多数编程语言一样,Java的基本策略是向零取整(round to zero),也就是向绝对值变小的方向取整。举几个香甜的小栗子:3/2=1, -3/2=-1。而对于Python而言,情况就有所不同了。

    >>>-1/10
    -1```
    显然如果按照Java的取整策略,-1/10应该得0,而Python给出的结果是-1。事实上Python的取整方式是向下取整,也就是向着数轴上负无穷的方向取整。
    好吧,Java和Python的取整方式不同,夺大点事儿啊…那么如果我们要在Python下采用向零取整的结果,咋整?一种比较直接的方式是:
    ```python
    >>>int(float(-1)/10)
    0```
         
    ##2.取余
    
    谁说没大事?(╰( ̄▽ ̄)╭) 大事来了!
    
    Java和Python整数除法都遵循下面这个公式:
    
    >(a/b)*b+c=a
    
    也就是说:
    
    >a mod b=c=a-(a/b)*b
    
    这里的/表示的是整数除法。既然它们的取整方式不一样,那么取余也会受到影响:
    
    >For Java: -2 % 3==-2
    For Python: -2 % 3==1
    
    在某些实际应用中,我们可能会被要求得到一个整数的各位数字。如果输入的整数的正的,Java和Python都可以用相同的方法来解决: 
    ```python
    def func(a):
        pos, res=1, []
        while a/pos:
            res+=(a/pos)%10,
            pos*=10
        return res```
    
    Java代码也差不多就是这样了。但如果输入的整数是一个负数,Java版本的代码还是可以得到正确的结果,而Python不能(曾经在这里被坑的,举手)。那怎样用Python正确地搞定这个问题嘞?可以先去绝对值和符号,当正数来处理,最后再在结果里搭上符号。 
    ##3. Follow-ups
    ###3.1 Python中的另一个除法操作
    我们知道,在Python中,基本的除号“/”是被重载了的。当两个操作数都是整数时,进行整数除法,得到整数结果,否则进行浮点数除法(真除法),得到浮点数结果。从Python 2.2开始,另一个除号被引入://,它只执行整数除法。注意,//的结果类型依操作数而定。
    ```python
    >>>1.0/2
    0.0
    >>>1.0//2.0
    0.0
    >>>1//2
    >0```
    
    另外,如果想同时得到商和余数,可以使用内建的函数divmod,结果是一个tuple。
    ```python
    >>>divmod(7, 2)
    (3, 1)
    >>>divmod(7.0, 2)
    (3.0, 1.0)```
    
    ###3.2 Python中的舍入
    除了缺省的舍入方式,Python还有多种舍入可供选择。
    **Floor rounding:**
    ```python
    >>>import math
    >>>math.floor(1.2)
    1.0
    >>>math.floor(-1.2)
    -2.0```
    
    **Ceiling rounding:**
    ```python
    >>>math.ceil(1.2)
    2.0
    >>>math.ceil(-1.2)
    -1.0```
    
    **Round-off:**
    ```python
    >>>round(0.5)
    1.0
    >>>round(-0.4)
    -0.0
    >>>round(-0.5)
    -1.0```
    
    内嵌的round函数也可以一个指定保留小数位数的参数:
    ```python
    >>>round(0.21, 1)
    0.2
    >>>round(0.21, 2)
    0.21```
    
    **Caution !**
    ```python
    >>>round(2.675, 2)
    2.67
    

    咦?bug啦?!当然不是。这里要明确一件事:计算机只认识0,1(量子计算机?懵)。就是说,我们输入的十进制数,在计算机内部都是用二进制来表示的。有的十进制数可以用二进制准确地表示出来,比如十进制的0.125可以表示为0b0.001;然而很多的小数是没法用二进制数精确表示的,计算机里存储的是它们的近似值,例如十进制的0.1,用二进制表示,可以近似为: 0b0.00011001100110011001100110011001100110011001100110011010,所以当我们把它换回十进制数以输出或者使用,得到的值就是0.1000000000000000055511151231257827021181583404541015625。也就是说,0.1在计算机里并不是刚好等于1/10的。

    >>>0.1+0.2
    0.30000000000000004
    

    同样,当我们运行round()函数,也是对计算机中实际存储的值近似取舍。2.67实际上近似为2.67499999999999982236431605997495353221893310546875,你看第三位小数是4,那么round(2.675, 2)就相当于round(2.674, 2),结果当然是2.67。值得注意的是,这种现象是广泛存在于各种计算机和各种编程语言的,不是bug,只是有的语言选择了不让你看到。

    3.3 Java中的舍入

    Java提供了floor和ceil方法来实现向下和向上取整。

    Math.floor(2.9)
    Math.ceil(2.1)

    这俩函数简单方便,居家旅行必备。另外Java中也有个round函数,可以实现各种复杂的取整。

    System.out.println(Math.round(0.5));
    //输出 1
    System.out.println(Math.round(-0.5));
    //输出 0
    System.out.println(Math.round(-0.51));
    //输出 -1```
    
    这什么鬼!Keep Calm and Carry On!
    数学上有多种不同的策略来进行取整,比如我们体育老师教的四舍五入。各种取整策略的共同点就是要做真值作近似,那就会引入偏差。四舍五入显然并不是一种公平的策略(想想0~4的舍和5~9的得)。
    有一个叫做银行家舍入(Banker’s Rounding)的东西,不造你听过没,反正我是最近才知道的。事实上.NET和VB6都是默认采用这种方式,而且IEEE 754默认采用这种Rounding。Banker’s Rounding 也就是** round to even **策略。
    假设当前考虑那位的数字是d(其实d就是将不被保留的第一位),如果d<5,则舍(round to zero);如果d>5,则入(round away from zero);而当d==5时,就要根据d前后的数位来确定往哪边取了。
    >1) 如果d之后存在非零的数位,则入;
     2)如果d之后不存在非零的数位,则看d之前的一个数位,用c表示:
        a.如果c是奇数,则入;
      b.如果c是偶数,则舍。
    
    再来一把栗子,对下列数保留0位小数,
    第一位小数就是d,整数位就是c:
    >BankRound(0.4)==0,  BankRound(0.6)==1,  BankRound(-0.4)==0,  BankRound(-0.6)==-1
    BankRound(1.5)==2.0,  BankRound(-1.5)==-2.0,  BankRound(2.5)==2.0,  BankRound(-2.5)==-2.0
    BankRound(1.51)==2.0,  BankRound(-1.51)==-2.0,  BankRound(2.51)==3.0,  BankRound(-2.51)==-3.0
    
    可以看出,Banker’s Rounding对正数和负数的处理是对称的,因此不会引入符号带来的偏差。另外它以均等的几率来舍入数位(考虑c, c有各一半的几率为奇数和偶数),所以多次舍入后与真值的差别会较小。
    
    扯了这么多,跟Java的**Math.round( )**有什么关系呢?我也是写到这才发现,好像没什么软(luan)关系。因为它并没有遵循Banker’s rounding。而是按照以下策略进行取整:
    当考虑的数位d不是5,d<5就舍,d>5则入。
    当d==5:
    >a.如果d的右边有非零数位,则入;
    >b.如果d的右边没有非零数位,则** round to ceiling**,即对负数舍,对正数入。
    
    [Java文档里是这么表述的](http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html)
    
    还有还有, 在Java里可以使用** BigDecimal **和** RoundingMode **实现更通用的取整方式。
    ```java
    double d=-2.5;
    BigDecimal bd=new BigDecimal(d);
    double nd=bd.setScale(0,
    RoundingMode.HALF_EVEN).doubleValue();
    System.out.println(nd);
    //输出 -2.0```
    
    ** setScale **的第一个参数是保留的小数位数,第二个参数是舍入模式。可选的舍入模式有:
    **HALF_EVEN**, 也就是银行家方式;
    **HALF_UP**, 四舍五入;
    **HALF_DOWN**, 五舍六入;
    **CEILING、FLOOR**, 向正无穷、负无穷方向;
    **UP、DOWN***, 向零和远离零;
    **UNNECESSARY**, 断言舍入后的值和原值相等,也就是不需要舍入。如果断言错了,抛出**ArithmeticException**异常。
    
    先写到这,比较粗糙,但是希望你有所收获吧。欢迎讨论,有话好好说(╰( ̄▽ ̄)╭)
    
    本文遵守[知识共享协议:署名-非商业性使用-相同方式共享 (BY-NC-SA)](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/)及[简书协议](http://www.jianshu.com/p/c44d171298ce)
    转载请注明:**作者[曾会玩](http://www.jianshu.com/users/5e81a35c8586/latest_articles)**

    相关文章

      网友评论

        本文标题:Java和Python中的整数除法,取余,舍入

        本文链接:https://www.haomeiwen.com/subject/vwrxhttx.html