/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/math/complex.nv
377 строк
19 KB
Evgeniy Golovin
merge: comment-hygiene-2 — чистка комментариев std (batch 1-20) + линт-свип 330→13
01 авг 2026, 18:13
01 авг 2026, 18:13
bb8b33d
Код
Авторство
О чём код?
// stdlib/complex.nv — комплексные числа Complex с алгеброй и // тригонометрической формой. // // API в стиле stdlib/duration.nv: // - Complex(re, im) прямой конструктор (D215 named-tuple) // - Complex(re: x) / Complex(re: n as f64) действительная часть // - s.to_complex() Result[Complex, ParseComplexError] — парсинг (174.1) // (обратное к @to_str() -> str, // round-trip property) // - Complex(im: x) доменный конструктор — мнимая часть // - Complex.from_polar(r, theta) из полярных координат // - 3.0.j() / 5.j() sugar для мнимой единицы // (доменный синтаксис эл.техники) // - z + w, z * w, z.conj(), z.abs() обычная алгебра через @-методы // - z.sqr(), z.norm_sqr() квадрат и квадрат модуля // - z.is_finite(), z.to_str() сервисные методы // // Decisions: // - D215 named-tuple: `type Complex(re f64 = 0.0, im f64 = 0.0)` — stack-allocated, // default-конструктор `Complex()` даёт 0+0i; всё иммутабельно через ro-binding. // Прямой record-конструктор — канон; тривиальные static-обёртки // `.new`/`.from`/`.from_imag` над ним СНЯТЫ (владелец, 2026-07-08, см. ниже). // - D35 instance-методы через `fn Type @method`. // - D45 для простых геттеров `-> T` опускаем; для арифметики оставляем // явно (документация stdlib API). // - D46/D363 operator overloading через `@plus`, `@times`, `@neg`, `@equal`. // `!=` выводится из `@equal` автоматически. // - D66 `Self` в return type — DRY, без повтора имени Complex. // - Строковое представление — `Complex @to_str() -> str` (D410). // // Использует instance-методы на f64 (std.math): `@sqrt`, `@hypot`, // `@atan2`, `@cos`, `@sin`, `@is_finite`, `@is_nan`, `@abs`. module math.complex // ────────────────────────────────────────────────────────────────────────── // Тип // ────────────────────────────────────────────────────────────────────────── /// Complex number in Cartesian coordinates (a + bi). Immutable — all operations /// return a new instance. /// /// # Examples /// ```nova /// let z = Complex(1.0, 2.0) /// assert(z.real() == 1.0) /// assert(z.imag() == 2.0) /// ``` #stable(since = "0.1") export type Complex(re f64 = 0.0, im f64 = 0.0) /// Zero: 0 + 0i. #stable(since = "0.1") export const ZERO Complex = Complex() /// One: 1 + 0i (multiplicative identity). #stable(since = "0.1") export const ONE Complex = Complex(re: 1.0) /// Imaginary unit: 0 + 1i. `I * I == -ONE`. #stable(since = "0.1") export const I Complex = Complex(im: 1.0) // ────────────────────────────────────────────────────────────────────────── // Static-конструкторы // ────────────────────────────────────────────────────────────────────────── // // Владелец (2026-07-08): `Complex.new`/`Complex.from(f64)`/`Complex.from(int)`/ // `Complex.from_imag` сняты — тривиальные обёртки без логики над record- // конструктором самого `Complex(re, im)` (D215 named-tuple уже даёт прямой // конструктор). Канон на call-site: `Complex(re, im)`, `Complex(re: x)`, // `Complex(im: x)`, `Complex(re: n as f64)` для int. Побочный эффект: // снятие `Complex.new`-style static-namespace функций (все были `-> Self`, // без вычислений/валидации) обходило codegen-gap: static `Type.fn() // -> Self` для named-tuple + instance-методы того же типа в одном CU давали // pointer/value-repr конфликт (`NovaTuple_Complex`) — gap закрыт 2026-07-06, // фикс сохранил решение владельца. `from_polar` (вычисление polar→cartesian) // и str-экстеншен `s.to_complex()` (парсинг, 174.1) ОСТАЮТСЯ — несут логику, не тривиальны. /// From polar coordinates: `r * (cos θ + i·sin θ)`. theta in radians. #stable(since = "0.1") export fn Complex.from_polar(r f64, theta f64) -> Self => Complex(re: r * theta.cos(), im: r * theta.sin()) // ────────────────────────────────────────────────────────────────────────── // Геттеры — типы очевидны, опускаем `-> T` (D45) // ────────────────────────────────────────────────────────────────────────── /// Real part (the a in a+bi). #stable(since = "0.1") export fn Complex @real() => @re /// Imaginary part (the b in a+bi). #stable(since = "0.1") export fn Complex @imag() => @im /// Addition: `(a+bi) + (c+di) = (a+c) + (b+d)i`. #stable(since = "0.1") export fn Complex @plus(other Complex) -> Complex => Complex(re: @re + other.re, im: @im + other.im) /// Subtraction. #stable(since = "0.1") export fn Complex @minus(other Complex) -> Complex => Complex(re: @re - other.re, im: @im - other.im) /// Unary negation: `-(a+bi) = -a + (-b)i`. #stable(since = "0.1") export fn Complex @neg() -> Complex => Complex(re: -@re, im: -@im) /// Multiplication: `(a+bi)(c+di) = (ac-bd) + (ad+bc)i`. #stable(since = "0.1") export fn Complex @times(other Complex) -> Complex => Complex( re: @re * other.re - @im * other.im, im: @re * other.im + @im * other.re, ) /// Division: `(a+bi)/(c+di) = ((ac+bd) + (bc-ad)i) / (c²+d²)`. /// IEEE 754 behavior on zero denominator (inf/nan). #stable(since = "0.1") export fn Complex @div(other Complex) -> Complex { ro denom = other.re * other.re + other.im * other.im Complex( re: (@re * other.re + @im * other.im) / denom, im: (@im * other.re - @re * other.im) / denom, ) } // ────────────────────────────────────────────────────────────────────────── // Скалярные операции — для эргономики `z * 2.0`, `z / 3.0` // ────────────────────────────────────────────────────────────────────────── // D46 разрешает overloading методов по типу аргумента (см. Vector @times): // `z * Complex(...)` и `z * 2.0` — оба валидны через одно имя `@times`. /// Scalar multiplication: `z * f64`. D46 overload by argument type. #stable(since = "0.1") export fn Complex @times(s f64) -> Complex => Complex(re: @re * s, im: @im * s) /// Scalar division: `z / f64`. #stable(since = "0.1") export fn Complex @div(s f64) -> Complex => Complex(re: @re / s, im: @im / s) // ────────────────────────────────────────────────────────────────────────── // Геометрия / тригонометрическая форма // ────────────────────────────────────────────────────────────────────────── /// Complex conjugate: `conj(a+bi) = a-bi`. #stable(since = "0.1") export fn Complex @conj() -> Complex => Complex(re: @re, im: -@im) /// Squared norm: `|z|² = a² + b²`. Cheaper than [`abs`] — for comparing lengths. #stable(since = "0.1") export fn Complex @norm_sqr() -> f64 => @re * @re + @im * @im /// Absolute value (modulus): `|z| = √(a² + b²)`. Via hypot — numerically stable. #stable(since = "0.1") export fn Complex @abs() -> f64 => @re.hypot(@im) /// Argument (polar angle) in radians. Range (-π, π]. atan2(im, re). #stable(since = "0.1") export fn Complex @arg() -> f64 => @im.atan2(@re) /// Square: `z²`. Slightly faster than `z * z` (fused formula). #stable(since = "0.1") export fn Complex @sqr() -> Complex => Complex( re: @re * @re - @im * @im, im: 2.0 * @re * @im, ) /// Multiplicative inverse: `1/z = conj(z) / |z|²`. #stable(since = "0.1") export fn Complex @inv() -> Complex { ro denom = @norm_sqr() Complex(re: @re / denom, im: -@im / denom) } // ────────────────────────────────────────────────────────────────────────── // Предикаты // ────────────────────────────────────────────────────────────────────────── /// True if 0 + 0i. #stable(since = "0.1") export fn Complex @is_zero() => @re == 0.0 && @im == 0.0 /// True if im == 0 (purely real). #stable(since = "0.1") export fn Complex @is_real() => @im == 0.0 /// True if re == 0 && im ≠ 0 (purely imaginary, not zero). #stable(since = "0.1") export fn Complex @is_imag() => @re == 0.0 && @im != 0.0 /// True if both parts are finite (not NaN, not ±inf). #stable(since = "0.1") export fn Complex @is_finite() => @re.is_finite() && @im.is_finite() /// True if at least one part is NaN. #stable(since = "0.1") export fn Complex @is_nan() => @re.is_nan() || @im.is_nan() // ────────────────────────────────────────────────────────────────────────── // Сравнение (D46) // ────────────────────────────────────────────────────────────────────────── /// Strict equality (`==`) — exact bit-equality of both components. For FP prefer [`approx_eq`]. #stable(since = "0.1") export fn Complex @equal(other Complex) -> bool => @re == other.re && @im == other.im /// Approximate equality: `|z - other| < tol`. Use for FP comparisons. /// /// # Examples /// ```nova /// let a = Complex(1.0, 0.0) /// let b = Complex(1.0 + 1e-10, 0.0) /// assert(a.approx_eq(b, 1e-9)) /// ``` #stable(since = "0.1") export fn Complex @approx_eq(other Complex, tol f64) -> bool { ro diff = @minus(other) diff.abs() < tol } // Линейного порядка `<`/`>` у комплексных нет — намеренно не реализуем. // ────────────────────────────────────────────────────────────────────────── // Конверсия в строку (D410) // ────────────────────────────────────────────────────────────────────────── // `@to_str()` — представление в строку (D410). Интерполяция `"${z}"` // использует его же через компиляторный fallback-путь. // /// `to_str()` — the format `a+bi` / `a-bi` / `a` / `bi` / `0.0`. /// Round-trips with `s.to_complex()`. #stable(since = "0.1") export fn Complex @to_str() -> str { if @im == 0.0 { "${@re}" } else if @re == 0.0 { "${@im}i" } else if @im < 0.0 { "${@re}${@im}i" // отрицательный im уже идёт со знаком "-" } else { "${@re}+${@im}i" } } // ────────────────────────────────────────────────────────────────────────── // Парсинг из строки — обратная к `@to_str() -> str` // ────────────────────────────────────────────────────────────────────────── // `s.to_complex()` — обратная к `to_str()`. Round-trip property: // z.to_str().to_complex()!! ≈ z // // Принимает форматы из @to_str(): // "3.0" → re=3.0, im=0 // "2.0i" → re=0, im=2.0 // "1.0+2.0i" → re=1.0, im=2.0 // "1.0-2.0i" → re=1.0, im=-2.0 // "0.0" → re=0, im=0 // // Возвращает Err(ParseComplexError) на невалидной строке (Result-форма, D325). /// Errors when parsing a Complex string. #stable(since = "0.1") export type ParseComplexError enum InvalidFormat | NotANumber /// Parse `a+bi` / `bi` / `a` formats. Round-trips with `@to_str`. /// /// Plan 174.1 (2026-07-08, owner canon): the conversion is a method ON THE SOURCE /// (`s.to_complex()`, mirroring `s.to_int()`/`s.to_f64()`); the static /// `Complex.try_from(s)` is RETRACTED (one name = one door, §22). /// /// # Examples /// ```nova /// ro z = "1.0+2.0i".to_complex()!! /// assert(z.real() == 1.0 && z.imag() == 2.0) /// ``` #stable(since = "0.1") export fn str @to_complex() -> Result[Complex, ParseComplexError] { ro trimmed = @trim_ascii() if trimmed == "" { return Err(InvalidFormat) } // Чисто мнимое: заканчивается на 'i', не содержит знаков посередине. if trimmed.ends_with("i") { ro body = trimmed.strip_suffix("i") ?? trimmed // Может быть `2.0i` (полностью мнимое) или `1.0+2.0i` / // `1.0-2.0i` (комбинированное). Различение по наличию знака // не на первой позиции. ro split = split_re_im(body) match split { Some((re_s, im_s)) => { ro re = parse_f64_or_err(re_s)? ro im = parse_f64_or_err(im_s)? Ok(Complex(re, im)) } None => { // Чисто мнимое: `2.0i`, `-3.0i`, `i`, `-i`. ro im = match body { "" => 1.0 // `i` → 1.0 "-" => -1.0 // `-i` → -1.0 _ => parse_f64_or_err(body)? } Ok(Complex(im: im)) } } } else { // Чисто действительное. Ok(Complex(re: parse_f64_or_err(trimmed)?)) } } // Разделить body вида "1.0+2.0" / "1.0-2.0" на (re, im). // Знак на первой позиции относится к re, не разделитель. // None если разделителя нет (значит чисто-мнимое). // `body` — числовой литерал (цифры/`.`/`+`/`-`/`e`/`E`/`i`), пure ASCII — // прямой byte-доступ O(1) вместо ретрактированного `chars().nth(i)` O(n) // скана (D260-амендмент). fn split_re_im(body str) -> Option[(str, str)] { ro bb = body.bytes() mut i = 1 // пропускаем знак re если есть while i < body.byte_len() { ro b = bb[i] if b == ('+' as u8) || b == ('-' as u8) { // Не часть экспоненты `1e-5`? ro prev = bb[i - 1] if prev != ('e' as u8) && prev != ('E' as u8) { ro re_s = body[..i] ro im_s = body[i..] return Some((re_s, im_s)) } } i += 1 } None } fn parse_f64_or_err(s str) -> Result[f64, ParseComplexError] { match s.to_f64() { Ok(x) => Ok(x) Err(_) => Err(NotANumber) } } // ────────────────────────────────────────────────────────────────────────── // Extension-методы на f64 / int — sugar `2.0.j()`, `3.j()` // ────────────────────────────────────────────────────────────────────────── /// Extension: `2.0.j()` → `Complex(im: 2.0)`. Domain sugar /// (physics/electrical engineering, `j` = √(-1)). For purely real use `Complex(re: v)`. #stable(since = "0.1") export fn f64 @j() -> Complex => Complex(im: @) /// Extension: `2.j()` → `Complex(im: 2.0)` (int overload). #stable(since = "0.1") export fn int @j() -> Complex => Complex(im: @ as f64) // ────────────────────────────────────────────────────────────────────────── // Тесты — базовые свойства алгебры и идентичности // ────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────── // str.from / to_str round-trip // ────────────────────────────────────────────────────────────────────────── // Compiler note: проверяем round-trip по одному значению за раз (без