JavaScript - 若是...沒有方法

這篇文章源於我上一週所讀的一篇12年的文章。原做者提出了一個問題,若是js沒有原生方法Math.round(),咱們如何去實現呢? javascript

對此我和個人基友進行了小小探討,並給出了一些有意思的答案。html

本文內容以下:前端

  • 若是...沒有方法
  • 解決方案
  • 另類解決方案
  • 簡單的分析
  • 參考和引用

JavaScript - 前端開發交流羣:377786580java

若是...沒有方法

這篇文章源於上週所讀的一篇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的值,代碼以下:.net

//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()都不使用該怎麼去作呢?
因而提出了第二種解決方案:prototype

這個問題的關鍵在於斷定小數點後的數字是否大於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,咱們以爲用這些什麼ifelse三目運算符實在太low,因而我和基友想:若是連這些運算符都給幹掉呢?只經過位運算來實現。
在各類惡補位運算的知識下,個人基友提出了另一種解決方案:

function round(x) {
            return ~~(x + 0.5 + (x >> 30));
        }

簡單的分析

以爲上面的代碼逼格十足?那麼讓咱們"粗略"的分析一下吧(詳細計算、補碼之類的知識請拉到參考引用)。
這些代碼都運用了位運算——咱們重點關照下~(按位取反運算)>>(有符號右偏移運算)

首先,偷點基礎資料來:

重溫整數

ECMAScript 整數有兩種類型,即有符號整數(容許用正數和負數)和無符號整數(只容許用正數)。在 ECMAScript 中,全部整數字面量默認都是有符號整數。
有符號整數使用 31 位表示整數的數值,用第 32 位表示整數的符號,0 表示正數,1 表示負數。數值範圍從 -2147483648 到 2147483647。見下圖:

由於樣式的緣由圖片會存在拉伸,看不清請拿鼠標拽一下圖片到新的瀏覽器標籤頁便可。

ct_js_integer_binary_signed_32bits

js中toString()方法能夠to出二進制,而parsetInt()方法的第二個參數能夠指定轉換進制:

(18).toString(2) //"10010"
        parseInt(10010,2) //18

  

~的運算過程

~就是按位取反,相似:00111,取反則爲11000
取反會幹掉小數,~運算符的運算過程能夠戳這裏,咱們看到調用了ToInt32()

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方法)》

JavaScript - 前端開發交流羣:377786580

參考和引用

JS,若是沒有方法。。。(不借助任何JS方法實現round方法)
ECMAScript 位運算符
【譯】從一行代碼來學習JavaScript
javascript 中 !~ 什麼意思
按位與(&)按位或(|)按位異或(^)按位取反(~)左移(<<)右移(>>)
Javascript小技巧,去掉小數位而且不會四捨五入
補碼與求補運算(最基本也最容易忽略的東西)
MDN - parseInt

做者:linkFly
聲明:嘿!你都拷走上面那麼一大段了,我以爲你應該也不介意順便拷走這一小段,但願你可以在每一次的引用中都保留這一段聲明,尊重做者的辛勤勞動成果,本文與博客園共享。
相關文章
相關標籤/搜索