找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 4501|回复: 3

(Windows计算器)函数结果未定义?

[复制链接]
发表于 2016-8-6 00:26:04 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
转载注明出处,谢谢
Screen Shot 2016-08-06 at 00.17.23.png
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_DIVIDEBYZEROCALC_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 );
            }
        }
}

这样子,是不是什么都明白了呢?
// 只在顶点(被除数)不为0的时候做除法。
// 如果底部(除数)为0,则抛出被零除异常。
// 如果顶点为零,那么则又分为两种情况。
// 如果是0/0那么抛出异常:函数结果未定义。
// 否则,计算结果就是0了。好比计算0/1=?
回复

使用道具 举报

发表于 2016-8-7 20:09:56 | 显示全部楼层
好文章啊,为什么被除数不能为0,这下更清晰了,不仅如此,还用代码演示了一遍。
不过windows的计算器太复杂了。
回复 赞! 靠!

使用道具 举报

发表于 2016-8-16 00:55:56 | 显示全部楼层
····························
回复 赞! 靠!

使用道具 举报

发表于 2018-2-18 15:45:11 | 显示全部楼层
只能说,微软临时工干的,我写的计算器都不存在这情况
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-21 21:00 , Processed in 0.039847 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表