数值的整数次方

简介: 数值的整数次方

前言


在JavaScript中有一个库函数(Math.pow())可以对一个数进行次方运算,本文将实现一个类似pow功能的函数,欢迎各位感兴趣的开发者阅读本文。


实现思路


第一眼看到这个问题,有些开发者可能思考几秒钟,觉得这很简单嘛。直接遍历次方数,将底数与前一次的计算结果相乘即可,直接一把梭🤓,很快就写完了代码,如下所示:


/**
   * 计算一个数的次方
   * @param base 底数
   * @param exponent 指数
   */
  public power(base: number, exponent: number): number {
    let result = 1;
    for (let i = 1; i <= exponent; i++) {
      result *= base;
    }
    return result;
  }


细心的开发者可能已经发现了这段代码的不足之处,上述代码只考虑了指数是正数的情况,当输入的指数为小于1的时候上述代码就计算错误了🌝


640.png

                              image-20211114225904657


全面考虑的解法


接下来,我们把指数为负数和0时的情况考虑进去,来捋一下实现思路:


  • 当指数为负数的时候,需要对指数求绝对值,算出次方的结果之后再取倒数
  • 当指数为0时,我们就要考虑两种情况:
  • 当底数为0且指数为负数时,就会出现对0求倒数,会导致程序运行出错,需要进行容错处理,将错误信息告知调用者
  • 当底数为0且指数为0时,这在数学上是没有意义的,此处我们将结果返回0或1都可以


我们将上述思路转化为代码,如下所示:


/**
   * 计算一个数的次方
   * @param base 底数
   * @param exponent 指数
   */
  public power(base: number, exponent: number): number | string {
    // 求出指数对绝对值
    const absExponent = Math.abs(exponent);
    // 程序会出错
    if (base === 0 && exponent < 0) {
      return "参数错误: 0的负次方不能进行计算";
    }
    // 当底数和指数都为0时,在数学上是没意义的
    if (base === 0 && exponent === 0) {
      return 0;
    }
    let result = 1;
    for (let i = 1; i <= absExponent; i++) {
      result *= base;
    }
    // 指数小于0, 对计算出的结果求倒数
    if (exponent < 0) {
      result = 1 / result;
    }
    return result;
  }


我们将各种情况都考虑进去,作为参数传入上述代码,观察下运行结果,如下所示:


640.png

                                 image-20211114235913960


最优解


上述解法已经满足了题目要求,但是并不是最优解法,接下来,我们来看一种更好的解法。


上述代码中循环计算底数的指数次方代码可以拆分成一个函数,如下所示:


/**
   * 求底数的指数次方
   * @param base
   * @param exponent
   */
  private static powerWithExponent(base: number, exponent: number) {
    let result = 1;
    for (let i = 1; i <= exponent; i++) {
      result *= base;
    }
    return result;
  }


拆分后,我们考虑这样一个场景:


当指数为32时,就需要在上述函数的循环中做31乘法。然而,我们的目标就是求出一个数字的32次方,如果我们已经知道了它的16次方,那么只要在16次方的基础上再平方一次就可以了。而16次方是8次方的平方。


以此类推,我们求32次方只需要做5次乘法:


  • 先求平方
  • 在平方的基础上求4次方
  • 在4次方的基础上求8次方
  • 在8次方的基础上求16次方
  • 在16次方的基础上求32次方


思考到这里,我们设要求的次方为n,那么:


  • 当n为偶数时,可以拆分为n/2 * n/2
  • 当n为奇数时,可以拆分为(n-1)/2 * (n-1)/2


乘式两边计算出结果后,仍然可以对结果应用上述规则进行计算,直至n为0或1,总结成公式后,如下图所示:


640.png

                                 image-20211115211445909


实现代码


有了公式后,我们很快就能想到可以用递归来解决这个问题,我们来画一下递归栈,如下图所示:


640.png

                          image-20211115233901255


思路捋清楚后,就可以愉快的进入编码环节了🤓,完整代码如下所示:


export default class IntegerPower {
  /**
   * 计算一个数的次方
   * @param base 底数
   * @param exponent 指数
   */
  public power(base: number, exponent: number): number | string {
    // 求出指数对绝对值
    const absExponent = Math.abs(exponent);
    // 程序会出错
    if (base === 0 && exponent < 0) {
      return "参数错误: 0的负次方不能进行计算";
    }
    // 当底数和指数都为0时,在数学上是没意义的
    if (base === 0 && exponent === 0) {
      return 0;
    }
    // 进行次方运算
    let result = this.powerWithExponent(base, absExponent);
    // 指数小于0, 对计算出的结果求倒数
    if (exponent < 0) {
      result = 1 / result;
    }
    return result;
  }
  /**
   * 求底数的指数次方
   * @param base 底数
   * @param exponent 指数
   */
  private powerWithExponent(base: number, exponent: number): number {
    // 递归终止条件
    if (exponent === 0) {
      // 非0数的0次方值为1
      return 1;
    }
    if (exponent === 1) {
      // 任意数的0次方为其本身
      return base;
    }
    // 递归对指数/2,计算结果
    // 用右移运算符代替除以2运算
    let result =
      this.powerWithExponent(base, exponent >> 1) *
      this.powerWithExponent(base, exponent >> 1);
    // 指数为奇数时,result就为result乘以底数
    if (exponent % 2 === 1) {
      result *= base;
    }
    return result;
  }
}


