float_backend 教程

本教程使用 Complex[Double] 的解析函数进行计算。你将在直角坐标形式与极坐标形式之间转换,求根和对数,解二次方程,计算三角函数及其反函数,并了解分支切割线的位置。公式及其数值处理见 float_backend 设计。

快速开始

moon add Luna-Flow/luna-complex@0.2.0
import {
  "Luna-Flow/luna-complex" @complex,
  "Luna-Flow/luna-complex/float_backend" @fb,
}
fn main {
  let z = @complex.Complex::new(-3.0, 4.0)
  println("|z|     = \{@fb.abs(z)}")
  println("sqrt(z) = \{@fb.sqrt(z)}")
  println("exp(z)  = \{@fb.exp(z)}")
}
|z|     = 5
sqrt(z) = 1 + 2i
exp(z)  = -0.032542999640154786 + -0.03767897757486585i

日常任务

极坐标形式

fn main {
  let z = @complex.Complex::new(1.0, 1.0)
  let r = @fb.abs(z)
  let theta = @fb.arg(z)
  println("r = \{r}, theta = \{theta}")
  println("back: \{@fb.polar(r, theta)}")
}
r = 1.4142135623730951, theta = 0.7853981633974483
back: 1.0000000000000002 + 1i

解二次方程

az2+bz+caz^2 + bz + c 的根为 (−b±b2−4ac)/(2a)(-b \pm \sqrt{b^2 - 4ac})/(2a)。借助 sqrt 和 div,该公式适用于复系数和负判别式:

fn main {
  // z^2 + 2z + 5 = 0
  let a = @complex.Complex::new(1.0, 0.0)
  let b = @complex.Complex::new(2.0, 0.0)
  let c = @complex.Complex::new(5.0, 0.0)
  let four = @complex.Complex::new(4.0, 0.0)
  let two_a = a + a
  let root = @fb.sqrt(b * b - four * a * c)
  println("z1 = \{@fb.div(-b + root, two_a)}")
  println("z2 = \{@fb.div(-b - root, two_a)}")
}
z1 = -1 + 2i
z2 = -1 + -2i

对数与幂

fn main {
  let z = @complex.Complex::new(0.0, 1.0)
  println("log(i)   = \{@fb.log(z)}")
  println("i^i      = \{@fb.pow(z, z)}")
  println("i^2      = \{@fb.pow_real(z, 2.0)}")
  println("log10(1000) = \{@fb.log_10(@complex.Complex::new(1000.0, 0.0))}")
}
log(i)   = 0 + 1.5707963267948966i
i^i      = 0.20787957635076193 + 0i
i^2      = -1 + 0i
log10(1000) = 2.9999999999999996 + 0i

ii=ei⋅iπ/2=e−π/2i^i = e^{i \cdot i\pi/2} = e^{-\pi/2} 是实数。整数指数使用精确的二进制快速幂,因此 i2i^2 恰好为 −1-1。

三角函数及其反函数

fn main {
  let z = @complex.Complex::new(0.5, 0.5)
  let s = @fb.sin(z)
  println("sin(z)       = \{s}")
  println("asin(sin(z)) = \{@fb.asin(s)}")
  println("asin(2)      = \{@fb.asin_real(2.0)}")
}
sin(z)       = 0.5406126857131534 + 0.4573041531842493i
asin(sin(z)) = 0.5 + 0.4999999999999999i
asin(2)      = 1.5707963267948966 + 1.3169578969248166i

asin_real 接受一个 Double,在 [−1,1][-1, 1] 之外返回复数结果。

大参数

这些公式经过缩放,因此极大或极小的输入都不会上溢:

fn main {
  let huge = @complex.Complex::new(1.0e300, 1.0e300)
  println("|huge|     = \{@fb.abs(huge)}")
  println("log|huge|  = \{@fb.abs_log(huge)}")
  println("tan(1+50i) = \{@fb.tan(@complex.Complex::new(1.0, 50.0))}")
  let q = @fb.div(@complex.Complex::new(1.0, 0.0), @complex.Complex::new(1.0e-300, 1.0e-300))
  println("1/(tiny)   = \{q}")
}
|huge|     = 1.4142135623730952e+300
log|huge|  = 691.1221014884936
tan(1+50i) = 6.765311025183565e-44 + 1i
1/(tiny)   = 4.9999999999999995e+299 + -4.9999999999999995e+299i

进阶

分支切割线

多值函数在跨越分支切割线时会跳变。sqrt 的割线位于负实轴;从上方和下方逼近它会得到符号相反的根:

fn main {
  println(@fb.sqrt(@complex.Complex::new(-4.0, 1.0e-12)))
  println(@fb.sqrt(@complex.Complex::new(-4.0, -1.0e-12)))
  println(@fb.sqrt(@complex.Complex::new(-4.0, 0.0)))
}
2.5e-13 + 2i
2.5e-13 + -2i
0 + 2i

在割线上,根为 +2i+2i。设计文档列出了每个函数的割线。

编写标量泛型的辅助函数

借助能力 trait,辅助函数可以声明它对实数标量的需求。例如,一个只检查特殊值的函数只要求 FloatingSpecialValues,便可同时适用于 Float 和 Double:

fn[T : @fb.FloatingSpecialValues] finite_parts(re : T, im : T) -> Bool {
  !(@fb.FloatingSpecialValues::is_nan(re) || @fb.FloatingSpecialValues::is_inf(re) ||
  @fb.FloatingSpecialValues::is_nan(im) || @fb.FloatingSpecialValues::is_inf(im))
}

fn main {
  let z = @fb.exp(@complex.Complex::new(800.0, 1.0))
  println(finite_parts(z.re, z.im))
  println(finite_parts((1.0 : Float), (2.0 : Float)))
}
false
true

常见陷阱

  • 负实轴。 arg 和 log 目前在负实轴上返回 2π2\pi 而非 π\pi(log(-1) 为 2πi2\pi i),负实数的非整数次幂也继承了这一角度;acos、acos_real、asec_real 和 acosh_real 对负输入也受影响。参见 float_backend API 中的警告。
  • 倒数类函数在极点处中止。 cot(0)、csc(0)、asec(0) 等会终止程序,而不是返回无穷大。
  • 对极端值使用 @fb.div 而非 /。 核心运算符会计算 c2+d2c^2 + d^2,若其下溢为零则中止。
  • exp 过早上溢。 当 Re⁡z>709.78\operatorname{Re} z > 709.78 时,exp(z) 含有无穷或 NaN 分量。
  • 仅支持 Complex[Double]。 解析函数不接受 Complex[Float]。

后续步骤