Java源码解析-Math类中的ceil、floor和round
分析源码
这三个方法, 其中的round()
方法可以使用floor()
来实现, 所以我们只需要分析 ceil
和floor
的实现方式即可.
在IDEA中创建一个测试项目, 并在main()方法中写下这段代码:
double round = Math.floor(20.5);
这段代码的作用是将输入参数20.5进行向下取整后返回一个浮点数值, 按下Ctrl+鼠标左键点击ceil()
点开该代码的实现,
发现这个方法中没有具体的实现, 而是调用了StrictMath
类中的方法, 接着往下层点:
发现它调用了一个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
表示需要取整的数值,negativeBoundary
和positiveBoundary
分别表示负数情况和正数情况下的边界值,sign
表示取整方向的符号。
代码的逻辑如下:
- 首先,通过调用
Math.getExponent(a)
函数获取a
的指数(exponent)。 - 如果
a
的指数小于0,即小于1的绝对值,则根据以下情况返回结果:- 如果
a
等于0.0,则返回0.0。这是为了处理-0.0和+0.0的情况。 - 如果
a
小于0.0,则返回negativeBoundary
。 - 如果
a
大于0.0,则返回positiveBoundary
。
这一部分的目的是处理小于1的数值,根据正负号返回最接近的整数值。
- 如果
- 如果
a
的指数大于等于52,则a
为无穷大(Infinity)、不是一个数字(NaN)或者一个超过限制必须取整的数值,直接返回a
。 - 如果代码执行到这里,说明
a
是一个整数值,或者需要取整到整数值。 - 使用
Double.doubleToRawLongBits(a)
将a
转换为对应的二进制表示,并将其存储在名为doppel
的long
类型变量中。 - 使用
DoubleConsts.SIGNIF_BIT_MASK
右移指数位数得到名为mask
的掩码,用于判断a
的小数部分。 - 如果
mask
与doppel
按位与的结果等于0,则a
是一个整数值,直接返回a
。 - 否则,执行以下操作:
- 使用
doppel
与(~mask)
按位与的结果得到不含小数部分的数值,存储在名为result
的double
类型变量中。 - 如果原始
a
乘以sign
大于0.0,即a
和sign
的符号相同,则将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;
}
封装一下:
测试正确性:
可以看到预期结果与实际结果完全一致
重写Round()方法
这个方法是用来对浮点数进行四舍五入操作的, 重写后的代码如下:
public static double myRound(double a)
{
return myFloor(a+0.5);
}
测试正确性:
可以看到我们自己写的代码与官方的结果一致: