ScriptCalculator - 不只是程序员的计算器
ScriptCalculator: https://calc.iwill.im/
GitHub Repo: https://github.com/iwill/ScriptCalculator
ScriptCalculator 是我为了满足个人需求开发的一款计算器。
先看效果,假设某同学月薪 10w,用 ScriptCalculator 计算一下他的税后收入(为了便于演示,这里不考虑五险一金、绩效、ESPP、加班费、餐补等):
最初的需求是想保留计算过程、能展示中间结果,能随时修改数据和计算逻辑,计算结果实时更新,必要的地方可以加备注。好像很正常的需求,但找了好久就没发现哪个计算器能满足,所以只能写代码计算。
开始我用 Chrome 控制台写 JavaScript 比较多,随手就能拿过来写,但问题是输入不方便、输出结果也不直观,要在文本编辑器和控制台之间来回拷贝代码和计算结果。Swift Playgrounds 的结果展示很友好,但代码里多少还是夹杂了数据之外的东西,还要要操心数据类型,而且 Xcode 也不是很轻量。气氛烘托到位,就是时候出手了。
ScriptCalculator 基于网页,核心思想是解析输入的计算脚本,通过预处理变成 JavaScript 代码,然后用 eval()
运行代码完成计算,最后模仿 Swift Playgrounds 输出结果。后来边用边迭代,逐步支持了自定义变量和函数、百分数和各种进制、位运算和布尔值、折行和注释,以及通过 URL 分享计算脚本等丰富的特性。
最终,脚本语法简洁、功能强大,充分地利用了 JavaScript 语言的多种特性。(至少我自己)用起来非常顺手,舒坦!
这里有 丰富的功能演示,不喜欢看文档的可以直接操练起来。
特性
类型扩展
百分数:支持输入百分数,支持结果以百分数形式展示
1
2
3
4
5
610% // 0.1
10 % // 0.1
10% + 1 // 0.1 + 1 == 1.1
10% % 1 // 0.1 % 1 == 0.1
10% - 7 // 0.1 - 7 == -6.9
10 % (-7) // 10 % 7 == 3| 需要注意百分号与取余运算符的使用差异
进制:支持输入二、八、十、十六进制数字,支持结果以二至三十六进制形式展示
结果默认以十进制形式展示:
1
2
3
40b10 // 2
0o10 // 8
10 // 10
0x10 // 16末尾加
#<进制数字>
可以切换为展示进制,但并不改变实际结果,支持二至三十六进制:1
2
3
4
5
6
7
8// 这里每一行都是获取上一行的结果、转换进制展示
32
$ // 32
运算
- 基础运算:支持
+
、-
、*
、/
、%
(取余)、(
和)
等运算符 - 比较运算:支持
<
、<=
、>
、>=
、==
和!=
等运算符 - 逻辑运算:支持布尔类型,支持
&&
、||
、!
和三目等运算符?:
- 位运算:支持
&
、|
、~
、^
、<<
、>>
和>>>
等运算符
常量、变量
变量:
- 行首为运算符时在前面自动填入上一行结果
- 使用
$n
获取第n
个结果 - 使用
$
获取上一个结果(不是上一行)
JavaScript 常量、方法:
允许访问 JavaScript 中
Math
和Number
的属性和方法。Math
的属性可以作为常量使用:1
2E // 2.718281828459045
PI // 3.141592653589793Math
的方法可以直接作为函数使用:1
2
3min(max(1, 2), 3) // 2
pow(2, 10) // 1024
sqrt(1024) // 32Number
的属性可以通过Number.
形式访问:1
2
3
4Number.MAX_SAFE_INTEGER // 9007199254740991
== pow(2, 53) - 1 // true
Number.MIN_SAFE_INTEGER // -9007199254740991
== - (pow(2, 53) - 1) // true扩展的函数:
Math
的小数截取方法只能截取整数,想要截取小数会比较麻烦,toFixed()
方法又不够灵活、而且输出的是字符串格式:1
2Math.round(2000 / 3 * 100) / 100 // 666.67 | Math 的 round() 方法
(2000 / 3).toFixed(2) // "666.66" | Number 实例的 toFixed() 方法扩展的函数支持可选的第二个参数就比较方便了,并且还支持整数部分的取舍:
1
2
3
4
5
6
7
8_666: 2000 / 3 // 666.6666666666666
round(_666, 3) // 666.667
floor(_666, 2) // 666.66
ceil(_666 / 2, 1) // 333.4
round(_666, 0) // 667
round(_666) // 667
round(_666, -1) // 670
round(_666, -2) // 700自定义变量
定义及使用:
1
2
3a: 1 // 1
b: 2 // 2
a + b // 3自定义函数
定义及调用:
1
2add(a, b): a + b
add(3, 4) // 7支持更具语义化的 RORO 形式参数:
1
2
3add({ a, b }): \
a + b
add({ a: 4, b: 5 }) // 9
空白、注释
- 支持任意的空格,便于纵向对齐
- 支持任意的空行,空行不影响结果行号
- 行尾加
\
可以折行,特别是自定义复杂的函数时可以避免一行过长 - 支持
#
和//
两种形式的注释
设置、按钮
- decimal:全局结果精确位数
- rounding:全局小数截取方法
- underline:给强迫症删除空格用
- reset:重置设置
- copy:拷贝
注释 + 算式 + 结果
辅助功能
- 行号:有计算结果的行显示行号,可用于
$ + 行号
获取指定行结果 - 撤销、重做:支持默认的撤销、重做功能
- 快捷键:
- control+u 单词或选中文本大写
- control+shift+u 单词或选中文本小写
- control+c 首字母大写
- control+shift+c 首字母小写
- URL Hash:URL Hash 中包含完整计算内容和设置,可前进/后退、刷新不丢数据,拷贝 URL 可以分享或备份,重新打开后 100% 还原
已知问题
暂不支持
BigInt
“行首为运算符时自动在前面填入上一行结果”导致正负号存在歧义
例如:
1
2
3
4
5// exp got
a: 1 // 1 1
b: -1 // -1 0
c: 1 // 1 1
d: +1 // 1 2解决办法是在带正负号的数字外加括号、或在带正负号的数字前换行:
1
2
3
4
5a: 1 // 1
b: (-1) // -1
c: 1 // 1
d: +1 // 1