-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Complex numbers #3892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
scimind2460
wants to merge
37
commits into
rust-lang:master
Choose a base branch
from
scimind2460:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+163
−0
Open
Complex numbers #3892
Changes from 4 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
ba634bb
Add complex numbers rfc
scimind2460 0f4d922
Change file name and pr to reflect current issue
scimind2460 863794c
Incorporate feedback into RFC
scimind2460 60465d0
Fix errors
scimind2460 66b4a8e
Remove impls and replace with stubs
scimind2460 e6a51ea
Fix polars
scimind2460 f63d8e6
Add more future possibilities and fix typos
scimind2460 8033da5
Add associated consts and collapse functions
scimind2460 d86a918
Apply suggestions from code review
scimind2460 4add4e1
Add fixes in type signature
scimind2460 5aceb3e
Apply suggestions from code review
scimind2460 94dece5
Clarified some details regarding `i`
scimind2460 0cf423a
Fix wording in complex numbers documentation
scimind2460 512a3be
Add possible workaround
scimind2460 ca8f9ba
Update rationale for Complex type in FFI context
scimind2460 23f1e67
Update drawbacks section for Complex type proposal
scimind2460 c055e1a
Improve clarity and rationale in complex numbers documentation
scimind2460 e438f35
Apply suggestions from code review
scimind2460 d2c671a
Update RFC on complex numbers support
scimind2460 3911071
Apply suggestions from code review
scimind2460 73f823f
Enhance rationale and examples for complex numbers API
scimind2460 e69a67f
Add suggestion
scimind2460 d1e41cb
Remove `this`
scimind2460 a1774ac
Refactor Complex struct and update methods
scimind2460 6851400
Apply suggestions from code review
scimind2460 d7429f8
Clarify reason for intrinsic calls
scimind2460 be1092e
Apply suggestion from @programmerjake
joshtriplett b28187b
Remove polar from the main body of the RFC
joshtriplett be027bb
Apply suggestions from code review
scimind2460 b376ab9
Update 3892-complex-numbers.md
scimind2460 410b497
Apply suggestions from code review
scimind2460 11a2bfa
Refine complex numbers documentation and remove redundancy
scimind2460 a99270d
Update text/3892-complex-numbers.md
scimind2460 5480bd4
Revise alternatives section for complex numbers
scimind2460 9e3bacd
Enhance discussion on complex number alternatives
scimind2460 d773668
Expand alternatives section for complex numbers
scimind2460 7db4d02
Expand notes on complex number implementation
scimind2460 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,332 @@ | ||
| - Feature Name: complex-numbers | ||
| - Start Date: 2025-12-02 | ||
| - RFC PR: [rust-lang/rfcs#3892](https://github.com/rust-lang/rfcs/pull/3892) | ||
| - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
|
||
| ## Summary | ||
| [summary]: #summary | ||
|
|
||
| FFI-compatible and calling-convention-compatible complex types are to be introduced into `core` to ensure synchronity with C primitives. | ||
|
|
||
| ## Motivation | ||
| [motivation]: #motivation | ||
|
|
||
| The definition of complex numbers in the C99 standard defines the _memory layout_ of a complex number but not its _calling convention_. | ||
| This makes crates like `num-complex` untenable for calling C FFI functions containing complex numbers without at least a level of indirection (`*const Complex`) or the like. | ||
| Only in `std` is it possible to make an additional repr to match the calling convention that C uses across FFI boundaries. | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| In essence, this RFC makes code like this: | ||
scimind2460 marked this conversation as resolved.
Show resolved
Hide resolved
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ```C | ||
| extern double _Complex computes_function(double _Complex x); | ||
| ``` | ||
| callable in Rust without indirection: | ||
| ```rust | ||
| extern "C" { | ||
| fn computes_function(x: Complex<f64>) -> Complex<f64>; | ||
| } | ||
| fn main() { | ||
| let returned_value = computes_function(Complex<f64>::new(3, 4)) | ||
joshtriplett marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| ``` | ||
| using the standard library's FFI-compatible complex numbers. | ||
|
|
||
| ## Guide-level explanation | ||
| [guide-level-explanation]: #guide-level-explanation | ||
|
|
||
| `Complex<T>` numbers can be instantiated as of any type using `Complex::new(re, im)` where `re` and `im` are of the same type (this includes all numbers). | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ```rust | ||
| let x = Complex::new(3.0, 4.0); // this instantiates them as integers, not floats! | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
| They can even be passed as an array: | ||
| ```rust | ||
| let y = Complex::from([3.0, 4.0]); | ||
| ``` | ||
| or as a tuple: | ||
| ```rust | ||
| let z = Complex::from((3.0, 4.0)); | ||
| ``` | ||
| They can even be passed in polar form (but only as a float): | ||
| ```rust | ||
| let polar = Complex::from_polar(3.0, f32::PI/2.0); | ||
| ``` | ||
joshtriplett marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| where .i() turns a real number into a complex one transposing the real value to a complex value. | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| They are added and multiplied as complexes are: | ||
| ```rust | ||
| let first = Complex::new(1.0, 2.0); | ||
| let second = Complex::new(3.0, 4.0); | ||
| let added = first + second; // 4 + 6.i() | ||
| let multiplied = first * second; // -4 + 10.i() | ||
| ``` | ||
|
|
||
| They can be divided using normal floating-point division | ||
| ```rust | ||
| let float_first = Complex::new(1.0, 2.0); | ||
| let float_second = Complex::new(3.0, 4.0); | ||
| let divided = float_second / float_first; // 2.4 - 0.2.i() | ||
| ``` | ||
|
|
||
| If the values are floating point, you can even calculate the complex sine, cosine and more: | ||
| ```rust | ||
| let val = Complex::new(3.0, 4.0); | ||
| let sine_cmplx = csin(val); // 3.8537380379 - 27.016813258i | ||
| ``` | ||
| It's not too much of a problem to print them: | ||
| ```rust | ||
| println!("{}", Complex::new(1.0, 2.0)); // prints 1 + 2i | ||
| ``` | ||
| If you want to call certain C libraries with complex numbers, you use this type: | ||
| ```C | ||
| // in the C library | ||
| extern double _Complex computes_function(double _Complex x); | ||
| ``` | ||
| ```rust | ||
| // in YOUR Rust code | ||
| extern "C" { | ||
| fn computes_function(x: Complex<f64>) -> Complex<f64>; | ||
| } | ||
| fn main() { | ||
| let returned_value = computes_function(Complex::<f64>::new(3, 4)) | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| ``` | ||
|
|
||
| ## Reference-level explanation | ||
| [reference-level-explanation]: #reference-level-explanation | ||
|
|
||
| Complex numbers will be implemented by using traits in the `core` crate: | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
| trait Float: Copy + Clone {} | ||
| impl Float for f32 {} | ||
| impl Float for f64 {} | ||
| ``` | ||
| Calls to some `libgcc` functions will also be needed: | ||
| ```rust | ||
| #[link(name="libgcc")] | ||
| unsafe extern "C" { | ||
| fn mulsc3(a: f32, b: f32, c: f32, d: f32); | ||
| fn divsc3(a: f32, b: f32, c: f32, d: f32); | ||
| fn muldc3(a: f64, b: f64, c: f64, d: f64); | ||
| fn divdc3(a: f64, b: f64, c: f64, d: f64); | ||
| } | ||
| ``` | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| to properly classify all types complex numbers can be implemented on. | ||
| They will have an internal representation of a Tx2 array: | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ```rust | ||
| // in core::complex | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #[lang = "complex"] // For matching the calling convention (special repr needed?) | ||
| #[derive(Copy, Clone, PartialEq, Debug)] | ||
| pub struct Complex<T: Float>([T; 2]); | ||
| ``` | ||
| have construction methods and `From` impls: | ||
| ```rust | ||
| impl Complex<T> { | ||
| fn new(re: T, im: T) { | ||
| Complex([re, im]) | ||
| } | ||
| } | ||
|
|
||
| impl<T: Float> From<(T, T)> for Complex<T> { | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| fn from(value: (T, T)) { | ||
| Complex(value.0, value.1) | ||
| } | ||
| } | ||
| impl<T: Float> From<(T, T)> for Complex<T> { | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| fn from(value: [T; 2]) { | ||
| Complex(value) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| have methods to calculate their real and imaginary part (`.re()` and `.im()`): | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ```rust | ||
| impl<T: Float> Complex<T> { | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| fn re(self) { | ||
| self.0[0] | ||
| } | ||
| fn im(self) { | ||
| self.0[1] | ||
| } | ||
| } | ||
| ``` | ||
| polar conversions: | ||
| ```rust | ||
| impl<T: Float + Mul + Add> Complex<T> { | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| fn modulus(self) { | ||
| (self.0 * self.0) + (other.0 * other.0) | ||
| } | ||
| } | ||
|
|
||
| impl Complex<f32> { | ||
| fn angle(self) { | ||
| f32::atan2(self.re(), self.im()) | ||
| } | ||
| fn from_polar(modulus: f32, angle: f32) -> Complex<f32> { | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Complex::new(modulus * f32::cos(angle), modulus * f32::sin(angle)) | ||
| } | ||
| } | ||
|
|
||
| impl Complex<f64> { | ||
| fn angle(self) { | ||
| f32::atan2(self.re(), self.im()) | ||
| } | ||
| fn from_polar(modulus: f32, angle: f32) -> Complex<f32> { | ||
| Complex::new(modulus * f32::cos(angle), modulus * f32::sin(angle)) | ||
| } | ||
| } | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
| and have arithmetic implementations similar to this: | ||
| ```rust | ||
| impl<T: Add + Float> Add for Complex<T> { | ||
| fn add(self, other: Self) { | ||
| Complex::new(self.0.re() + other.0.re(), self.0.im() + other.0.im()) | ||
| } | ||
| } | ||
| impl<T: Add + Float> Add<T> for Complex<T> { | ||
| fn add(self, other: T) { | ||
| self + Complex::new(other, 0) | ||
| } | ||
| } | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| impl<T: Add + Float> Add<Complex<T>> for T { | ||
| fn add(self, other: Complex<Self>) { | ||
| Complex::new(self, 0) + other | ||
| } | ||
| } | ||
| impl<T: Sub + Float> Sub for Complex<T> { | ||
| fn sub(self, other: Self) { | ||
| Complex::new(self.0.re() - other.0.re(), self.0.im() - other.0.im()) | ||
| } | ||
| } | ||
| impl<T: Sub + Float> Sub<T> for Complex<T> { | ||
| fn sub(self, other: T) { | ||
| self - Complex::new(other, 0) | ||
| } | ||
| } | ||
| impl<T: Sub + Float> Sub<Complex<T>> for T { | ||
| fn sub(self, other: Complex<Self>) { | ||
| Complex::new(self, 0) - other | ||
| } | ||
| } | ||
| impl Mul for Complex<f32> { | ||
| fn mul(self, other: Self) { | ||
| __mulsc3(self.re(), self.im(), other.re(), other.im()) | ||
| } | ||
| } | ||
| impl Mul for Complex<f64> { | ||
| fn mul(self, other: Self) { | ||
| __muldc3(self.re(), self.im(), other.re(), other.im()) | ||
| } | ||
| } | ||
| impl Mul<f32> for Complex<f32> { | ||
| fn mul(self, other: T) { | ||
| self * Complex::new(other, 0); | ||
| } | ||
| } | ||
| impl Mul<f64> for Complex<f64> { | ||
| fn mul(self, other: T) { | ||
| self * Complex::new(other, 0); | ||
| } | ||
| } | ||
| impl Mul<Complex<f32>> for f32 { | ||
| fn mul(self, other: Complex<Self>) { | ||
| Complex::new(self, 0) * other | ||
| } | ||
| } | ||
| impl Mul<Complex<f64>> for f64 { | ||
| fn mul(self, other: Complex<Self>) { | ||
| Complex::new(self, 0) * other | ||
| } | ||
| } | ||
| impl Div for Complex<f32> { | ||
| fn Div(self, other: Self) { | ||
| __divsc3(self.re(), self.im(), other.re(), other.im()) | ||
| } | ||
| } | ||
| impl Div for Complex<f64> { | ||
| fn Div(self, other: Self) { | ||
| __divdc3(self.re(), self.im(), other.re(), other.im()) | ||
| } | ||
| } | ||
| impl Div<f32> for Complex<f32> { | ||
| fn div(self, other: T) { | ||
| self / Complex::new(other, 0); | ||
| } | ||
| } | ||
| impl Div<f64> for Complex<f64> { | ||
| fn div(self, other: T) { | ||
| self / Complex::new(other, 0); | ||
| } | ||
| } | ||
| impl Div<Complex<f32>> for f32 { | ||
| fn div(self, other: Complex<Self>) { | ||
| Complex::new(self, 0) / other | ||
| } | ||
| } | ||
| impl Div<Complex<f64>> for f64 { | ||
| fn div(self, other: Complex<Self>) { | ||
| Complex::new(self, 0) / other | ||
| } | ||
| } | ||
| ``` | ||
| The floating point numbers shall have sine and cosine and tangent functions, their inverses, their hyperbolic variants, and their inverses defined as per the C standard and with Infinity and Nan values defined as per the C standard. | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ## Drawbacks | ||
| [drawbacks]: #drawbacks | ||
scimind2460 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| If there is suddenly a standard-library Complex type, people may rush to include it in their current implementations, which would leave people behind if they didn't know about it. I really don't think this is a drawback though, since similar things have happened in Rust before: the inclusion of `OnceCell` in Rust, for example. | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Also, the multiple emitted calls to `libgcc.so` (`__mulsc3` and the like) may cause a bit of overhead and may not be what the Rust lang team and compiler team want. | ||
|
|
||
| ## Rationale and alternatives | ||
scimind2460 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| [rationale-and-alternatives]: #rationale-and-alternatives | ||
|
|
||
| The rationale for this type is mostly FFI: C libraries that may be linked from Rust code currently cannot provide functions with direct struct implementations of Complex - they must be hidden under at least a layer of indirection. However, it is not always possible to write a C complex-valued function that wraps the first function in a pointer. Thus, FFI becomes a problem if such complex-valued functions are passed by value and not by reference. | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### Alternatives: | ||
| - Don't do this: There are, obviously, millions of alternatives on crates.io, the foremost being `num-complex`. However, I believe that if we wish to support proper FFI with C, then a standard type that matches calling conventions with C complex numbers is an important feature of the language. Hence, I do not recommend this idea. | ||
| - Use a polar layout: Polar complex numbers, are undoubtedly a more optimal solution for multiplying complexes. However, I believe that if we wish to have proper FFI with C, then complex number layout should be chosen in accordance with the layout that is used in the C standard, and that is the orthogonal layout. This is also the layout used by most of other languages and crates on crates.io. | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - Non-generic primitive types: These are, obviously, the most obvious and practical solution. However, if we implemented lots of such types, then we would not be able to expand for `f16` and `f128` support without repeating the code already implemented. It would be extremely repetitive and tedious to add new types, especially since Gaussian integers and other floating points could have added support. | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Prior art | ||
| [prior-art]: #prior-art | ||
|
|
||
| FORTRAN, C, C++, Go, Perl and Python all have complex types implemented in the standard library or as a primitive. This clearly appears to be an important feature many languages have. | ||
scimind2460 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| For example, in Python: | ||
| ```py | ||
| complex_num = 1 + 2j | ||
| complex_second = 3 + 4j | ||
| print(complex_num * complex_second) | ||
| ``` | ||
| or in C: | ||
| ```c | ||
| float _Complex cmplx = 1 + 2*I; | ||
| float _Complex two_cmplx = 3 + 4*I; | ||
| printf("%.1f%+.1fi\n", creal(cmplx * two_cmplx), cimag(cmplx * two_cmplx) | ||
| Even in Rust, it has been discussed two times in IRLO: | ||
| - [First discussion](https://internals.rust-lang.org/t/c-compatible-complex-types-using-traits/13757) | ||
| - [Second discussion](https://internals.rust-lang.org/t/standard-complex-number-in-std-library/23748) | ||
scimind2460 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Many crates, like `num-complex` also provide this feature, though it is not FFI-safe. | ||
| ## Unresolved questions | ||
| [unresolved-questions]: #unresolved-questions | ||
|
|
||
| Should this type be in `core::ffi`? This type's purpose is mostly FFI, but it might be useful in library contexts as well, so I am not sure if we should place it in `core::ffi`. | ||
|
|
||
| ## Future possibilities | ||
| [future-possibilities]: #future-possibilities | ||
|
|
||
| - Maybe later on, we can think of adding a special custom suffix for complex numbers (`1+2j` for example), and using that as a simpler way of writing complex numbers if this RFC is accepted? This is very similar to how most languages implement complex numbers? Or perhaps we could consider a constant: | ||
| ```rust | ||
| const I: T = Complex::new(T::zero(), T::one()); | ||
scimind2460 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
| where `zero` and `one` is implemented on a trait similar to `num_traits`? | ||
| Or maybe we could have a method on normal numbers: | ||
| ```rust | ||
| // for example | ||
| impl f32 { | ||
| fn i(self) -> Complex<f32> { | ||
| Complex::new(0, self) | ||
| } | ||
| } | ||
| ``` | ||
| that could help simplify the life of people who otherwise would have to keep writing `Complex::new()`? | ||
| - Should we support Imaginary eventually? This RFC doesn't cover it, but I think we can do this later in another RFC. | ||
| - Eventually we may support Gaussian integers (an extension of the real integers) which have a Euclidean division procedure with remainder. We could theoretically eventually support these integers? | ||
|
||
| - We can also support f16 and f128 once methods for them are stabilised. | ||
| - We should also consider adding aliases (like c32 and c64) for floating points once they are established, to allow for a shorthand syntax. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.