Java源码解析-Math类中的ceil、floor和round

Java源码解析-Math类中的ceil、floor和round

分析源码

这三个方法, 其中的round() 方法可以使用floor() 来实现, 所以我们只需要分析 ceilfloor 的实现方式即可.

在IDEA中创建一个测试项目, 并在main()方法中写下这段代码:

double round = Math.floor(20.5);

这段代码的作用是将输入参数20.5进行向下取整后返回一个浮点数值, 按下Ctrl+鼠标左键点击ceil() 点开该代码的实现,

image-20230814115804105

发现这个方法中没有具体的实现, 而是调用了StrictMath 类中的方法, 接着往下层点:
image-20230814115828618

发现它调用了一个floorOrCeil() 方法, 看来是把向上取整和向下取整合并成为了一个方法, 接着往下看, 终于有了

源代码具体实现:

/**
* Internal method to share logic between floor and ceil.
*
* @param a the value to be floored or ceiled
* @param negativeBoundary result for values in (-1, 0)
* @param positiveBoundary result for values in (0, 1)
* @param increment value to add when the argument is non-integral
*/
private static double floorOrCeil(double a,
                                  double negativeBoundary,
                                  double positiveBoundary,
                                  double sign) {
    int exponent = Math.getExponent(a);

    if (exponent < 0) {
        /*
             * Absolute value of argument is less than 1.
             * floorOrceil(-0.0) => -0.0
             * floorOrceil(+0.0) => +0.0
             */
        return ((a == 0.0) ? a :
                ( (a < 0.0) ?  negativeBoundary : positiveBoundary) );
    } else if (exponent >= 52) {
        /*
             * Infinity, NaN, or a value so large it must be integral.
             */
        return a;
    }
    // Else the argument is either an integral value already XOR it
    // has to be rounded to one.
    assert exponent >= 0 && exponent <= 51;

    long doppel = Double.doubleToRawLongBits(a);
    long mask   = DoubleConsts.SIGNIF_BIT_MASK >> exponent;

    if ( (mask & doppel) == 0L )
        return a; // integral value
    else {
        double result = Double.longBitsToDouble(doppel & (~mask));
        if (sign*a > 0.0)
            result = result + sign;
        return result;
    }
}

这段代码是一个用于执行向下取整或向上取整操作的函数。接受四个参数:a表示需要取整的数值,negativeBoundarypositiveBoundary分别表示负数情况和正数情况下的边界值,sign表示取整方向的符号。

代码的逻辑如下:

  1. 首先,通过调用Math.getExponent(a)函数获取a的指数(exponent)。
  2. 如果a的指数小于0,即小于1的绝对值,则根据以下情况返回结果:
    • 如果a等于0.0,则返回0.0。这是为了处理-0.0和+0.0的情况。
    • 如果a小于0.0,则返回negativeBoundary
    • 如果a大于0.0,则返回positiveBoundary
      这一部分的目的是处理小于1的数值,根据正负号返回最接近的整数值。
  3. 如果a的指数大于等于52,则a为无穷大(Infinity)、不是一个数字(NaN)或者一个超过限制必须取整的数值,直接返回a
  4. 如果代码执行到这里,说明a是一个整数值,或者需要取整到整数值。
  5. 使用Double.doubleToRawLongBits(a)a转换为对应的二进制表示,并将其存储在名为doppellong类型变量中。
  6. 使用DoubleConsts.SIGNIF_BIT_MASK右移指数位数得到名为mask的掩码,用于判断a的小数部分。
  7. 如果maskdoppel按位与的结果等于0,则a是一个整数值,直接返回a
  8. 否则,执行以下操作:
    • 使用doppel(~mask)按位与的结果得到不含小数部分的数值,存储在名为resultdouble类型变量中。
    • 如果原始a乘以sign大于0.0,即asign的符号相同,则将result加上sign
    • 返回最终的result作为结果。

这段代码的目的是根据指定的边界和符号来取整一个给定的数值。它主要处理小于1的数值和大到必须取整的数值,对整数值或需要取整到整数值的数值不做处理。函数中使用了一些位操作和类型转换来判断是否需要取整,并进行取整操作。

分析Floor()

为了方便研究, 将这段代码复制到主程序中, 稍后用来断点调试会较为方便, 同时我们注意到这个方法的四个参数可以来决定是向上或向下取整, 在下面的代码里, 我们首先研究向下取整, 所以直接将该方法设置为向下取整时的参数:

// 向下取整
public static double floor(double a)
{
    double sign=-1.0;
    double negativeBoundary=-1;
    double positiveBoundary=0;

    int exponent = Math.getExponent(a);// 指数: 2^?

    if (exponent < 0)//指数小于0,为正负纯小数, 即(-1,1)区间
    {
        /*
             * Absolute value of argument is less than 1.
             * floorOrceil(-0.0) => -0.0
             * floorOrceil(+0.0) => +0.0
             */
        return ((a == 0.0) ? a :
                ( (a < 0.0) ?  negativeBoundary : positiveBoundary) );
    }
    else if (exponent >= 52) // IEEE Long Real:64位	符号1位,指数11位,尾数52位。也称为双精度。
    {
        // double = 尾数 * 2^exponent, 指数就相当于要把小数点 点在尾数的第几位
        // 当指数大于52时, 就意味着尾数全部都用来存储整数了, 小数没有地方存储了
        /*
             * Infinity, NaN, or a value so large it must be integral.
             */
        return a;
    }

    long doppel = Double.doubleToRawLongBits(a);//double to long, double和long都是64bit的
    long mask   = DoubleConsts.SIGNIF_BIT_MASK >> exponent;// 小数掩码: 有效位掩码(0~51位)>>指数,  DoubleConsts.SIGNIF_BIT_MASK=0xFFFF FFFF FFFF F
    // System.out.println("mask = " + mask);//0~47位, 正是20.5小数所在的区域

    if ( (mask & doppel) == 0L )// 小数部分==0?
        return a; // integral value(整数)
    else {
        double result = Double.longBitsToDouble(doppel & (~mask));//整数部分
        if (sign*a > 0.0)//根据正负号来确定要+1或-1
            result = result + sign;
        return result;
    }
}

Math.getExponent(a); 这个方法是得到浮点数a的指数部分,这个指数不是我们的科学计算法中的以10为底的指数,而是以2为底的指数

上面的代码中主要是利用了浮点数类型的底层构成来区分整数部分和小数部分, 关于浮点数的详解可以查看我的另外一篇文章: 关于IEEE754浮点数类型_Missper~的博客-CSDN博客

重写CeilOrFloor()

根据源码我们了解到浮点数取整的思路, 采用其他办法也可以实现同样的功能

重写后的方法只用传递两个参数:

  • a 表示需要取整的数值
  • fix 表示是向下或向上取整, -1为向下取整, 1为向上取整
public static double myFloorOrCeil(double a, int fix)
{
    int res=0;
    //正数或负数
    if (a<0.0)
    {
        res=(int)a;
        if (fix == -1)
        {
            String s = a + "";
            String[] split = s.split("\\.");
            if (!split[1].equals("0") && !split[0].equals("0"))// 小数不为0 + 整数不为0 才减一
                res += fix;
        }
    }
    else if(a>0.0)
    {
        res=(int)a;
        if (fix == 1)
        {
            String s=a+"";
            String[] split = s.split("\\.");
            if (!split[1].equals("0"))
                res+=fix;
        }
    }
    //等于0
    return res;
}

封装一下:

img-LgLAv5ni-1692005060902

测试正确性:
img-XJpdosT9-1692005060904

可以看到预期结果与实际结果完全一致
img-m5AxxYP6-1692005060905

重写Round()方法

这个方法是用来对浮点数进行四舍五入操作的, 重写后的代码如下:

public static double myRound(double a)
{
    return myFloor(a+0.5);
}

测试正确性:
img-KyxUpytX-1692005060907
可以看到我们自己写的代码与官方的结果一致:

img-WNdlHZnl-1692005060908

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值