整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript基础语法(一)

类有非常多的语言,例如中文、英语、法语等。实际上,计算机也有很多语言,例如C、C++、Java等。简单来说,JavaScript就是众多计算机语言(也叫编程语言)中的一种。跟人类语言类似,计算机语言也有一些共性,例如我们可以将用C语言写的代码转化为JavaScript代码,这就像将英语翻译成中文一样,虽然语言不一样了,但是表达出来的意思是一样的。

当我们把JavaScript学完,再去学另外一门语言(如C、Java等),就会变得非常容易。因为两门计算机语言之间,是有非常多共性的。因此,认真把JavaScript学了,以后再想去学其他编程语言就会变得非常轻松,何乐而不为呢?

我们都知道,学习任何一门人类语言,都得学这门语言的词汇、语法、结构等。同样的,想要学习一门编程语言,也需要学习类似的东西。只不过呢,这些在编程语言中不是叫词汇、语法、结构,而是叫变量、表达式、运算符等。

在本文中我们主要学习JavaScript以下三个基础语法。

  • 常量与变量
  • 数据类型
  • 运算符

常量与变量

变量

在JavaScript中,每一条语句都是以英文分号(;)作为结束符。每一条语句都有它特定的功能,跟英语每一句话都有它表达的意思是一样的道理。在JavaScript中,变量与常量就像是英语中的词汇。

使用变量,首先得进行命名;一般名字不会改变但是会改变值。

在JavaScript中,给一个变量命名,我们需要遵循以下两个方面:

  • 变量由字母、下划线、$或数字组成,并且第一个字母必须是“字母、下划线或$”
  • 变量不能是系统关键字和保留字

在JavaScript中,如果想要使用一个变量,我们一般需要进行两步:

  • 变量的声明
  • 变量的赋值

所有JavaScript变量都是由var声明。在这一点上,JavaScript跟C、Java这些是不同的。

常量

在JavaScript中,常量指的是一个不能改变的量。也就是说,常量的值从定义开始就是固定的,一直到程序结束都不会改变。

常量,形象地说,就像千百年来约定俗成的名称,这个名称是定下来的,不能随便改变。在JavaScript中,我们可以把常量看成是一种特殊的变量,之所以特殊,是因为它的值是不会变的。一般情况下,常量名全部大写,别人一看就知道这个值很特殊,有特殊用途,如:

var DEBUG = 1;

我们都知道,程序是会变化的,因此变量比常量有用得多。常量在JavaScript中用得比较少。我们简单了解常量是这么一回事就行了,不需要做过多的深入了解。

数据类型

数据类型可以分为两种,一种是“基本数据类型”,另外一种是“引用数据类型”。其中,基本数据类型只有一个值,而引用数据类型可以含有多个值。

在JavaScript中,基本数据类型有五种:数字、字符串、布尔值、未定义值和空值。而常见的引用数据类型有两种:数组、对象。这一节,我们先来介绍基本数据类型。后面章节会逐渐介绍“数组”和“对象”这两种引用数据类型。

运算符

在JavaScript中,要完成各种各样的运算,是离不开运算符的。运算符用于将一个或几个值进行运算从而得出所需要的结果值。就像我们数学上,也需要加减乘除这些运算符才可以运算。不过对于JavaScript来说,我们需要遵循计算机语言运算的一套方法。

在JavaScript中,运算符指的是“变量”或“值”进行运算操作的符号。在JavaScript中,常见的运算符有五种:

算术运算符

赋值运算符

比较运算符

逻辑运算符

条件运算符

cJinja 是一个使用cpp编写的轻量html模版解析库,依赖 ejson 来实现模版的数据替换(在jinja中称为context,上下文)。模版的语法基本与django jinja一致,功能还算丰富。源码仅有700行,适合学习,觉得不错的点个star吧。

(该程序为 https://github.com/HuangHongkai/tinyserver 中的一个模块)

编译

使用cmake来编译,windows和linux下均可编译。推荐使用clion作为IDE。

编译成功后在build目录下会有libcjinja.a和cjinja_test.exe这2个文件。libcjinja.a是静态库,cjinja_test.exe是一个简单的测试程序。

运行测试程序后会出现output.html(该文件是tmp.html解析后的结果。)

已经完成的功能

  • 变量,例如 {{ var }}
  • 变量索引访问,例如 {{ var.var2 }} {{ var[2] }} {{ var[2].key.value[2] }},其中**[]** 表示对数组(类似python的list)进行索引, . 表示对object进行索引(类似与python的dict)
  • 表达式计算(包括字符串拼接) ,例如{{ 1*1+2-3*var }} {{ 1+1*2-3/4 }} {{ "asdfsf"+var }}
  • for-endfor对列表进行迭代, 例如 {% for var in list %} {% endfor %}
  • for-endfor对对象进行迭代,例如 {% for key,value in object %} {% endfor %} 或者 {% for key in object %}{% endfor %} 或者 {% for ,value in object %} {% endfor %}
  • if-else-endif 语句, 其中if的条件支持四则运算,简单的比较(!= == )等,例如 {% if 1+1==2 %}aaa{% else %}bbb{%endif %}
  • 模版包含,嵌套其他的模版文件{% include 'other.html' %}
  • 模版语法错误提示

需要注意,表达式之间不能含有空格,例如{{ 1 + 1 }}是非法的,而{{ 1+1 }}是合法的。

使用方法

1. 变量和变量索引

简单的例子如下,

HtmlTemplate html("username:{{ username }}\n"
 "parm.list[1][2]: {{parm.list[1][2] }} \n"
 "parm.key: {{ parm.key }}",
 1); // 参数1表示传入的是模版字符串,0表示传入的是文件名,默认为0
JSONObject obj = {
 {"username", 1234},
 {"parm", {
 {"key", "cde"},
 {"list", {1, {1,2.3, "abcd"}, "hahaha"}},
 }}
};
html.setValue(obj);
cout << html.render() << endl << endl;
/* 运行后打印如下
username:1234
parm.list[1]: abcd 
parm.key: cde
*/

HtmlTemplate是一个库的主要类,构造函数为

explicit HtmlTemplate(const string& str, int flag = 0); // flag=0是str表示文件路径,不为0是表示传入的模版字符串

其中str参数为字符串,可以表示html模板原始串,也可也表示为文件的路径,flag默认为0。

setValue 方法表示传入数据给模版对象。

render() 方法表示将模版解析成字符串。

JSONObject来源于 ejson 库,用来模拟python的dict,构造函数也比较容易看懂。

2. 列表迭代

HtmlTemplate html("{% for x in list %}{{ x }}\n{%endfor%}"
 "此时x已经是临时变量了,不可以在打印了 {{x}}\n"
 , 1);
JSONObject obj = OBJECT(
 KEYVALUE("list", LIST(1,2,3,4,5))
);
cout << html.setValue(obj).render() << endl << endl;
/*运行后输出如下
1
2
3
4
5
此时x已经是临时变量了,不可以在打印了 
*/

注意到在迭代过程中x是作为临时变量,在外部的话是无法打印出来的。

3. 字典迭代

HtmlTemplate html("{% for key in dict %}迭代1: 字典的key值为 {{ key }}\n{% endfor %}"
 "{% for key,value in dict %}迭代2: 字典的key值为 {{ key }}, value值为 {{ value}}\n{% endfor %}"
 "{% for ,value in dict %}迭代3: 字典的value值为 {{ value }}\n{% endfor %}", 1);
JSONObject obj = OBJECT(
 KEYVALUE("dict", OBJECT(
 KEYVALUE("key1", "value1"),
 KEYVALUE("key2", 1234),
 KEYVALUE("key3", nullptr),
 ))
);
cout << html.setValue(obj).render() << endl << endl;
/*运行后输出
迭代1: 字典的key值为 key1
迭代1: 字典的key值为 key2
迭代1: 字典的key值为 key3
迭代2: 字典的key值为 key1, value值为 value1
迭代2: 字典的key值为 key2, value值为 1234
迭代2: 字典的key值为 key3, value值为 null
迭代3: 字典的value值为 value1
迭代3: 字典的value值为 1234
迭代3: 字典的value值为 null
*/

4. 字符串拼接与表达式计算

HtmlTemplate html("{{ a+b+c+\"444\" }}\n"
 "{{x}} * {{y}} + 2 * 3 - 4 / {{x}} = {{ x*y+2*3-4/x }}\n",
 1);
JSONObject obj = OBJECT(
 KEYVALUE("a", "111"),
 KEYVALUE("b", "222"),
 KEYVALUE("c", "333"),
 KEYVALUE("x", 12),
 KEYVALUE("y", 34)
 );
cout << html.setValue(obj).render() << endl << endl;
/*运行后输出
111222333444
12 * 34 + 2 * 3 - 4 / 12 = 413.667
*/

5. if-else-endif语句

HtmlTemplate html("{% if 1==1 %} 1==1 成立 {% else %} 1==1不成立 {%endif %}\n"
 "{% if !x %} x为空 {% else %} x不为空 {%endif %}\n"
 "{% if x==2 %} x==2 成立 {% endif %}\n"
 "{% if x+1!=2 %} x+1!=2 成立 {% endif %}\n"
 "{% if x<3 %} x<3 成立 {% endif %}\n"
 "{% if x>1 %} x>1 成立 {% endif %}\n"
 "{% if str==\"abcd\" %} str为abcd {% endif %}\n"
 "{% if 1 %} 常量表达式1 {% endif %}\n"
 "{% if 0 %} 常量表达式0,此处不会输出 {%endif%}", 1);
JSONObject obj = {
 {"x", 2},
 {"str", "abcd"}
};
cout << html.setValue(obj).render() << endl;
/*运行后输出
 1==1 成立 
 x不为空 
 x==2 成立 
 x+1!=2 成立 
 x<3 成立 
 x>1 成立 
 str为abcd 
 常量表达式1 
*/

6.for与if嵌套使用

 HtmlTemplate html("{%for x in list%}"
 "{%if x %}"
 "{% for y in list2%}"
 "{{x}} * {{y}} = {{ x*y }}\n"
 "{% endfor %}"
 "{% else %}"
 "x的值为空\n"
 "{%endif%}"
 "{% endfor%}", 1);
JSONObject obj = OBJECT(
 KEYVALUE("list", LIST(1,2,3,4,5)),
 KEYVALUE("list2", LIST(1,2,3)),
);
cout << html.setValue(obj).render() << endl << endl;
/*运行后输出
1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
3 * 1 = 3
3 * 2 = 6
3 * 3 = 9
4 * 1 = 4
4 * 2 = 8
4 * 3 = 12
5 * 1 = 5
5 * 2 = 10
5 * 3 = 15
*/

7.模版文件作为输出

HtmlTemplate html("tmpl.html");
JSONObject context = OBJECT(
 ...
);
FILE* f = fopen("output.html", "w"); // 写入到文件中
string&& str = html.setValue(context).render();
fwrite(str.c_str(), 1, str.size(), f);
fclose(f);
/*运行后,代开当前目录的tmpl.html文件作为输入,输出文件为output.html*/
/*如果tmpl.html不存在则抛出异常*/

8. 异常处理

HtmlTemplate html("{% if 1 %} xxx ", 1);
// 不传入context
try {
 cout << html.render() << endl;
} catch(exception& e) {
 cerr << e.what() << endl;
}
cout << endl;

运行后终端上打印如下,

会提示异常的类名,异常文件所在位置,代码行数,以及一些错误的信息。

讨论

1. 实现一个简单的表达式计算器用什么方法比较好?(例如 {{ 2.3*3+4/5*x }} 这类表达式)

我分享一下我自己的方法,有什么更好的方法一起讨论一下。

  • 第一步,先把数据和符号提取出来放入到数组中,输入类型全部设为double。例如上面那个表达式,符号提取出来是{*, /, *}, 数据提取出来是{2.3, 3, 4, 5, x}
  • 这一步位于__parse_var这个函数,比较简单不详细讨论。
  • 第二步,先计算乘除法,结果放入栈中,在对栈中元素计算加减法(按照我们平常计算表达式的思路先乘除后加减),这一步实现如下(其中运用到C语言的宏和C++11的匿名函数)
double cJinja::HtmlTemplate::calculator(vector<any>& number, vector<char>& op) {
 // 例如下表达式会成为
 // 1 - 2 - 3 + 2 *3 * 4 - 4*5
 // vector<char> op = { '-', '-', '+', '*', '*', '-', '*' };
 // vector<any> number = { 1, 2, 3, 2, 3, 4, 4, 5 };
 if (number.size() != op.size() + 1)
 throwException(TemplateParseException, "运算符号数和操作数不匹配");
 /* 定义计算器的内部函数 */
 auto calc = [](any& var1, double var2, char op) -> double{
 // var2 + var1
 // var2 * var1
 // var2 - var1
 // var2 / var1
 // 注意顺序
#define CALC(op2) \
 if(#op2[0] == op) { \
 if (var1.type() == typeid(int)) \
 return var2 op2 static_cast<double>(any_cast<int>(var1)); \
 else if (var1.type() == typeid(float)) \
 return var2 op2 static_cast<double>(any_cast<float>(var1)) ; \
 else if (var1.type() == typeid(double)) \
 return var2 op2 static_cast<double>(any_cast<double>(var1)) ; \
 }
 CALC(+);
 CALC(-);
 CALC(*);
 CALC(/);
 throwException(TemplateParseException, "不允许对空指针进行运算");
#undef CALC
 };
 vector<double> num_stack; // 计算中间结果存储栈
 num_stack.push_back(calc(number[0], 0, '+')); // 获取值 number[i+1] + 0 (加法运算零元为0,乘法运算零元为1)
 /* 计算 * / 法*/
 for (size_t i = 0; i < op.size(); i++) {
 if (op[i] == '+' || op[i] == '-') {
 num_stack.push_back(calc(number[i + 1], 0, '+')); // number[i+1] + 0
 }
 else if (op[i] == '*' || op[i] == '/') {
 double var1 = num_stack.back(); num_stack.pop_back();
 num_stack.push_back(calc(number[i + 1], var1, op[i])); // var1/number[i+1] 或者是 var1/number[i+1]
 } else
 throwException(TemplateParseException, str_format("非法操作符 %d", op[i]));
 }
 /* 计算 + - 法*/
 double result = num_stack[0];
 size_t i = 1;
 for (auto& ch : op) {
 if (ch == '+') {
 result += num_stack[i++];
 } else if(ch == '-') {
 result -= num_stack[i++];
 }
 }
 return result;
}

2. 抛出异常包含更多的信息

我定义了一个throwException宏,如下

#define throwException(Exception, ...) { \
 std::cerr << "[" << #Exception << "] : FILE: " << string(__FILE__).substr(string(__FILE__).find_last_of('/') + 1) << " LINE: " << __LINE__ << " FUNCTION: " <<__FUNCTION__ << std::endl; \
 throw Exception(__VA_ARGS__); \
 }

其中__FILE__ 为文件名,__LINE__ 为当前代码行数,这些都是C中的内置宏,__VA_ARGS__是可变参数,对应于宏函数参数中的....

日常工作计算中,我们如履薄冰,但是 JavaScript 总能给我们这样那样的 surprise~

  1. 0.1 + 0.2 = ?
  2. 1 - 0.9 = ?

如果小伙伴给出内心的结果:

  1. 0.1 + 0.2 = 0.3
  2. 1 - 0.9 = 0.1

那么小伙伴会被事实狠狠地扇脸:

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(1 - 0.9); // 0.09999999999999998

为什么会出现这种情况呢?咱们一探究竟!

三 问题复现

返回目录

下面,我们会通过探讨 IEEE 754 标准,以及 JavaScript 加减的计算过程,来复现问题。

3.1 根源:IEEE 754 标准

返回目录

JavaScript 里面的数字采用 IEEE 754 标准的 64 位双精度浮点数。该规范定义了浮点数的格式,对于 64 位的浮点数在内存中表示,最高的 1 位是符号为,接着的 11 位是指数,剩下的 52 位为有效数字,具体:

  • 第 0 位:符号位。用 s 表示,0 表示为正数,1 表示为负数;
  • 第 1 - 11 位:存储指数部分。用 e 表示;
  • 第 12 - 63 位:存储小数部分(即有效数字)。用 f 表示。



