这篇文章源于我上一周所读的一篇12年的文章。原作者提出了一个问题,如果js没有原生方法Math.round(),我们如何去实现呢?

对此我和我的基友进行了小小探讨,并给出了一些有意思的答案。

> 本文内容如下:
> - 如果...没有方法
> - 解决方案
> - 另类解决方案
> - 简单的分析
> - 参考和引用

JavaScript - 前端开发交流群:377786580

如果...没有方法

这篇文章源于上周所读的一篇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,我们觉得用这些什么ifelse三目运算符实在太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方法)》

JavaScript - 前端开发交流群:377786580

参考和引用

JS,如果没有方法。。。(不借助任何JS方法实现round方法)

ECMAScript 位运算符

【译】从一行代码来学习JavaScript

javascript 中 !~ 什么意思

按位与(&)按位或(|)按位异或(^)按位取反(~)左移(<<)右移(>>)

Javascript小技巧,去掉小数位并且不会四舍五入

补码与求补运算(最基本也最容易忽略的东西)

MDN - parseInt

作者:linkFly
声明:嘿!你都拷走上面那么一大段了,我觉得你应该也不介意顺便拷走这一小段,希望你能够在每一次的引用中都保留这一段声明,尊重作者的辛勤劳动成果,本文与博客园共享。

JavaScript - 如果...没有方法的更多相关文章

  1. Javascript对象的方法赋值

    Javascript对象编程学习中,一直不能很好的掌握对象的属性(property)和方法(method).今天在写代码过程中,又犯了一个低级错误. <!DOCTYPE html> < ...

  2. JavaScript中reduce()方法

    原文  http://aotu.io/notes/2016/04/15/2016-04-14-js-reduce/   JavaScript中reduce()方法不完全指南 reduce() 方法接收 ...

  3. 两种动态加载JavaScript文件的方法

    两种动态加载JavaScript文件的方法 第一种便是利用ajax方式,第二种是,动静创建一个script标签,配置其src属性,经过把script标签拔出到页面head来加载js,感乐趣的网友可以看 ...

  4. 用jQuery之后,之前javascript的一些方法就不能用了吗

    用jQuery之后,之前javascript的一些方法就不能用了吗? 比如$("#btn").onclick = function(){}这种用法?或者$("#btn&q ...

  5. html,JavaScript调用winfrom方法

    ---恢复内容开始--- 目的: 在动画上面添加点击事件,通过JavaScript调用winfrom方法 1.创建一个页面 using System; using System.Collections ...

  6. delegate实现Javascript的each方法

    C#如何用delegate实现Javascript的each方法   C#中有很多易混淆的关键词,例如delegate,Func, Action和 Predicate.Func, Action和 Pr ...

  7. 在C#中模拟Javascript的setTimeout方法

    在C#中模拟Javascript的setTimeout方法 背景 每种语言都有自己的定时器(Timer),很多人熟悉Javascript中的setInterval和setTimeout,在Javasc ...

  8. JavaScript创建对象的方法

    显示在浏览器中的控制台中. <script type="text/javascript"> //这个工厂方法返回一个新的"范围对象" functio ...

  9. Javascript 面向对象(共有方法,私有方法,特权方法,静态属性和方法,静态类)示例讲解

    一,私有属性和方法 私有方法:私有方法本身是可以访问类内部的所有属性(即私有属性和公有属性),但是私有方法是不可以在类的外部被调用. <script> /* * 私有方法:私有方法本身是可 ...

  10. JavaScript中的方法、方法引用和参数

    首先,我们来看一段代码,如果觉得不甚明白的,则本文会对你有益: var player = function (e) {             return (function f(m) {      ...

随机推荐

  1. 使用Ubuntu 12.04作为日常电脑环境

    搜狗输入法出来之后,我觉得有必要写一篇博客说明一下,如何使用Ubuntu作为日常的电脑系统.我使用的Ubuntu版本是12.04,没有使用Ubuntukylin,因为的电脑比较老,使用那个版本,电脑有 ...

  2. CSS3中border-radius、box-shadow与gradient那点事儿

    一.border-radius border-radius用于添加圆角边框,用处非常广泛. 1)一个值,代表了四个角 .radius-one { /* Safari 3-4, iOS 1-3.2, A ...

  3. 记录一则ORA-12154,ORA-12560解决过程

    应用服务器:Windows Server 2008 R2 Enterprise 故障现象:项目侧同事反映应用服务器上的程序连接数据库报错:ORA-12560: TNS: 协议适配器错误 1.故障重现 ...

  4. ECMAScript 6 开篇准备

    1前言 该系列文章均为学习阮一峰老师<ECMAScript 6 入门>一书的学习笔记.原著:http://es6.ruanyifeng.com/ 各大浏览器的最新版本,对ES6的支持可以查 ...

  5. 浅析JS中的模块规范(CommonJS,AMD,CMD)

    如果你听过js模块化这个东西,那么你就应该听过或CommonJS或AMD甚至是CMD这些规范咯,我也听过,但之前也真的是听听而已. 现在就看看吧,这些规范到底是啥东西,干嘛的. 一.CommonJS ...

  6. Eclipse与Android源码中ProGuard工具的使用

    由于工作需要,这两天和同事在研究android下面的ProGuard工具的使用,通过查看android官网对该工具的介绍以及网络上其它相关资料,再加上自己的亲手实践,算是有了一个基本了解.下面将自己的 ...

  7. JDBC连接MySQL数据库代码模板

    下面这个例子是最简单的JDBC连接MySQL数据库的例子. 一般步骤: 1.注册驱动: 2.建立连接: 3.创建语句: 4.处理结果: 5.释放资源. 注意: 1.软件开发环境:MyEclipse 8 ...

  8. 【Debug】Web开发中,Tomcat正常启动,访问欢迎页404,怎么办?

    访问页面出现404是一个会经常遇到的问题.每次开发Web项目时总要掉这个坑里几次,而且还不长记性.今天来总结一下,开发时遇到这个问题的解决思路. 1. 查看访问地址是否正确,有无拼写错误. 越是低级的 ...

  9. GUI 和 GUILayout 的区别

    GUI 和 GUILayout 的区别 A~ GUI是Unity中的基础控件类,其中包含了常用的GUI控件,列如Button,Label,PasswordField,slider,Window等等~ ...

  10. 【菜鸟玩Linux开发】通过MySQL自动同步刷新Redis

    在服务端开发过程中,一般会使用MySQL等关系型数据库作为最终的存储引擎,Redis其实也可以作为一种键值对型的数据库,但在一些实际场景中,特别是关系型结构并不适合使用Redis直接作为数据库.这俩家 ...