Complex Numbers

This is technically possible, but not without massive implications for performance that would be quite undesirable.

Sketch for the curious: you would need some runtime support for nan-boxing, and then have the squareRoot operation return a nan-boxed pointer to a newly allocated complex object for negative inputs. This works (again, at large performance cost) for Double and wider types. It doesn't work so well for Float or narrower types, because you don't have enough room to store your pointers.

3 Likes

I think that if you look at Matlab, Julia and probably R and others, the reason the have the market on scientists completely cornered is that they have support for complex numbers and matrices built in at the ground level. Useful libraries can be created and shared because they share the common data structures for complex numbers and matrices. If matrices and complex number get left outside the standard library, it'd be impossible to get an ecosystem to flourish.

Your post is great Steve, thanks! One random point though, overloading on return type is generally not a good idea for this, because it makes things like this ambiguous:

let x = y.squareRoot() // Float or Complex ??

and makes the error message situation in nested expressions even more confusing :)

Yup, I was hinting around that with “not everyone is fond of ...” but it’s a real concern and thanks for making it explicit.

I think there’s some options for mitigating this particular issue, but it’s definitely not an API choice I would make lightly.

Very helpful post, thanks Steve.

I want to unpack this idea a bit more:

I could imagine a Complex number system that distinguishes between a Real type (with no imaginary component) and an Imaginary type (with no real component). Taking the square root of a Real number could result in either a purely Real type or a purely Imaginary type. You'd never end up a fully Complex number that includes both Real and Imaginary.

So, for 99% of the existing code, the return of .squareRoot would work exactly as expected---it would just be returning a Real type.

From a programmers perspective, the programmer is either 1) already certain they're feeding the square root function a positive number or 2) checking that they didn't get NaN out the other side. But, if the programmer can reason about this, why can't the compiler?

To be more specific, if the programmer is already checking to see if a>0 or taking abs(a) before taking the square root, then we can create a system that promises you'll get a Real type back.

The advantage to this is that if the programmer is not checking a>0 or taking abs(a), then it very well may be a bug---and it'd be really helpful if the return type is ambiguous.

So, do I understand this correctly in that you want to, for example, distinguish between something like multiplying x by itself n times, and taking the power of x with n?

I'll admit, it's always bothered me that pow(x,2) does something different than x*x, and I often use the latter.

How would you want to implement this is practice?

Do I mean UnsignedInteger? Or am I just completely not understanding. Can you expand on this?

The entire point of NaN is that the programmer does not need to check if a > 0 or take abs(a) before computing the square root. It allows them to do a long series of computations, and at the end check if anything went wrong with a simple result.isNaN, rather than needing to put precondition checks on every single operation.

I should follow-up by noting that your suggested system is implementable as you suggest, without any changes to the FloatingPoint protocols or concrete types, by defining something like:

struct Complex<T> {
  var real: Optional<T>
  var imag: Optional<T>
}

and treating a value as real if .imag == nil and imaginary if .real == nil. This is space-inefficient, but would allow you to implement the semantics that you describe.

1 Like

BinaryInteger mostly represents implementation details of computational integers, rather than mapping to any specific mathematical type. UnsignedInteger is closer to what you mean (depending on whether or not you include 0 in the natural numbers, which is a whole different can of worms).

But if that's true, then how is that different (in practice) than simply checking result.isImaginary?

I’d appreciate if we could tone down the hyperbole.

The idea behind what have been called “non-standard libraries” is that they would be distributed with the language, much as Foundation is today. You won’t have to install them separately, and you can always count on them being available. You’ll just write, for instance, “import Math” at the top of a file.

Of *course* zero is a natural number, I cannot *believe* we are even discussing this! ;-)

1 Like

In order to interoperate with existing libraries in other languages, any successful Complex or Matrix library in Swift will necessarily need to be able to interface with raw buffers of the underlying real type, which conveniently also allows them to interoperate with each other.

In the medium-to-long term, better solutions are possible, but in the short term, this works.

Great. An ISO 80000-2 fanatic. Just great. =)

1 Like

Yes, that's exactly what I'm thinking of. I currently work with many of these existing libraries, and am thinking about how to make Swift work with them better.

I apologize for meta-posting but:

I think you might need to be careful with this kind of wording. These languages are built with special emphasis on the domains they're used in. Swift really has no goal in trying to dethrone these, or really any language, except maybe Objective-C. What Swift has focused on is tackling hard problems in software engineering and to try and make these tasks simpler.

For example @Chris_Lattner3's "deferred" massive Swift first-class concurrency proposal. Concurrency is one of the hardest things in CS to do well, and there are a few languages out there that try and tackle this issue with first-class (or near-first-class) support. If you read that proposal, if you can find the link, you'll see that proposal draws on the experience from other languages, and incorporates them into the design. In this case Swift concurrency would need to reside inside of the core of the language to be as fluent as is concurrency is frequently used.


So instead of a drawing conclusions about whether or not an ecosystem will die because of something residing outside of the stdlib and syntax of a language, I'd recommend doing some research to see how a sample of languages tackle this issue, and how they go about doing it.

If it turns out that most languages tackle this as library shipped outside of the standard lib, then my opinion is that an official statement on the non-standard curated libraries needs to be made before we proceed too far on this. Because my guess is that people would want these new libs to have more than one type in them. Which would mean we'd probably tie this up with more math goodies. And that would probably mean a good portion of a Swift major cycle would be fleshing out these libs for a first release.


tl;dr: Can you post a list of what other languages do so that we can better understand if this new functionality should be in the stdlib?

This is problematic for matrices and vectors because for those things we really want the compiler to know about them and be able to optimize with simd/sse/whatever. This means they would have to be implemented in the standard library since they could not be compiled like ordinary Swift code.

1 Like

I appreciate the meta-post.

Is there any particular reason that Swift shouldn't try to compete with Matlab or Julia? Or, if not compete, at least overlap in their domains? From my perspective, Swift only needs a few minor changes---it's not far off. Personally, I don't see complex numbers or matrices as big of a problem as concurrency, and that's why I think this can be tackled relatively quickly.

Matlab and Julia are the two languages I'm most familiar with (outside of C/Objective-C):

Julia includes both Complex numbers and Multi-dimensional Arrays as "first-class implementations". And you can see that external packages get built around those pieces of functionality.

Matlab also includes both complex numbers and multidimensional arrays. The community has built several external packages that use those.

Here's a list of Swift projects that include definitions of matrices or tensors:

I've studied some of these carefully, others barely at all. Several of those also include rudimentary definitions of complex numbers, and there are several other separate complex number projects.

1 Like

IMO I don’t think Swift could or should try and dethrone any language. Swift should focus on making Swift good for general purpose programming, and maybe part of that is including things like complex numbers and other things. But explicitly setting out to replace a very domain specific language like R or matlab is setting up for failure because Swift’s goal is not to be another one of them.

That being said I don’t think complex numbers themselves are outright wrong for the standard lib. I just haven’t seen enough arguments that they really should be there.

If it turns out that the hypothetical non-standard curated libs are ruled out forever from Swift. One I will be very sad, but more importantly I think the argument for stdlib inclusion is much stronger in that case.

1 Like

Just a quick aside question:

What do you think of the idea of the ability to define a default or some other way of breaking ambiguity on the definition side in certain cases?

For example, maybe you could define the complex version of the function as "weak" or something similar, and so the Float definition would "win" when they are considered together. You could still explicitly use the Complex version, but when the type isn't specified, Complex wouldn't be considered unless it is the only option that fits.

FWIW, I've discussed this a tiny bit with @Douglas_Gregor in the past, and his take at the time was basically "overload resolution is already too complex".

2 Likes

We could use BLAS for that. There's a WIP Swift library that is cross platform that wraps BLAS :slight_smile: