core チュートリアル
このチュートリアルでは、ジェネリックな Complex[T] 型を使って複素数の算術を行います。複素数を構築して出力し、Double 上と整数上で計算し、luna-generic トレイトを通じて複素数を受け付けるジェネリックなコードを書き、値をインプレースで更新します。背後にある代数は core の設計 にあります。
クイックスタート
moon add Luna-Flow/luna-complex@0.2.0
import {
"Luna-Flow/luna-complex" @complex,
}
fn main {
let z = @complex.Complex::new(1.0, 2.0)
let w = @complex.Complex::new(3.0, -1.0)
println("z + w = \{z + w}")
println("z * w = \{z * w}")
println("z / w = \{z / w}")
}
z + w = 4 + 1i
z * w = 5 + 5i
z / w = 0.1 + 0.7000000000000001i
日常的なタスク
共役と絶対値の 2 乗
は実数です:
fn main {
let z = @complex.Complex::new(3.0, 4.0)
let n = z * z.conjugate()
println("z * conj(z) = \{n}")
println("1 / z = \{z.inv()}")
}
z * conj(z) = 25 + 0i
1 / z = 0.12 + -0.16i
ガウス整数
Int 上では、Complex[Int] はガウス整数の環 です。除算以外はすべて動作し、結果は厳密です:
fn main {
let a = @complex.Complex::new(2, 1)
let b = @complex.Complex::new(2, -1)
println("(2 + i)(2 - i) = \{a * b}")
let mut p = @complex.Complex::one()
for _ in 0..<4 {
p = p * @complex.Complex::new(1, 1)
}
println("(1 + i)^4 = \{p}")
}
(2 + i)(2 - i) = 5 + 0i
(1 + i)^4 = -4 + 0i
から、 は において素数でないことがわかります。
環上のジェネリックなコード
luna-generic トレイトに対して書かれたコードは複素数を受け付けます。ここでは を Horner 法で評価します:
fn[T : @lg.Ring] horner(coefficients : Array[T], x : T) -> T {
let mut acc : T = @lg.Zero::zero()
for i = coefficients.length() - 1; i >= 0; i = i - 1 {
acc = acc * x + coefficients[i]
}
acc
}
fn main {
let one : @complex.Complex[Double] = @complex.Complex::one()
let zero : @complex.Complex[Double] = @complex.Complex::zero()
let i = @complex.Complex::new(0.0, 1.0)
println("p(i) = \{horner([one, zero, one], i)}")
println("p(2) = \{horner([1.0, 0.0, 1.0], 2.0)}")
}
p(i) = 0 + 0i
p(2) = 5
インプレースでの更新
set、set_re、set_im は値を上書きします。その値へのすべての束縛が変更を観測します:
fn main {
let acc = @complex.Complex::new(0.0, 0.0)
let shared = acc
for k in 1..=3 {
let kd = k.to_double()
acc.set(acc.re + kd, acc.im - kd)
}
println("acc = \{acc}")
println("shared = \{shared}")
}
acc = 6 + -6i
shared = 6 + -6i
さらに進んで
入れ子の複素数
Complex[T] は、必要なトレイトを持つ任意の T に対して動作し、Complex[Double] 自体も例外ではありません:
fn main {
let a = @complex.Complex::new(1.0, 2.0)
let b = @complex.Complex::new(-0.5, 0.25)
let z = @complex.Complex::new(a, b)
let w = z * z.inv()
println("z * z^-1 = (\{w.re}) + (\{w.im})j")
}
z * z^-1 = (1.0000000000000002 + 5.204170427930421e-17i) + (0 + 0i)j
この の絶対値の 2 乗 は可逆なので、積は丸め誤差の範囲で になります。一般には Complex[Complex[Double]] は体ではありません。下のよくある落とし穴を参照してください。
解析関数
コアには sqrt、exp、sin はありません。Complex[Double] 向けには Luna-Flow/luna-complex/float_backend をインポートしてください。方法は float_backend チュートリアル で説明しています。
よくある落とし穴
- ゼロ除算は中断する。
z / wとw.inv()は に対してInverse::invを呼び出します。Doubleでは、値がゼロのとき、また でアンダーフローしたときにも中断します。堅牢な除算にはfloat_backendの@fb.divを使ってください。 - 大きな値は除算でオーバーフローする。 で がオーバーフローします。
- 共有された変更。
let shared = zはコピーしません。セッターはすべての別名に影響します。 - テキスト形式。 負の虚部は
+ -2iと出力されます。別の形式が必要な場合はdebug_inspectか独自の整形を使ってください。 - 入れ子の型は体ではない。
Complex[Complex[Double]]では、 のような値の絶対値の 2 乗はゼロとなり、invが中断します。
次のステップ
- core API にはすべてのメソッドとインスタンスが載っています。
- core の設計 では構成と体になる条件を導出しています。
- float_backend チュートリアル では解析関数を扱います。
- luna-generic では、ジェネリックなコードで使うトレイトを説明しています。