Java,Math类中的ceil、floor和round函数源码解析以及自己重写实现
1. ceil、floor和round的功能
首先,這三個方法都是Math類的靜態(tài)方法,而且類Math在java.lang包下,所以我們在代碼中可以直接調(diào)用Math的方法。
Math.ceil(double a)實現(xiàn)的是對小數(shù)向右取整,如 Math.ceil(-0.7) = -0.0,Math.ceil(0.5) = 1.0, Math.ceil(1.3) = 2.0
Math.floor(double a)實現(xiàn)的是對小數(shù)向左取整,如 Math.floor(-0.7) = -1.0,Math.floor(0.5) = 0.0, Math.floor(1.3) = 1.0
Math.round(double a)實現(xiàn)的邏輯是四舍五入,但是對于負數(shù)有點不一樣,如 Math.round(-1.5) = -1,Math.round(-0.5) = 0,有點繞,所以為了好記點,等效為 Math.floor(a+0.5),而且返回的是整數(shù)。
?
2. ceil、floor源碼?
2.1 ceil和floor代碼
因為round可通過floor來實現(xiàn),所以round源碼就不多加分析,主要分析ceil和floor方法,從下圖我們可以知道ceil和floor方法實際上都是調(diào)用floorOrCeil方法實現(xiàn),只是參數(shù)不一樣。參數(shù)后續(xù)分析,所以我們接下來分析floorOrCeil這個方法。
2.2??floorOrCeil源碼
看了源代碼,有點復雜,接下來我們一段一段分析?
?
2.2.1?
int exponent = Math.getExponent(a);這個方法是得到浮點數(shù)a的指數(shù)部分,這個指數(shù)不是我們的科學計算法中的以10為底的指數(shù),這個指數(shù)是以2為底的指數(shù)。不懂就舉例,如82.2的指數(shù)為6,0.23的指數(shù)為 -3,-0.1的指數(shù)為 -4,-6.3的指數(shù)為2,這個是怎么算的?
不知道怎么算出來的,那我們反推一下這些數(shù)字用指數(shù)怎么表示的,如下圖,不知道你們能不能看懂,如果看不懂慢慢分析一下吧,講也不太好講。
?
2.2.2?
在知道指數(shù)怎么算出來后,我們應該知道指數(shù)小于0的情況是什么了,其實就是浮點數(shù)范圍處于 -1<a<1,接下來用到了三目運算符 a ?b:c,如果a==0.0,注意 -0.0=0.0,?直接返回a?,如果不是0.0,判斷是負數(shù)還是整數(shù),如果是負數(shù)取負邊界,如果是正數(shù),取正邊界。這個時候用到了我們前面提到的floorOrCeil參數(shù)問題,我們先考慮如果是ceil方法,ceil方法調(diào)用floorOrCeil,傳入的負邊界是 -0.0,正邊界是1.0,那么負數(shù)取負邊界得到 -0.0,整數(shù)取正邊界得到 1.0,剛好是向右取整;如果是floor調(diào)用floorOrCeil,傳入的負邊界是 -1.0,正邊界是0.0,負數(shù)取負邊界 -1.0,整數(shù)取正邊界 0.0,剛好是向左取整。
?
2.2.3?
如果指數(shù)大于52會怎么樣呢?這里涉及浮點數(shù)的底層存儲了,接下來請仔細閱讀,如下圖。
一個double浮點數(shù)8個字節(jié)64位,一位符號位,11位存放指數(shù)值,52位存放數(shù)值,不懂就舉例。比如76.3,十進制小數(shù)轉(zhuǎn)為二進制不懂的可以看我另一篇博客,76.3轉(zhuǎn)為二進制是 100 1100.01001 1001 1001... (1001一直循環(huán)),和以10為底的指數(shù)原理一樣,這個數(shù)字以2為底的指數(shù)知道是幾位嗎?對的,指數(shù)段是6,使用了指數(shù),以2為底的指數(shù),我們用P來表示,那么76.3就變?yōu)榱?1.00110001001 1001 1001...P6。然后接下來76.3的64位是怎么存儲的呢?符號位是0,指數(shù)是6,但是還要加上 1023,也就是1029,指數(shù)段二進制表示是 10000000101,然后數(shù)值段記錄的數(shù)據(jù)是小數(shù)位,也就是 1.00110001001 1001 1001...E6截取 00110001001 1001 1001...?部分,要完整表示52位的話,數(shù)值段二進制則是 0011 0001 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011,至此,我們得到了76.3的二進制表示,接下來我們通過代碼驗證一下,如下。
現(xiàn)在我們來談談如果指數(shù)位大于等于52會怎么樣?當指數(shù)位等于52時,那么數(shù)值段存的內(nèi)容都是整數(shù)部分的二進制,小數(shù)部分根本沒有存,所以會有官方注釋提的“a value so large it must be integral”,也就是說這個浮點數(shù)小數(shù)部分在52位數(shù)值段得不到存儲,所以認為是整數(shù),那么就直接return。
?
2.2.4
“assert exponent >= 0 && exponent <= 51;”沒什么好說的,進一步判斷指數(shù)位是否處于0和51之間。從if語句中的mask和doppel相與和0比較,我們可以推斷程序是想判斷浮點數(shù)的小數(shù)部分是不是都是0,如果小數(shù)部分是0,我們可以直接返回。經(jīng)過上面對浮點數(shù)的存儲分析,我們怎么得到浮點數(shù)的小數(shù)部分呢?
首先,小數(shù)部分在數(shù)值段存儲,但是指數(shù)部分也在數(shù)值段中存儲,因為我們可以得到指數(shù)部分的位數(shù),這樣我們就可以通過向左移位把指數(shù)段的11位和數(shù)值段的指數(shù)部分移除,注意:符號位是不會被移除的。所以如果是負數(shù)的話,我們在移位結束之后還得把二進制的首位符號位去掉。
源碼中首先通過Double.doubleToRawLongBits(a)求得浮點數(shù)的二進制表示 0100 0000 0101 0011 0001 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011,然后將DoubleConsts.SIGNIF_BIT_MASK >> exponent向右移動,其中DoubleConsts.SIGNIF_BIT_MASK是個常量,數(shù)值大小如下圖,exponent是6。因此得到二進制表示 0000 0000 0000 0000 0011 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111。然后進行 與 運算,最后得到浮點數(shù)的小數(shù)部分二進制表示為 0000 0000 0000 0000 0001 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011,然后再和0比較,如果相等,說明小數(shù)部分是0,那么說明這個浮點數(shù)其實是個整數(shù),直接return。
?
?2.2.5
終于來到了最后,上面計算得到小數(shù)部分不是0,那我們需要先得到浮點數(shù)的整數(shù)部分,然后再考慮是向右取整還是向左取整?。先前求得的mask小數(shù)部分的二進制位全是1,現(xiàn)在取反則是符號位、指數(shù)段和數(shù)值段指數(shù)部分的二進制是1,那么 (~mask)的二進制表示是?1111 1111 1111 1111 1100?0000 0000 0000 0000?0000 0000 0000 0000 0000 0000 0000,和doppel進行與運算得到?0100 0000 0101 0011 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000,該二進制表示為76.0。緊接著判斷是ceil還是floor,如果是ceil,那么sign是1.0,然后如果浮點數(shù)是負數(shù),我們剛剛取整相當于已經(jīng)向右取整,只考慮正數(shù),所以sign*a>0.0,整數(shù)+1(sign是 1.0);如果是floor,那么sign是 -1.0,然后如果浮點數(shù)是正數(shù),我們剛剛取整相當于已經(jīng)向左取整,只考慮負數(shù),所以sign*a>0.0,整數(shù)-1(sign是 -1.0),到此源碼分析結束。
?
?
3. 自己重寫實現(xiàn)ceil和floor以及round方法
我們自己重寫的話,主要是取整那一塊代碼可以修改,其它的比較簡單容易理解也不需要重寫。所以,我們有什么方法可以取出浮點數(shù)的整數(shù)部分呢?
public static double ceilOrFloor(double a, double negative, double positive, double sign){int exp = Math.getExponent(a);if(exp<0){return (a==0.0) ? a : (a<0.0 ? negative : positive);}else if(exp>51){return a;}String s = String.valueOf(a);String s2 = s.substring(0, s.indexOf('.'));double result = Double.valueOf(s2);if(a==result){return a;}else{if(sign*a > 0.0)result += sign;return result;}}public static double floor(double a){return ceilOrFloor(a, -1.0, 0.0, -1.0);}public static double ceil(double a){return ceilOrFloor(a, -0.0, 1.0, 1.0);}public static long round(double a){return (long)floor(a+0.5);}?
部分測試結果如下
?
總結
以上是生活随笔為你收集整理的Java,Math类中的ceil、floor和round函数源码解析以及自己重写实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是泛型,为什么要使用泛型? 泛型类和
- 下一篇: 修饰符private和protected