符号位决定一个数的正负,指数部分决定数值的大小,小数部分决定数值的精度。

IEEE 754 规定,有效数字第一位默认总是 1,不保存在 64 位浮点数之中。

也就是说,有效数字总是 1.XX......XX的形式,其中 XX......XX 的部分保存在 64 位浮点数之中,最长可能为 52 位。

因此,JavaScript 提供的有效数字最长为 53 个二进制位(64 位浮点的后 52 位 + 有效数字第一位的 1)。

3.2 复现:计算过程

返回目录

通过 JavaScript 计算 0.1 + 0.2 时,会发生什么?

1、 将 0.1 和 0.2 换成二进制表示:

0.1 -> 0.0001100110011001...(无限)
0.2 -> 0.0011001100110011...(无限

浮点数用二进制表达式是无穷的

我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。

2、 因为 IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持 53 位二进制位,所以两者相加之后得到二进制为:

0.0100110011001100110011001100110011001100110011001100

因为浮点数小数位的限制,这个二进制数字被截断了,用这个二进制数转换成十进制,就成了 0.30000000000000004,从而在进行算数计算时产生误差。

3.3 扩展:数字安全

返回目录

在看完上面小数的计算不精确后,jsliang 觉得有必要再聊聊整数,因为整数同样存在一些问题:

console.log(19571992547450991);
// 19571992547450990

console.log(19571992547450991 === 19571992547450994);
// true

是不是很惊奇!

因为 JavaScript 中 Number 类型统一按浮点数处理,整数也不能逃避这个问题:

// 最大值
const MaxNumber = Math.pow(2, 53) - 1;
console.log(MaxNumber); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991

// 最小值
const MinNumber = -(Math.pow(2, 53) - 1);
console.log(MinNumber); // -9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991

即整数的安全范围是: [-9007199254740991, 9007199254740991]。

超过这个范围的,就存在被舍去的精度问题。

当然,这个问题并不仅仅存在于 JavaScript 中,几乎所有采用了 IEEE-745 标准的编程语言,都会有这个问题,只不过在很多其他语言中已经封装好了方法来避免精度的问题。

  • PHP Float 浮点型 - Manual
  • Java 您的小数点到哪里去了? - Brian Goetz

而因为 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。

到此为止,我们可以看到 JavaScript 在处理数字类型的操作时,可能会产生一些问题。

事实上,工作中还真会有问题!

某天我处理了一个工作表格的计算,然后第二天被告知线上有问题,之后被产品小姐姐问话:

  • 为什么小学生都能做出的小数计算,你们计算机算不了呢?

默哀三秒,产生上面的找到探索,最终找到下面的解决方案。

四 解决问题

返回目录

下面尝试通过各种方式来解决浮点数计算的问题。

4.1 toFixed()

返回目录

toFixed() 方法使用定点表示法来格式化一个数值。

  • 《toFixed - MDN》

语法:numObj.toFixed(digits)

参数:digits。小数点后数字的个数;介于 0 到 20(包括)之间,实现环境可能支持更大范围。如果忽略该参数,则默认为 0。

const num = 12345.6789;

num.toFixed(); // '12346':进行四舍五入,不包括小数部分。
num.toFixed(1); // '12345.7':进行四舍五入,保留小数点后 1 个数字。
num.toFixed(6); // '12345.678900':保留小数点后 6 个数字,长度不足时用 0 填充。
(1.23e+20).toFixed(2); // 123000000000000000000.00 科学计数法变成正常数字类型

toFixed() 得出的结果是 String 类型,记得转换 Number 类型。

toFixed() 方法使用定点表示法来格式化一个数,会对结果进行四舍五入。

通过 toFixed() 我们可以解决一些问题:

原加减乘数:

console.log(1.0 - 0.9);
// 0.09999999999999998

console.log(0.3 / 0.1);
// 2.9999999999999996

console.log(9.7 * 100);
// 969.9999999999999

console.log(2.22 + 0.1);
// 2.3200000000000003

使用 toFixed():

// 公式:parseFloat((数学表达式).toFixed(digits));
// toFixed() 精度参数须在 0 与20 之间

parseFloat((1.0 - 0.9).toFixed(10));
// 0.1 

parseFloat((0.3 / 0.1).toFixed(10));
// 3 

parseFloat((9.7 * 100).toFixed(10));
// 970

parseFloat((2.22 + 0.1).toFixed(10));
// 2.32

那么,讲到这里,问题来了:

  • parseFloat(1.005.toFixed(2))

会得到什么呢,你的反应是不是 1.01 ?

然而并不是,结果是:1。

这么说的话,enm...摔!o(╥﹏╥)o

toFixed() 被证明了也不是最保险的解决方式。

4.2 手写简易加减乘除

返回目录

既然 JavaScript 自带的方法不能自救,那么我们只能换个思路:

  • 将 JavaScript 的小数部分转成字符串进行计算
/**
 * @name 检测数据是否超限
 * @param {Number} number 
 */
const checkSafeNumber = (number) => {
 if (number > Number.MAX_SAFE_INTEGER || number < Number.MIN_SAFE_INTEGER) {
 console.log(`数字 ${number} 超限,请注意风险!`);
 }
};

/**
 * @name 修正数据
 * @param {Number} number 需要修正的数字
 * @param {Number} precision 端正的位数
 */
const revise = (number, precision = 12) => {
 return +parseFloat(number.toPrecision(precision));
}

/**
 * @name 获取小数点后面的长度
 * @param {Number} 需要转换的数字
 */
const digitLength = (number) => {
 return (number.toString().split('.')[1] || '').length;
};

/**
 * @name 将数字的小数点去掉
 * @param {Number} 需要转换的数字
 */
const floatToInt = (number) => {
 return Number(number.toString().replace('.', ''));
};

/**
 * @name 精度计算乘法
 * @param {Number} arg1 乘数 1
 * @param {Number} arg2 乘数 2
 */
const multiplication = (arg1, arg2) => {
 const baseNum = digitLength(arg1) + digitLength(arg2);
 const result = floatToInt(arg1) * floatToInt(arg2);
 checkSafeNumber(result);
 return result / Math.pow(10, baseNum);
 // 整数安全范围内的两个整数进行除法是没问题的
 // 如果有,证明给我看
};

console.log('------\n乘法:');
console.log(9.7 * 100); // 969.9999999999999
console.log(multiplication(9.7, 100)); // 970

console.log(0.01 * 0.07); // 0.0007000000000000001
console.log(multiplication(0.01, 0.07)); // 0.0007

console.log(1207.41 * 100); // 120741.00000000001
console.log(multiplication(1207.41, 100)); // 0.0007

/**
 * @name 精度计算加法
 * @description JavaScript 的加法结果存在误差,两个浮点数 0.1 + 0.2 !== 0.3,使用这方法能去除误差。
 * @param {Number} arg1 加数 1
 * @param {Number} arg2 加数 2
 * @return arg1 + arg2
 */
const add = (arg1, arg2) => {
 const baseNum = Math.pow(10, Math.max(digitLength(arg1), digitLength(arg2)));
 return (multiplication(arg1, baseNum) + multiplication(arg2, baseNum)) / baseNum;
}

console.log('------\n加法:');
console.log(1.001 + 0.003); // 1.0039999999999998
console.log(add(1.001, 0.003)); // 1.004

console.log(3.001 + 0.07); // 3.0709999999999997
console.log(add(3.001, 0.07)); // 3.071

/**
 * @name 精度计算减法
 * @param {Number} arg1 减数 1
 * @param {Number} arg2 减数 2
 */
const subtraction = (arg1, arg2) => {
 const baseNum = Math.pow(10, Math.max(digitLength(arg1), digitLength(arg2)));
 return (multiplication(arg1, baseNum) - multiplication(arg2, baseNum)) / baseNum;
};

console.log('------\n减法:');
console.log(0.3 - 0.1); // 0.19999999999999998
console.log(subtraction(0.3, 0.1)); // 0.2

/**
 * @name 精度计算除法
 * @param {Number} arg1 除数 1
 * @param {Number} arg2 除数 2
 */
const division = (arg1, arg2) => {
 const baseNum = Math.pow(10, Math.max(digitLength(arg1), digitLength(arg2)));
 return multiplication(arg1, baseNum) / multiplication(arg2, baseNum);
};

console.log('------\n除法:');
console.log(0.3 / 0.1); // 2.9999999999999996
console.log(division(0.3, 0.1)); // 3

console.log(1.21 / 1.1); // 1.0999999999999999
console.log(division(1.21, 1.1)); // 1.1

console.log(1.02 / 1.1); // 0.9272727272727272
console.log(division(1.02, 1.1)); // 数字 9272727272727272 超限,请注意风险!0.9272727272727272

console.log(1207.41 / 100); // 12.074100000000001
console.log(division(1207.41, 100)); // 12.0741

/**
 * @name 按指定位数四舍五入
 * @param {Number} number 需要取舍的数字
 * @param {Number} ratio 精确到多少位小数
 */
const round = (number, ratio) => {
 const baseNum = Math.pow(10, ratio);
 return division(Math.round(multiplication(number, baseNum)), baseNum);
 // Math.round() 进行小数点后一位四舍五入是否有问题,如果有,请证明出来
 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/round
}

console.log('------\n四舍五入:');
console.log(0.105.toFixed(2)); // '0.10'
console.log(round(0.105, 2)); // 0.11

console.log(1.335.toFixed(2)); // '1.33'
console.log(round(1.335, 2)); // 1.34

console.log(-round(2.5, 0)); // -3
console.log(-round(20.51, 0)); // -21

在这份代码中,我们先通过石锤乘法的计算,通过将数字转成整数进行计算,从而产生了 安全 的数据。

JavaScript 整数运算会不会出问题呢?

乘法计算好后,假设乘法已经没问题,然后通过乘法推出 加法、减法 以及 除法 这三则运算。

最后,通过乘法和除法做出四舍五入的规则。

JavaScript Math.round() 产生的数字会不会有问题呢、

这样,我们就搞定了两个数的加减乘除和四舍五入(保留指定的长度),那么,里面会不会有问题呢?

如果有,请例举出来。

如果没有,那么你能不能依据上面两个数的加减乘除,实现三个数甚至多个数的加减乘除?

五 现成框架

返回目录

这么重要的计算,如果自己写的话你总会感觉惶惶不安,感觉充满着危机。

所以很多时候,我们可以使用大佬们写好的 JavaScript 计算库,因为这些问题大佬已经帮我们进行了大量的测试了,大大减少了我们手写存在的问题,所以我们可以调用别人写好的类库。

下面推荐几款不错的类库:

  • Math.js。

Math.js 是一个用于 JavaScript 和 Node.js 的扩展数学库。

它具有支持符号计算的灵活表达式解析器,大量内置函数和常量,并提供了集成的解决方案来处理不同的数据类型,例如数字,大数,复数,分数,单位和矩阵。

强大且易于使用。

  • decimal.js

JavaScript 的任意精度的十进制类型。

  • big.js

一个小型,快速,易于使用的库,用于任意精度的十进制算术运算。

  • bignumber.js

一个用于任意精度算术的 JavaScript 库。

最后的最后,值得一提的是:如果对数字的计算非常严格,或许你可以将参数丢给后端,让后端进行计算,再返回给你结果。

例如涉及到比特币、商城商品价格等的计算~


作者:jsliang
链接:https://juejin.im/post/5ddc7fa66fb9a07ad665b1f0