上述代码中,我使用了右移运算符来代替除法运算,这样可以提升代码的执行速度。对此不了解的开发者请移步我的另一篇文章:二进制中一的个数-右移运算符[1]

对递归不熟悉的开发者,请移步:递归的理解与实现[2]


编写测试用例


接下来,我们将各种边界条件都考虑进去,验证下上述代码能否正确执行。


import IntegerPower from "../IntegerPower.ts";
const powerHandler = new IntegerPower();
const result1 = powerHandler.power(5, 6);
const result2 = powerHandler.power(3, -4);
const result3 = powerHandler.power(0, 0);
const result4 = powerHandler.power(0, 3);
const result5 = powerHandler.power(0, -3);
console.log(result1);
console.log(result2);
console.log(result3);
console.log(result4);
console.log(result5);


运行结果如下所示:


640.png

                                       image-20211115225812545


项目代码


文中的示例代码请移步:


  • IntegerPower.ts[3]
  • integerPower-test.ts[4]


写在最后


至此,文章就分享完毕了。


我是神奇的程序员,一位前端开发工程师。


如果你对我感兴趣,请移步我的个人网站[5],进一步了解。


  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 文中链接可从文末参考资料中获取🤗


相关文章
|
SQL 关系型数据库 MySQL
如何使用MySQL Binlog Digger 4.14对binlog日志进行挖掘分析以便快速恢复误删除数据
MySQL Binlog Digger是一款运行在windows操作系统的挖掘与分析MySQL binlog的可视化工具,通过它可以快速打回被误操作时的数据,例如:delete, insert, update操作,并依据这些误操作生成相应的undo回滚语句,以便快速恢复数据,此外,它还可以支持离线binlog挖掘分析与binlog下载,它仅支持dml操作的回滚,但不支持ddl的回滚。
5711 1
如何使用MySQL Binlog Digger 4.14对binlog日志进行挖掘分析以便快速恢复误删除数据
|
缓存 安全 Java
Spring Get请求 与post请求
本文详细介绍了Spring框架中GET请求和POST请求的区别及应用场景。GET请求用于从服务器获取资源,参数附在URL末尾,适合查看非敏感信息;POST请求用于向服务器提交数据,参数在请求体中传输,适合处理敏感信息。Spring通过`@GetMapping`和`@PostMapping`注解分别处理这两种请求。此外,文章还提供了示例代码,展示了如何在Spring中实现这两种请求的处理。最后,文章总结了推荐使用POST请求的原因,包括更高的安全性、更大的数据传输量、更好的幂等性及灵活性。
838 1
Spring Get请求 与post请求
|
XML JSON Java
深入解析 Java @RestController 注解:构建现代化的RESTful API
在当今的Web应用开发中,RESTful API已成为连接前后端的重要桥梁。为了更方便地构建满足RESTful架构风格的API,Spring框架引入了 @RestController 注解,使得构建和管理API更加高效和简单。本文将带您深入了解 @RestController 注解,探讨其特点、用法、实现方式以及在实际应用中的优势。
java202303java学习笔记第二十一天-多态的综合练习2
java202303java学习笔记第二十一天-多态的综合练习2
190 0
java202303java学习笔记第二十一天-多态的综合练习2
|
安全 Linux 网络安全
个人博客刚部署,隔壁开发还没开始馋,就有人来撬门(上)
本文专门用于记录服务器运行过程中遇到的 安全问题及应对之法。
245 0
|
弹性计算 大数据 Linux
如何选择阿里云服务器相关配置
简介: 什么配置的阿里云服务器是适合自己的呢?下面我们就来说说如何选择阿里云服务器配置。
如何选择阿里云服务器相关配置
|
负载均衡 网络协议 Java
基于Docker部署 Tomcat集群、 Nginx负载均衡
当作一百世一样。这里的道理很明白:我思故我在,既然我存在,就不能装作不存在。无论如何,我要为自己负起责任。——王小波《三十而立》
646 0
基于Docker部署 Tomcat集群、 Nginx负载均衡
|
Go
Golang-Defer
Golang-Defer
237 0
|
应用服务中间件
|
存储 NoSQL Java
Redis持久化深入理解
Redis持久化深入理解