来源:公众号【编程珠玑】
作者:守望先生
网站:https://www.yanbinghu.com
前言
近日,关于手机计算器10% 10%=0.11的事情火热,多个品牌的手机未能幸免,基本“阵亡”,同时还包括了windows10的自带标准计算器。你的手机阵亡了吗?
10%按理应该等于0.2,为什么会出现这样的情况?
那么这是计算器的BUG?还是另有隐情?是程序员的不负责任,还是另有考虑?
真实情况
到底是怎么回事呢?实际上,这些计算器并不是为数学家和工程师准备的,但%的引入,却可以极大方便计算折扣,税率,小费等。比如一件100元的衣服,折扣20%:
代码语言:javascript复制100 - 20%
80
实际上它计算的是
代码语言:javascript复制100 - (100*20%)
早期计算器的按键比较少,没有括号,能显示的字符长度也有限,因此%在这种场景下能解决痛点,极大减少按键数量。
当然了国内我们通常见到的是打几折,不过国外的网站是这样的:
折扣通常都会用类似10% off这样的表达。针对这种表达的计算方式也逐渐成为了一种”标准“,所以在我们的很多计算器中都有。
所以10% 10%实际上计算的是:
代码语言:javascript复制10% 10% * 10%
= 0.11
但是对于国内的用户来说,如果计算器没有括号你会怎么计算?你可能是会按照下面这样:
代码语言:javascript复制100 * 0.8
= 80
毕竟是受过九年义务教育的优秀青年,小数还不会么?
同时你也可以看到在微软自带的计算器(win r,输入calc回车即可打开)中,标准型计算器有%:
不过它计算10% 10%计算得出的值可能每次都不一样,我们稍后解释。
而科学型和程序员型中,压根没有%运算符,通常也不会用百分数直接计算,而是用小数。
那么在标准计算器或者说这些简单功能的计算器中%到底是什么作用呢?
来源:公众号【编程珠玑】 ID:shouwangxiansheng 博客:https://www.yanbinghu.com
从代码角度来看
作为一个程序员,自然要从代码的角度来看了。为此我在github上找到了微软开源的计算器项目,其地址为: https://github.com/microsoft/calculator/
我找到关于%计算的部分,摘出了其中相关的代码:
代码语言:javascript复制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;
}
注释中也已经解释了(论一个好注释的重要性),当操作符是乘法或者除法的时候,与%相关的直接除以100再和另外的数操作(即我们通常认识的算法),否则就按照上一次结果的百分比来计算。 所以,如果你计算10% 10%,它是下面的过程:
代码语言:javascript复制结果 操作
0 初始值
0 输入10%,计算0 10% * 0
0 输入 10%,计算0 10 *0
最终会得到0。只不过很多手机计算器中直接把第一个10%当成了0.1,这也就是我们看到一些手机计算器最终会得到0.11结果的原因。
但是如果你计算100 * 10%,它按照原始的方式计算,即计算得到10。
所以这是有意为之,而并非什么bug!程序员不背这个锅。
另外我们都知道,%常用于取模运算,它是一个二元运算符,例如: 10%3 = 1
所以当你在Linux的命令行输入bc,然后输入10 10%,你会看到下面的结果
代码语言:javascript复制$ bc
10 10%
(standard_in) 3: syntax error
10%3
1
没错,它会提示你语法错误,而不是帮你计算10的10%,因为这里的%并非计算百分数,而是用来取模的。所以在windows自带的程序员计算器和科学计算器中,有MOD,而没有%。
注:bc命令是Linux一个强大的计算器。
总结
%在某些场景方便计算,这不是bug,而是feature。
讨论
- 你觉得有必要使用这种计算方式吗?欢迎留言说出你的看法!
- 你的手机”阵亡“了吗?