前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手机计算器中输入:10%+10% = ?

手机计算器中输入:10%+10% = ?

作者头像
范蠡
发布2019-09-08 09:10:04
1K0
发布2019-09-08 09:10:04
举报

答案不是 0.2,答案是 0.11

为什么会这样呢?这是一个历史遗留问题,属于语法糖,叫做百分计算器

按人类语义的理解,你去买东西,100 元钱减去 10%,那就是 90 元。早期的计算器就可以直接这样写 100 - 10%。再比如,一只股票股价 10 元,增长了 50%,可以直接写 10 + 50%。这么设计更深层次的原因可能与早期计算器的按键数量有限,以及单步运算的性质有关。具体有答主已经作了回答。

手机计算器保留了这种特性

10% + 10% 就是 0.11。

至于部分国内计算器(如魅族)结果是 0.2,是因为国内手机厂商自己做了修改,符合中国人打几折的说法。上述的 10% off其实是外国人的逻辑。

魅族的工程师已经在微博说明他们在国内使用了 0.2 的方案,在国外使用 0.11 的方案。

这里有早期计算器百分键功能的具体说明《**How does the calculator percent key work? **》(https://devblogs.microsoft.com/oldnewthing/20080110-00/?p=23853),作者是大名鼎鼎的 Raymond Chen ,《The Old Things of Windows》的作者 。

百分计算识别条件

exp1 [+-] exp2 % [+-] exp3 = exp1*(1 [+-] exp2 %)[+-] exp3

  • exp1 的值会被优先计算,比如 5 + 5 - 10% =9
  • 如 exp2 与 exp3 之间为 [ * / ] ,则会将 exp2 % [* /] exp3 作为整体计算,比如 5 + 10% * 10 = 6
  • 有关在 exp2% 前后加括号的问题,涉及代码处理,已在最后更新。

要知道计算器如此工作的原因,我们可以直接从源码入手。

源码分析:

我找了一份 Github 上计算器的源码(https://github.com/hoijui/arity)。

微软在今年也开源了 Windows 上的计算器源码:https://github.com/Microsoft/calculator

和大多数计算器的处理方法一致,先将原表达式转化为后缀表达式,利用数字栈和操作符栈,配合指针,从左到右扫描一次就可以得出答案。

代码语言:javascript
复制
Object o = new Object();

TestBigDecimal.XX a1 = new TestBigDecimal.XX();

String str = new String();
str.length();

double s[] = context.stackRe;
int percentPC = -2;
for (int pc = 0; pc < codeLen; ++pc) {
    final int opcode = code[pc];
    switch (opcode) {
        case VM.CONST:
            s[++p] = constsRe[constp++];
            break;

        case VM.ADD: {
            final double a = s[--p];
            double res = a + (percentPC == pc-1 ? s[p] * s[p+1] : s[p+1]);
            if (Math.abs(res) < Math.ulp(a) * 1024) {
                // hack for "1.1-1-.1"
                res = 0;
            }
            s[p]= res
            break;

        case VM.SUB: {
                final double a = s[--p];
                double res = a - (percentPC == pc-1 ? s[p] * s[p+1] : s[p+1]);
                if (Math.abs(res) < Math.ulp(a) * 1024) {
                    // hack for "1.1-1-.1"
                    res = 0;
                }
                s[p] = res;
                break;
            }

        case VM.PERCENT: s[p] = s[p] * .01; 
            percentPC = pc; 
            break;
}

return p;        

我已去除和百分运算无关的部分。

下面对该代码运算过程举个例子:

代码语言:javascript
复制
表达式:a+b%+c
表示成后缀表达式:ab%+c+
Code队列:[ a , b, % , + , c , +]
有个s栈,开始为空:[]
一共三个指针:p、pc、percentPC, 初始值分别为-1,-1,-2。
每次遇到常数,p自增1,再在s中p指向的位置放入该常数。
每次遇到+-,p会自减1。
每次遇到%,令p指向的内容乘以0.01,percentPC=pc。
从左向右开始扫描code,pc为指针,右移一次pc增1。
首先遇到常数a,b,放入s中:[a,b] ,p指向b
继续扫描,遇到%,将p指向的内容*0.01,s变成:[a , b*0.01];同时,percentPC指向code中的%。
继续扫描,遇到+,pc此时指向的位置为percentPC+1,由三元判断式,a=a+a*b*0.01,p重新指向a,s变为[a+a*b*0.01,b*0.01]
继续扫描,c替代b*0.01
继续扫描,遇到+,此时的pc不等于percentPC+1,s[p]=s[0]=a+a*b*0.01+c
结束扫描,返回指针p,s[p]就代表结果,完结。

可以明显看出,加减法中多了一步判断:

代码语言:javascript
复制
double res = a + (percentPC == pc-1 ? s[p] * s[p+1] : s[p+1]);

本质就是查看后缀表达式+-之前的符号是否为%来执行该+-的操作。

如果不需要该特性,只需将这一句改为:

代码语言:javascript
复制
res = a + s[p+1];

另外有网友提出括号的问题,部分计算器的后缀表达式生成时,遇到左括号“(”会将其作为一个标记插入队列。于是,a+(b%)后缀表达式会变成 a b % mark +,加号之前的符号不再是%,不再执行特殊百分比加法。也有计算器加了括号也没有用,这也很好推断,该计算器在生成后缀表达式时没有对括号作插入标记。

计算器的处理过程就是这么简单粗暴,也不涉及什么高深的算法。对于百分运算的特殊处理也只需多一个指针就能做到。所以你能想到了,要适应国内的习惯,只需要加一个地区判断替换语句就可以了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能服务器开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档