caculator

10% + 10% 他为什么等于0.11

好久没有更新了,最近比较忙。昨天微博上突然「曝光」(现在这个词都只有读bào光了,高考的时候还是pù光,歪题了)出好多安卓手机的简单计算器在计算10%+10%时居然给出了结果是0.11,不是人们预想的0.2=20%。

现象

先描述现象,简单计算器中,计算某项加或减百分数。xx + n% / xx - n%

1
2
BNF表示:
PERCENT ::= {expr} <+|-> (<num>"%")

此时计算器会将算式解释为 xx 为某个计算结果,+/-n%代表着对当前计算结果进行+/-n%倍的计算。例如昨天产生的争议10% + 10%,被解释为10% + 10% * 10%。事实上前边的表达式为纯数字/计算结果时,也会有同样的现象。例如6 + 10%,计算结果为6 + 6 *10% = 6.6。这种现象其实在Windows计算器的标准模式下也会出现,还有一干的安卓计算器的简单模式。

结论

再说结论,先把结论放上来,有兴趣的可以继续看,不想继续了解的看完结论就明白了。首先,结论是,这不是bug,这是产品特性。因为在这些计算器里就是这样定义百分数计算的。这些计算器将+/-百分数的计算解释为对之前的结果进行加/减比例的计算。

之所以这样做,是因为在设计计算器的算法时,出于一个前提,即人们很少计算百分数的直接加减法,更常见的是诸如在计算税收,利息等等问题时,会对某个结果进行(1 + n%)的计算。比如存款10000元,年利息5%,一年后的存款为10000 + 5%,得到10500。计算器一般都是边解释边进行计算的,所以实时都会出现一个当前结果。也可能早起的计算器屏幕有限,输入方法不够友好,所以不方便输入1 * (1 + 10%)这种结合,所以有这种设计。

而针对乘法/除法,用户的需求就是直接对某个数计算百分比倍数,所以和我们通常认知的结果是一致的。

所以这种现象只是产品设计,不是bug,windows和安卓都存在。安卓软件可能用了相同的开源组件/库或者内置计算器等,而部分手机没有采用这种解决方案。至于看到一些乱分析说自己是学计算机的,这种明显就是浮点数的问题blabla的,笑一笑就ok了。结论很简单,是审计问题,不是bug,是「产品经理」的锅hhh。

分析

我最开始也以为是语义分析一类做错了导致的bug,后来一想,计算器一般都采用的是逆波兰式进行分析计算的,应该不存在二义性,而且简单计算器都是一边计算一边出结果的,显然不会有这样的bug。后来发现微软在Windows中的计算器也是这样设计的,那应该是某种设计问题。刚好微软的计算器开源了,那我们找一下他们是怎样实现的。

Windows计算器中的标准模式中对于百分数计算的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case IDC_PERCENT:
{
// If the operator is multiply/divide, we evaluate this as "X [op] (Y%)"
// Otherwise, we evaluate it as "X [op] (X * Y%)"
if (m_nOpCode == IDC_MUL || m_nOpCode == IDC_DIV)
{
result = rat / 100;
}
else
{
result = rat * (m_lastVal / 100);
}
break;
}

这样逻辑就十分清晰了,注释中也解释了会将加减百分数计算解释为result = pre-result * persent / 100(1 + 10%解释为 1 + 1 * 10 / 100)

至于逆波兰式,有兴趣的话我可以再写一篇解释一下解释过程和计算过程。

参考:

Microsoft github caculator

https://github.com/microsoft/calculator/blob/2826d370565092dfca9a983a5fc6ec0b8b1c62e1/src/CalcManager/CEngine/scifunc.cpp

row 79