【解题】数值的整数次方——关于代码完整性及错误处理方式的探讨(C++实现)

一、题目描述

题目:实现函数double Power(double base, int exponent),求base的exponent次方。不使用库函数,不考虑大数问题。

二、常见的错误解法

 由于不考虑大数问题,可能相当的读者会很快给出如下实现方式:

double Power(double base, int exponent)
{
    double result = 1.0;
    for (int i = 1;i <= exponent;i++)
        result *= base;
    return result;
}

 完成上述代码之后,如果再加上一句疑问:输入的指数为负数的情况怎么处理?此时,可能马上就能意识到,上述算法是不完备的,没有考虑到所有可能的输入。

三、代码的完备性

      为了确保代码的完整性,我们在正式设计算法之前,应该要把所有可能的输入都想清楚,从而避免在程序中出现各种质量漏洞。通常,我们可以从功能测试、边界测试、负面测试三个方面来设计测试用例,以确保代码的完备性。

  1. 功能测试:即考虑普通功能测试的测试用例。比如上述例子中实现的代码可以完成求数值正整数次方的基本要求。
  2. 边界测试:考虑各种边界值的测试用例。如结束循环的边界条件是否正确,递归的终止条件是否正确,图遍历输入的是否为空指针等等。
  3. 负面测试:考虑各种错误的输入。即当出现错误输入时,程序应可以进行不同的错误处理。

四、错误处理的常见方法

      错误处理方式一般有三种:返回值、全局变量和异常。下面将分别简单介绍三种错误处理方式:

  1. 返回值:函数用返回值告诉调用者是否出错。比如很多Windows的API的返回值为0则表示调用成功,而不为0则表示调用出错,不同的非0值定义了不同类型的错误。
  2. 全局变量:设置一个全局变量,当发生错误时,可以在返回值中传递计算结果。但是此方法有个问题:调用者很容易忘记检查全局变量,因此在调用出错的时候忘记做相应的错误处理,从而留下安全隐患。
  3. 异常:当函数运行出错的时候,就抛出一个异常。但是抛出异常的时候,程序的执行会打乱正常的顺序,对程序的性能有很大的影响。

      总的来说,上述三种错误处理方式各有优劣,使用者应根据具体情况决定。

五、一个较优的解题方法

      经过上述对于程序完备性的讨论,我们回到开篇提出来的这个实际问题。首先对于正整数指数的处理没有问题。然后,当指数为负整数时,此时我们知道计算幂值必然会涉及到求倒数。那么新的问题便产生了,0的倒数是没有意义的,反应在计算机上就是当分母为0的时候将产生错误,我们必须对这个错误予以相应的处理。
      最后需要说明的是,0的0次方在数学上是没有意义的,故而返回0还是1都可以接受,应根据应用场景来决定。

bool invalidInput = false;   //全局变量用于表示是否出错
double Power(double base, int exponent)
{
    if (isEqual(base, 0.0) && exponent < 0)   //0的负数次方,没有意义
    {
        invalidInput = true;    //0不能求倒数,标记出错
        return 0.0;
    }

    //求指数的绝对值
    unsigned int absExponent = (unsigned int)(exponent);
    if (exponent < 0)
        absExponent = (unsigned int)(-exponent);

    //计算幂值
    double result = PowerWithUnsigenedExponent(base, absExponent);
    if (exponent < 0)
        result = 1.0 / result;
    return result;

}

bool isEqual(double num1, double num2)
{
    if ((num1 - num2>-0.0000001) && (num1 - num2<0.0000001))
        return true;
    else
        return false;
}

double PowerWithUnsigenedExponent(double base, unsigned int exponent)
{
    if (exponent == 0)
        return 1;
    if (exponent == 1)
        return base;

    double result = PowerWithUnsigenedExponent(base, exponent >> 1);
    result *= result;
    if (exponent & 0x1 == 1)
        result *= base;

    return result;
}

六、分析与总结

      在上面最终给出的算法中,需要说明的几个问题:

  1. 利用全局变量invalidInput标记是否出错,如果出错,函数返回值为0。但是分析上述算法可以知道,当底数为0正常运行也返回0.定义这个全局变量就是为了区分这两种不同的情况。
  2. 判断底数是否为0时,并没有使用if(base==0),原因在于浮点数在计算机中表示存在误差,不能直接使用(==)判断。而应该计算两个小数的差,当这个差值在某个很小的范围内时(如上述0.0000001,这个值应根据实际情况的误差限确定),就认为二者相等。
  3. 计算base的absExponent次幂即函数 PowerWithUnsigenedExponent()中,利用如下关系减少乘法次数。
    【解题】数值的整数次方——关于代码完整性及错误处理方式的探讨(C++实现)_第1张图片
  4. 计算base的absExponent次幂即函数 PowerWithUnsigenedExponent()中,利用右移运算代替除以2运算,利用位与运算代替求余运算来判断一个数的奇偶。因为位运算效率比乘除法高很多。

注:以上内容为《剑指offer》学习笔记。

你可能感兴趣的:(编程语言)