Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

幂与对数

幂是指将一个数(底数)自乘若干次(由指数决定)的运算. 其一般形式为 , 表示 乘以自身 次.

中, 是底数, 是指数.

不同指数的幂有特性:

正整数指数

  • 域: 1
  • 定义:
  • 性质:

零指数

  • 域:
  • 定义:
  • 注:
    • 这是为了保持幂运算的连续性, 例: , 确保像这类运算可以正常执行.
    • 未定义的. (某些情况下等于 但不推荐使用)

负整数指数

  • 域:
  • 定义:
  • 注:
    • 负指数表示倒数.
    • 这是为了扩展幂运算的定义域, 使其在整数范围内封闭.

分数指数

  • 域:
  • 定义:

Note

分数指数的本质是将幂运算延拓到有理数集. 这里的推广基于正整数指数的性质: :

如果分数指数要满足该性质, 我们可以用一个简单的式子: 看前面的一项 再利用, 不难得到.

无理指数

  • 域:
  • 注: 无理指数将幂运算延拓到实数甚至复数集.

定义: 设 是一个正实数, 是一个无理数. 选取一个有理数序列 使得 , 则无理指数 定义为: 这个方法的本质是通过有理数无限逼近一个无理数来进行. 我们也可以通过指数函数来进行延拓, 此处不再赘述.

编程中使用

一般来讲, 我们将设计的幂算法只支持自然数. 更广的作用域, 在编程中通常不使用. 接下来我们开始设计:

从定义出发, 我们不难想到只需要对一个基数进行循环n次, 每次乘它自身, 便可得到:

pub fn power_iter(base: i64, exponent: u32) -> i64 {
    // 下面省略溢出检测
    let mut result = 1;
    for _ in 0..exponent {
        result *= base;
    }
    result
}

它的时间复杂度是, 对于较大的输入规模, 这个算法可能并不特别好. 我们还有一种名为快速幂的方法:

fn power_fast(mut base: i64, mut exponent: u32) -> i64 {
    let mut result = 1;
    while exponent > 0 {
        if exponent % 2 == 1 {
            result *= base;
        }
        base *= base;
        exponent /= 2;
    }
    result
}

它采用分治法的策略, 时间复杂度是. 其数学基础是对于输入规模, 如果它是偶数, 那么, 如果它是奇数, 那么. 快速幂算法实际上是在利用指数的二进制表示: 2

所以完全可以用位运算:

fn power_fast(mut base: i64, mut exponent: u32) -> i64 {
    let mut result = 1;
    while exponent > 0 {
        if exponent & 1 == 1 {  // 等价于 exponent % 2 == 1
            result *= base;
        }
        base *= base;
        exponent >>= 1;  // 等价于 exponent /= 2
    }
    result
}

利用stdpow可以直接进行幂运算:

let base = 2;
let exponent = 3;
let result = base.pow(exponent); // 2^3 = 8

对数

对数(Logarithm) 是数学中用于简化乘除运算的一种函数, 定义为幂运算的逆运算. 具体来说, 如果满足以下等式: 就是以为底的对数, 记作: 其中, 为底数(), 为真数(). 在上面这两个式子中, 我们可以得到对数与指数的关系: 我们常用几种特殊的对数:

  • : 常用对数. (可以简写为), 用于科学计算和工程.
  • : 自然对数. (可以简写为), 广泛用于微积分和自然科学.
  • : 二进制对数. (仅在计算机科学中, 我们可以简写为), 常见于计算机科学和信息论.

性质

对数的运算有很多特殊的性质:

乘法性质

性质:

证明: 设 , , 则: 两式相乘: 取对数得: 证毕.

除法性质

性质:

证明: 设 , , 则: 两式相除: 取对数得: 证毕.

幂运算性质

性质:

证明: 设 , 则: 取对数得: 证毕.

换底公式

性质:

证明: 设 , 则: 解得: 即: 证毕.

特殊值性质

性质1: 证明: 由 , 根据定义: 证毕.

性质2: 证明: 由 , 根据定义: 证毕.

对数将复杂的乘除、幂运算转化为加减乘除, 是数学和科学中不可或缺的工具.

在编程中使用

其实在幂那里我们就已经知道了——由于计算机的二进制特性, 利用位运算我们可以计算整数以为底的对数:

pub fn log2_bitwise(n: u32) -> u32 {
    // 下面省略数据校验
    31 - n.leading_zeros()
}

位操作法 的核心思想是一个整数的二进制表示中, 最高有效位(MSB)的位置即为其以2为底的对数. n.leading_zeros() 返回n的二进制表示中前导零3的个数. u32的二进制长度是32, 由于位置编号从0开始(范围是0~31), 所以最高有效位是31 - leading_zeros. 对于其他底数, 我们也可以使用牛顿迭代法:

pub fn log_newton(x: f64, base: f64, epsilon: f64) -> f64 {
    // 下面省略数据校验
    let mut y = (x.ln() / base.ln()).max(0.0); // 初始猜测
    loop {
        let next_y = y - (base.powf(y) - x) / (base.powf(y) * base.ln());
        if (next_y - y).abs() < epsilon {
            return next_y;
        }
        y = next_y;
    }
}

牛顿迭代法 是一种用于近似求解方程 的根的高效数值方法. 其主要通过切线逼近逐步修正解的近似值. 从一个初始猜测值开始. 用当前点的切线(导数)更新: 重复迭代, 直到小于预设精度4.

利用std相关方法可以直接进行对数运算:

// log
let five = 5.0f32;
// log5(5) - 1 == 0
let abs_difference = (five.log(5.0) - 1.0).abs();

// log2
let two = 2.0f32;
// log2(2) - 1 == 0
let abs_difference = (two.log2() - 1.0).abs();

// log10
let ten = 10.0f64;
// log10(10) - 1 == 0
let abs_difference = (ten.log10() - 1.0).abs();

// ln
let one = 1.0_f64;
// e^1
let e = one.exp();
// ln(e) - 1 == 0
let abs_difference = (e.ln() - 1.0).abs();

练习与回答

  1. 试证明一个整数的二进制表示中, 最高有效位(MSB)的位置即为其以2为底的对数.

证明

命题: 对于正整数, 其二进制最高有效位的位置满足:

证明:

的二进制表示为:

因为, 所以:

对不等式取:

因此:


  1. 集合. 有时也用 .

  2. 中的表示二进制形式, 同理是十六进制.

  3. 前导零(Leading Zeros) 是指一个数的二进制表示中, 从最高位开始连续出现的零, 直到遇到第一个1为止.

  4. (epsilon) 是希腊字母表的第5个字母. 表示任意小的正数, 用于量化“无限接近“的概念. 计算机科学中向如上迭代算法中也可表示停止阈值, 一般用常量(如const EPS = 0.000001).