Two doubles, and all the ways they surprise you
Complex is a pair of doubles. The arithmetic is the C99
behaviour, including the parts nobody writes down: signed zeros, infinities
that would otherwise turn into NaN, and a repr that drops the real part when
it is a positive zero.
Reading
Every form you would write by hand is accepted, plus the i
suffix for people coming from mathematics rather than engineering.
for text in ["1+2j", "(1+2j)", "2j", "j", "-j", "1e3j", "3+5i", "+2j"] {
println(@moonnum.parse_complex(text))
}
(1+2j)
(1+2j)
2j
1j
-1j
1000j
(3+5j)
2j
Spaces are not accepted inside, exactly as in the language
this library follows: "1 - 2j" is refused, "1-2j"
is not. Accepting both would make it impossible to say what a trailing space
means.
Arithmetic that keeps its infinities
Multiplying a complex number with an infinite part by another with a zero
part yields nan under the naive formula. The implementation
follows the recovery step of C11's Annex G, so the answer is the infinity
the arithmetic asks for.
let z = @moonnum.parse_complex("3+4j")
z + @moonnum.Complex::new(1.0, 0.0)
z * z
z.divide(z)
z.abs()
z.conjugate()
(4+4j)
(-7+24j)
(1+0j)
5
(3-4j)
Integer powers are exact
A small integer exponent is applied by repeated squaring, not by
exp(iθ · n). That is why (1+1j)² is exactly
2j and not 1.2246e-16+2j. Non-integer exponents go
through the polar form, as they must.
let z = @moonnum.parse_complex("1+1j")
z.pow(@moonnum.parse_complex("2"))
z.pow(@moonnum.parse_complex("100"))
z.pow(@moonnum.parse_complex("0.5"))
2j
(-1125899906842624+0j)
(1.0986841134678098+0.45508986056222733j)
Division by zero says so
Complex::divide is a function and not an operator, because
MoonBit's division trait cannot raise. A zero divisor is
ZeroDivision, not a NaN that propagates.
let z = @moonnum.parse_complex("3+4j")
let zero = @moonnum.parse_complex("0j")
let answer : String = z.divide(zero).to_string() catch {
@moonnum.ZeroDivision => "no answer"
_ => "refused"
}
no answer
Formatting
One specifier, both halves. The imaginary part always carries a sign; the
real part follows your sign flag. Zero padding and = alignment
are refused, as they are in the language this follows, because padding
inside a pair has no sensible meaning.
let z = @moonnum.parse_complex("3+4j")
z.format("")
z.format(".2f")
z.format("+9.2f")
z.format("<14.1e")
(3+4j)
3.00+4.00j
+3.00+4.00j
3.0e+00+4.0e+00j