這篇文章源於我上一週所讀的一篇12年的文章。原作者提出了一個問題,如果js沒有原生方法Math.round(),我們如何去實現呢?
對此我和我的基友進行了小小探討,並給出了一些有意思的答案。
如果…沒有方法
這篇文章源於上週所讀的一篇2012年的文章。原作者在使用了Math.round()方法之後,突然產生了一個小念頭。
如果,js沒有Math.round()方法,我們又該如何去實現呢?
為此展開了一些探討。我知道發表這麼一篇文章肯定小有爭議,但仍需要註明的是這篇文章僅供娛樂,或者說——玩玩程式碼,不會太在意效能、健壯、邏輯嚴謹性等XXOO的東西。
Math.round()就是傳說中的四捨五入啦…
Math.round(12.1);//12
Math.round(12.8);//13
Math.round(-12.1);//-12
Math.round(-12.8);//-13
解決方案
原作者提供了這麼些思路:
例如數字6.2,先把6.2轉換為字串,然後透過String.prototype.split()方法來分割字串,判定字串索引為1的值是否大於5,再處理索引為0的值,程式碼如下:
//num===6.2
function round(num) {
var nums = String(num).split(“.”),
//[6,2]
num0 = nums[0],
//6
num1 = nums[1]; //2
if (parseInt(num1.substring(0, 1)) < 5) { //2<5
return parseInt(num0);
} else {
if (num0 > 0) {
return parseInt(num0) + 1;
} else { //負數
return parseInt(num0) – 1;
}
}
}
原作者並不滿意上面的解決方案,提出瞭如果連js原生方法都不使用呢?什麼String()、parseInt()都不使用該怎麼去做呢?
於是提出了第二種解決方案:
這個問題的關鍵在於判定小數點後的數字是否大於5,所以我們把傳遞進來的數字6.2*10%10即可得到小數點後的數字,這時候再判定這個小數是否大於5即可。
//num===6.2
function round(num) {
var round_x = (((10 * num) % 10) > 0) ? ((10 * num) % 10) : //正數
– ((10 * num) % 10); //負數
if (round_x < 5) {
return num – (num % 1); //把小數點後的的數字幹掉
} else {
return (num > 0 ? (num – (num % 1) + 1) : //正數
num – (num % 1) – 1); //負數
}
}
原文只講述到這裡,後來我跟基友聊到了這篇文章,我的基友給出了另外一點思路:
因為是四捨五入原理,所以給當前的數字+0.5,得到的值直接丟棄小數點後面的部分轉換為整數(類似parseInt),原來的數字也轉換為整數丟棄小數點後面的部分,這兩個數如果相差<1,表示取原來數字的整數,否則取新數字的整數。
function round(num) {
var value = num > 0 ? num + 0.5 : //正數
– (num – 0.5); //負數
value = value – value % 1; //得到新數的整數部分
//如果相差<1
return value – num < 1 ? num – num % 1 : value;
}
至此,稍微正常點的解決方案介紹完畢,下麵我們來感受下什麼叫做玩程式碼。
另類解決方案
聽到基友的思路我表示非常贊非常好人民需要你程式碼需要你下一個圖靈目測就是你了小夥子要不要買本《頸椎病康復指南》看看決定如何拯救世界?
然後給他感受了一下這個世界森森的惡意——也就是原文評論裡的程式碼。
下麵是欣賞程式碼時間,分析程式碼之類的肯定要放在後面。
//@Gray Zhang的”給跪版”,不支援負數
function round(x) {
return~~ (x + 0.5);
}
//@Gray Zhang的”給跪加深版”,支援正負數
function round(x) {
return~~ (x > 0 ? (x + 0.5) : (x – 0.5));
}
//@強子~Developer的”請收下我的膝蓋版”
function round(x) {
return (x > 0 ? x + 0.5 : x – 0.5) | 0;
}
看到這些程式碼當時我就給跪了,突然有種回家找家影樓給別人撒撒花,揚揚裙擺,送送快遞的想法。好吧,我承認我的位運算就是個渣。
當然,你以為我們的思考僅限於此?no no no,我們覺得用這些什麼if、else、三目運運算元實在太low,於是我和基友想:如果連這些運運算元都給幹掉呢?只通過位運算來實現。
在各種惡補位運算的知識下,我的基友提出了另外一種解決方案:
function round(x) {
return~~ (x + 0.5 + (x >> 30));
}
簡單的分析
覺得上面的程式碼逼格十足?那麼讓我們”粗略”的分析一下吧(詳細計算、補碼之類的知識請拉到參考取用)。
這些程式碼都運用了位運算——我們重點關照下~(按位取反運算)和>>(有符號右偏移運算)。
首先,偷點基礎資料來:
重溫整數
ECMAScript 整數有兩種型別,即有符號整數(允許用正數和負數)和無符號整數(只允許用正數)。在 ECMAScript 中,所有整數字面量預設都是有符號整數。
有符號整數使用 31 位表示整數的數值,用第 32 位表示整數的符號,0 表示正數,1 表示負數。數值範圍從 -2147483648 到 2147483647。見下圖:
因為樣式的原因圖片會存在拉伸,看不清請拿滑鼠拽一下圖片到新的瀏覽器標簽頁即可。
js中toString()方法可以to出二進位制,而parsetInt()方法的第二個引數可以指定轉換進位制:
(18).toString(2) //”10010″
parseInt(10010,2) //18
~的運算過程
~就是按位取反,類似:00111,取反則為11000。
取反會幹掉小數,~運運算元的運算過程可以戳這裡,我們看到呼叫了ToInt32():
所以會被幹掉小數,所以我們可以這麼來實現小數轉整數:
~~18.5 //18 – 等同於parseInt(18)
parseInt(18.5) //18
~~是按位取反再取反,本質上就是一個幹掉小數的過程。
>>有符號右移運運算元
>>是有符號右移運運算元由兩個大於號表示(>>)。它把 32 位數字中的所有數位整體右移,同時保留該數的符號(正號或負號)。有符號右移運運算元恰好與左移運算相反。
我們來解析一下這段程式碼:
-2>>30 // -1 (感謝群裡的@Superior和@Jeff Xiao提供)
過程如下:
- 1 0000000000000000000000000000010 //-2二進位制
- 1 1111111111111111111111111111110 //-2進行補碼
- 1 1111111111111111111111111111111 //向右移動30,高位以符號位(第32位)補全
- 1 0000000000000000000000000000001 //因為符號位為符號,所以是負數,則補碼形式儲存,還原為-1
我們再來看看我的基佬提供的程式碼:
~~(x + 0.5 + (x >> 30))
- 假設X是-12.5
- 首先,-12.5+0.5===-12
- -12.5>>30:上面我們說過,ECMAScript有符號整數使用31位表示整數的數值,所以在ECMAScript中,任何一個數右移30位得到的結果只能是2種:正數得到0,負數得到-1。
- -12-1===-13
由此完成了我們的運算,不得不說這個+0.5和>>30很是精髓(雖然我基佬也是查了半天資料才搞出來 = =)。
再次宣告,這篇文章和程式碼,純屬娛樂。對於上面看的迷迷糊糊,位運算之類的東西還搞不明白的童鞋可以看看下麵的參考。
程式碼總是很有意思的,沒事玩玩程式碼放鬆一下自己也是好的,順便還可以漲姿勢,何樂而不為呢?順便說一下,我和基佬商量著以後要是當了面試官就準備這個問題問一下別人——當然,只是娛樂娛樂。再次感謝群裡的@Superior和@Jeff Xiao為我細心的講解。
最後,向原文和前輩致敬:《JS,如果沒有方法。。。(不借助任何JS方法實現round方法)》。
參考和取用
- JS,如果沒有方法。。。(不借助任何JS方法實現round方法)
- ECMAScript 位運運算元
- 【譯】從一行程式碼來學習JavaScript
- javascript 中 !~ 什麼意思
- 按位與(&)按位或(|)按位異或(^)按位取反(~)左移(<>)
- Javascript小技巧,去掉小數位並且不會四捨五入
- 補碼與求補運算(最基本也最容易忽略的東西)
- MDN – parseInt
來自:linkFly
連結:http://www.cnblogs.com/silin6/p/4367019.html