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