转载注明出处,谢谢
Windows 自带的计算器是我们接触 Windows 操作系统后必玩App之一。
然而还处于孩提时懵懂阶段的我们可能在课堂上刚学习了除法,就要使用计算器试一下。
于是当一双双稚嫩的小手依次按下 0 / 0 Enter 这几个键后,Window 计算器总会给我们一个莫名其妙的结果:函数结果未定义。
0不能做除数————我们后来知道了有这么一回事儿。
0/2=0
0/3=0
0/43605=0
...
但是:
1/0 [无法计算,除数不能为0]
10/0 [无法计算,除数不能为0]
65535/0 [无法计算,除数不能为0]
...
为什么?
好了,除法的本质是什么?比如 25/6=4...(余)1.
这就代表了在被除数25中能最多放下四个6还能再放一个1.
如果我们这样计算25/6:
⓪ 令 ans := 0
① 25-6=19,19≥6 ? Yes, 记录 ans := ans + 1 (ans=1), 继续。
② 19-6=13,13≥6 ? Yes, 记录 ans := ans + 1 (ans=2), 继续。
③ 13-6=7, 7≥6 ? Yes, 记录 ans := ans + 1 (ans=3), 继续。
④ 7-6=1, 1≥6 ? No!, 记录 ans := ans + 1 (ans=4), 计算结束,输出答案 ans=4。
我们再来算一个:6/2:
⓪ 令 ans := 0
① 6-2=4,4≥2 ? Yes, 记录 ans := ans + 1 (ans=1), 继续。
② 4-2=2,4≥2 ? Yes, 记录 ans := ans + 1 (ans=2), 继续。
③ 2-2=0,0≥6 ? No!, 记录 ans := ans + 1 (ans=3), 计算结束,输出答案 ans=3。
我们发现:只要被除数大于除数,两个正整数相除,余数不是零就是另一个正整数。
好了,那我们看看0/2会怎么样?
⓪ 令 ans := 0
① 0-2=-2,-2≥2 ? 这一步不算数,因为结果都是负数了,和说好的不一样!所以,我决定 计算结束,输出答案 ans=0。
那么,3/0呢?
⓪ 令 ans := 0
① 3-0=3,3≥3 ? Yes, 记录 ans := ans + 1 (ans=1), 继续。
② 3-0=3,3≥3 ? Yes, 记录 ans := ans + 1 (ans=2), 继续。
③ 3-0=3,3≥3 ? Yes, 记录 ans := ans + 1 (ans=3), 继续。
④ 3-0=3,3≥3 ? Yes, 记录 ans := ans + 1 (ans=4), 继续。
⑤ 3-0=3,3≥3 ? Yes, 记录 ans := ans + 1 (ans=5), 继续。
⑥ 3-0=3,3≥3 ? Yes, 记录 ans := ans + 1 (ans=6), 继续。
......
啊咧?好像停不下来了呢!
这样下去,ans 是要等于+∞的节奏了!
让我们回过头追查为什么 Windows 计算器给我们一个:“函数结果未定义。”吧!
为此,我找来了传说的泄漏 NT4 源码(windows 2000 source code.zip)。
我们找到了计算器 Calc.EXE 的可能源代码。
计算器的源代码运算除法使用了一个叫做 ratpak 的科学计算库。
根据 ratpak 源代码注释出的时间戳,大部分 ratpak 代码由 MS 的程序员 Timothy David Corrie Jr. 于1995年1月16日编写。
其中,calcerr.h 文件中定义了所有的错误代码。而宏 CALC_E_DIVIDEBYZERO 和 CALC_E_INDEFINITE 就分别表示被0除和“函数结果未定义”。
我们看看抛出 CALC_E_INDEFINITE 的除法函数吧,它被写在文件 rat.c 中,并且它大致是这样的:
void divrat( PRAT *pa, PRAT b )
{
if ( !zernum( (*pa)->pp ) )
{
// Only do the divide if the top isn't zero.
mulnumx( &((*pa)->pp), b->pq );
mulnumx( &((*pa)->pq), b->pp );
if ( zernum( (*pa)->pq ) )
{
// raise an exception if the bottom is 0.
throw( CALC_E_DIVIDEBYZERO );
}
trimit(pa);
}
else
{
// Top is zero.
if ( zerrat( b ) )
{
// If bottom is zero
// 0 / 0 is indefinite, raise an exception.
throw( CALC_E_INDEFINITE );
}
else
{
// 0/x make a unique 0.
DUPNUM( ((*pa)->pq), num_one );
}
}
}