golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.5k stars 17.6k forks source link

proposal: spec: remove complex numbers #19921

Closed bradfitz closed 5 years ago

bradfitz commented 7 years ago

Go supports complex numbers, but ~nobody uses them.

The only thing complex numbers add to Go is their complexity to Go's runtime, compiler, and standard library.

I propose we remove them from Go 2.

bradfitz commented 7 years ago

Note: https://golang.org/wiki/NoMeToo

cznic commented 7 years ago

Is it still April 1st in your universe timezone @bradfitz ?

mvdan commented 7 years ago

@cznic if you are against this proposal, it would help to illustrate why. I've never used complex numbers in Go, for example - likely the only part of the spec I skipped on purpose.

cznic commented 7 years ago

@mvdan After I've got fooled just a few days ago by the quaternions, my question was just an attempt to figure out if it's a joke or not.

Nonetheless, according to the Go 2016 Survey, 11% of respondents work in the "Academic/Scientific/Numeric" area (multiple choices allowed).

mvdan commented 7 years ago

I'm fairly sure this is not a joke. After all, it's about making the language simpler, not more complex ;)

Note that the "Academic/Scientific/Numeric" category might not be much related to the use of complex numbers. Perhaps better support for multi-dimensional slices (there is an issue for that already I think) would be more important to them. A study on that demographic of users could be useful.

cznic commented 7 years ago

After all, it's about making the language simpler, not more complex ;)

Removing strings from the language would make the language simpler as well, but not simpler to use. (Don't let the absurdity miss you the point).

I don't believe the goal of making the language simpler is the right goal. Simple to use while keeping the language simple it is, IMO.

dgryski commented 7 years ago

I remember grepping a large corpus of Go code for complex numbers, and aside from the standard library and a few serialization packages aiming for completeness, the only users of I found were a few people who had implemented the Mandelbrot set.

mremond commented 7 years ago

Could those features be moved to a third-party library or do they have to be implemented inside Go runtime ?

ALTree commented 7 years ago

The fact that nobody uses complex numbers in go1 is not that surprising, since go1 is objectively not that great as a language for scientific computing (we don't even have multidimensional slices). But this is a go2 issue. What if an hypothetical go2 turns out to be a great language for scientific programmers? In that case you would probably want to leave complex numbers in.

What I'm saying is that basing the decision of leaving out complex numbers from go2 on analyses made on a go1 code corpus makes little sense.

griesemer commented 7 years ago

I appreciate the sentiment (of wanting to remove complex) and have personal sympathy, but I think this ties in with other issues that all need to be considered together: 1) complex numbers, while requiring quite a bit of implementation support, and despite their name, don't really add much complexity to the language: if you don't use them you won't notice them. 2) I believe that @ALTree is correct: if Go were more directly suited for numeric applications (e.g. linear algebra), complex numbers would be showing up much more often. 3) If we remove them, I think we need a way to user-define them in a natural way, which probably means some form of operator methods.

(As an aside, Mandelbrot iteration is much (2x) faster when implemented using floats because significant parts of the complex computation can be factored out. Implementing Mandelbrot using complex is like implementing Factorial using recursion...)

dongweigogo commented 7 years ago

think about python. Guido never thought python comes this far in scientific computing fields today. Just don't close the door, but throwing it to the std from the language level is acceptable.

bradfitz commented 7 years ago

@griesemer, I agree this needs to be considered alongside other issues (operator methods, etc). I'm just throwing it on the pile. It's a funny situation we're in now where *big.Ints are relatively common but awkward to use while unused complex numbers are first class.

griesemer commented 7 years ago

@bradfitz good point!

maj-o commented 7 years ago

I love Go for its simplicitiy! I don't realy know if more people need complex numbers or other numbers. Though if You google it, many people ask for decimals and linear algebra (vectors, multidim slices,..). But I know that it would not realy matter, if anybody could do with his/her numbers what is natural and common to be done with numbers. Like the scientists, traders, developers, .. of tomorrow, our children, would do it - Will they use Go, if it is impossible to use it this way? operator methods are endless more worth then complex numbers.

as commented 7 years ago

Go supports complex numbers, but ~nobody uses them.

What percentage of the current userbase is ~nobody?

urandom commented 7 years ago

The issue seems to boil down to not whether complex numbers are used (a lot), or the current difficulty in representing different math types that aren't built in. I agree that allowing custom types to define their own behavior for existing operators would be a lot more useful. And it would generally simplify the language, since complex types (+ the builtin funcs for them) can be removed from the runtime, and implemented in a third party lib.

pierrre commented 7 years ago

I use them: https://github.com/pierrre/mandelbrot

griesemer commented 7 years ago

@pierrre FWIW, using complex operations for Mandelbrot tends to be much slower than then doing it "by hand" using floats because some of the distance computation can be used later in the product (https://github.com/golang/go/issues/19921#issuecomment-293300029).

pierrre commented 7 years ago

@griesemer thanks

kortschak commented 7 years ago

The fact that nobody uses complex numbers in go1 is not that surprising, since go1 is objectively not that great as a language for scientific computing (we don't even have multidimensional slices). But this is a go2 issue. What if an hypothetical go2 turns out to be a great language for scientific programmers? In that case you would probably want to leave complex numbers in.

The argument that Go1 is objectively a "not that great" language for scientific computing is not supported with evidence. Removing complex float support from Go2 would certainly help aid an argument in that direction for that language though.

FWIW we get requests for addition of native complex support for the gonum/blas packages - we have not done this purely because of developer time availability and we do provide a cgo interface to a BLAS implementation.

btracey commented 7 years ago

Complex numbers are really nice to have for linear algebra. They occur in basic matrix decompositions, namely eigenvalue decomposition, and it's really nice to be able to implement a complex matrix using a []complex128. Another very common case arises in signal processing with Fourier transforms. There are many useful number systems, in particular I have worked with quaternions and (hyper-)dual numbers in the past. Complex numbers occur far more often and in a much wider set of disciplines. There is good reason to include complex and not the others.

I can understand the argument that big.Int is hard to use while complex numbers are first class. Part of the goal of https://github.com/golang/go/issues/19623 is to make the situation with big ints better. There doesn't need to be a "shooting down" of complex numbers at the same time.

Operator methods are possible, but they are a much bigger change to the language, and can have a big influence (for better or worse) on APIs.

rhedile commented 7 years ago

I agree that Complex numbers really help with linear algebra. "Flame Front Propagation" in closed vessels is difficult without Complex numbers. In Fluid Dynamics potential flow in two dimensions works on the Complex Plane. Conformal Mapping can be avoided but it really helps.

On 23 May 2017 at 07:55, Brendan Tracey notifications@github.com wrote:

Complex numbers are really nice to have for linear algebra. They occur in basic matrix decompositions, namely eigenvalue decomposition, and it's really nice to be able to implement a complex matrix using a []complex128. Another very common case arises in signal processing with Fourier transforms. There are many useful number systems, in particular I have worked with quaternions and (hyper-)dual numbers in the past. Complex numbers occur far more often and in a much wider set of disciplines. There is good reason to include complex and not the others.

I can understand the argument that big.Int is hard to use while complex numbers are first class. Part of the goal of #19623 https://github.com/golang/go/issues/19623 is to make the situation with big ints better. There doesn't need to be a "shooting down" of complex numbers at the same time.

Operator methods are possible, but they are a much bigger change to the language, and can have a big influence (for better or worse) on APIs.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/golang/go/issues/19921#issuecomment-303297775, or mute the thread https://github.com/notifications/unsubscribe-auth/AEAf7IebxbBGrL3zZ80uDKpB4n3eesw2ks5r8nTngaJpZM4M5mHZ .

JonathanFraser commented 7 years ago

Please don't, alot of numerical computing depends on it.

bezigon commented 7 years ago

Needs to create a separate project regarding NumPy in Go (NumGo?), if this happens...

btracey commented 7 years ago

www.godoc.org/gonum.org/v1/gonum

maj-o commented 7 years ago

I like this godoc point. Let me repeat myself. Much more computation, data-exchange and storage depends on decimal as on complex numbers. And for this reason we have this: https://godoc.org/?q=decimal - a lot of workarounds. For us (the company I work for), with a lot of decimal computation, this workarounds don't realy help. There are three solutions for this: https://github.com/golang/go/issues/19787 or https://github.com/golang/go/issues/19770 or to use a different language.

Nobody wants to remove complex numbers out of Go completely. But complex numbers don't need to be part of the core.

With operator methods as presented by Mr Griesemer many of this and other problems could be solved at once. And complex numbers could savely be moved to a separete package without braking code (just a different import and this could be automated).

andlabs commented 7 years ago

How would splitting complex numbers out into the library impact performance of code that uses them heavily?

I notice there's a number of standard library functions, particularly in math, that have special SSA rules that mean those functions are handled at compile-time. (Please correct me if I am wrong.) If we remove complex numbers, but they eventually get moved alongside these special cases, would we have a zero net sum for the change? It'd just be swapping syntax for syntax.

And if Go 3 reverses the decision, I hope it doesn't introduce _Complex

emkatz commented 7 years ago

I'm on the way to reimplement some network analysis tool in go - where complex number are really basic to linear networks. It would be awkward if complex number would be dropped and i have to do something less performent or a less KISS thing. I'v first put the proposed functionality to public domain in the early 80s (neviza) http://downloads.atari-home.de/Public_Domain/Serie_CCE/0100/CCE_0112.TXT and so i do not see that the YAGNI thesis of this proposal is reasonable. Please dont drop the needs of engineering calculation in a way that is worse then the todays solution! To my point of view matrix operation would be a candidate to integrate into the language :-) so if there is a lib that is as stable, easy and fast as the the given complex number integration besides enabling matix operation it would be acceptable.

quasilyte commented 7 years ago

This repository can be useful for someone who has doubts that complex numbers can be implemented as a user-defined type without significant performance changes (if any at all). It contains benchmarks and machine code output for amd64.

dmitshur commented 7 years ago

@Quasilyte That's very interesting to see, thanks for sharing.

Just want to ask, but are there any edge cases that your implementation handles differently compared to the builtin complex types, or does it behave identically for all input? It's interesting that some operations are actually faster compared to the builtin type.

Another observation I want to point out (since I haven't seen it mentioned in this thread already) is that the builtin complex types have the following property that a user-defined type could not have (at this time):

If the operands of these functions are all constants, the return value is a constant.

quasilyte commented 7 years ago

Just want to ask, but are there any edge cases that your implementation handles differently compared to the builtin complex types, or does it behave identically for all input?

@shurcooL, as far as I am aware, add/sub/mul/div behave identical. Division implementation taken directly from runtime complex128div.

Complex64.Add and Complex64.Sub use single precision, while multiplication and division is done with doubles. This matches builtin operations (can't find spec description of that, unfortunately).

wsc1 commented 7 years ago

I use them. It would be nice rather to add the ability to update the real and imaginary components individually (eg make real() and imag() addressable) Then many advantages of re-formulation as pairs of floats would disappear and more applications could use them effectively.

wsc1 commented 7 years ago

Well, I'll try to elaborate. First of all, syntactically it's a PITA to reconstruct a complex whenever you want to change only one component.

An example is the complex conjugate. Now I guess the compiler might optimize the unnecessary work of calling cmplx.Conj away, but if performance matters it seems prudent to be explicit about the memory being treated as an update to the imaginary component.

Another example is going from float ops to complex ops, like the FFT. you can either call cmplx.Exp(...) or compute the result in terms of sines and cosines (Eulers identity). Sure, some components can update independently. Another example is the inverse FFT implemented as FFT on a+bi for every b+ai in the input. If your components are addressable, you can pass pointers to such an implementation and just swap them

Hope that helps clarify...

2017-10-06 15:58 GMT+02:00 Iskander Sharipov notifications@github.com:

Hello, @wsc1 https://github.com/wsc1. It is not obvious why addressable complex number components will be useful for you without concrete examples. Can you elaborate, please?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/golang/go/issues/19921#issuecomment-334762274, or mute the thread https://github.com/notifications/unsubscribe-auth/ASR01zLgcRimrge7BqH-zJr1y-Mkw3DBks5spjIYgaJpZM4M5mHZ .

-- Scott Cotton President, IRI France SAS http://www.iri-labs.com

splace commented 6 years ago

my immediate reaction when i first saw they were built-in was; why not a standard library?

that aside, from what i see, not commended on so far;

do they, as built-ins, bloat the runtime?

as built-ins they can be used by built-in functions generically, (complex64 , complex128) this requirement seems to me to be a defining indicator as to if something should, (needs to be ) a built-in, however here this seems to have no value, only issues, with these two being cross-usable. Even thought real(),imag(),complex() accept either, maths operators don't and math/cmplx only supports complex128 anyway.

currently adding to the real part, requires making a new complex and adding that, with performance often required for complex numbers this seems a problem, ideally a float could be regarded as a complex with zero imaginary part, and generically operator on, making built-ins a necessity.

complex numbers, often used with modelling real stuff, so often needing large datasets, (which would ideally mean complex64 support) so high speed is very important, so does being built-in mean ability to support hardware acceleration better? (i personally don’t believe it does)

andlabs commented 6 years ago

Again, I wouldn't be surprised if compiler intrinsics would allow for hardware optimization, especially since math/bits already has its own compiler intrinsics. I do know XMM registers are 128 bits wide, but I'm not sure if the XMM instruction set has arithmetic instructions that operate on both float64 halves at once (or if treating them independently is the correct approach).

I still wonder if the cost (in terms of work) of establishing compiler intrinsics cancel out the benefits of dropping them from the language, if that makes any sense... It shouldn't affect performance much; at least I hope not...

On a semi-related note, how performant are assembly implementations of functions vs. compiler intrinsics?

evincarofautumn commented 6 years ago

I have no stake in this, but as a language designer, I have some guiding questions:

They’ve been around for a few centuries, and they’re taught in high school mathematics—so I’d say they’re pretty fundamental, even if they don’t come up all that often in existing Go (cf. Python).

splace commented 6 years ago

They’ve been around for a few centuries, and they’re taught in high school mathematics—so I’d say they’re pretty fundamental, even if they don’t come up all that often in existing Go (cf. Python).

my understanding is fundamentally they exist for algebraic manipulation, developing mathematical formula, they arn't required for numeric computation. which means no language actually needs them, and explains why they arn't used much, all they do is make it clearer to take a formula using complex numbers and directly translate it into code, particular when using systems of related formula.

PaluMacil commented 6 years ago

I'm mostly a web dev but still would like to see them supported first class. There are a lot of technical arguments here--whether about electrical engineering or about physics--but to me, there is another ignored aspect that developers tend to forget about (though Go does well with this)... marketing the language. If memory serves, the Go survey seems to indicate that more developers are coming from Python than any other language.

Just this last week I was speaking with a neuroscientist leaving Python due to performance concerns (probably not a candidate to use imaginary numbers). While I think more matrix functionality would be fantastic, something that stands out as unique and smooth for data scientists is a great way to get attention of the data science and academic community. Academics especially (from my experience and from seeing slow migration of data libraries from Python 2 to 3) seem to stay with languages a long time once they find a comfortable fit. They need a lot but are possibly more starved of time than a typical web developer. I'm excited to see more academics in the Go ranks, and I think they are some of the best "neighbors" since they dedicate their lives to learning and research. Complex numbers are a way to get the attention of this group in a way other languages don't.

bradfitz commented 6 years ago

Again, this proposal isn't about just nuking them out of spite. I want to rationalize why we have complex numbers as first class in the language yet the much more common *math/big.{Int,Float} are library types requiring method calls for all operations. I'd ideally like to see math/big implemented such that they can be used naturally with normal Go operators (in addition to specialized methods as needed), and then also move complex numbers to a library (into the existing math/cmplx, perhaps)

The compiler could still recognize the complex package and intrinsify cmplx.Num & operations into today's machine instructions, just like the compiler today recognizes math.Sqrt, etc in the standard library.

cznic commented 6 years ago

I want to rationalize why we have complex numbers as first class in the language yet the much more common *math/big.{Int,Float} are library types requiring method calls for all operations.

Operations over complex{64,128} fit CPU into registers, operations on math/big.{Int,Float} generally do not. That's IMHO a sufficient reason why the former are part of the language and the later are not and come in a package.

bradfitz commented 6 years ago

@cznic, that's somewhat of an implementation detail. string and map also don't fit in registers, and they're part of the language. And if we do #19623, it also doesn't fit in a register. But if we do #19623, and also don't do any sort of generics w/ operator methods, then this proposal doesn't matter much and we should keep complex numbers as built-ins. But if we did add generics w/ operator methods, we could pay for some of the extra language complexity from generics by removing complex numbers and making them a library feature instead.

lpar commented 6 years ago

2¢: An order of magnitude more people do (for example) percentage calculations on currency values, than use complex numbers. So if a decimal float type isn't part of Go's stdlib, I have a hard time understanding the justification for a complex number type.

sbinet commented 6 years ago

complex numbers are part of many machine learning pipelines. they're a fundamental building block for FFTs and data science: there's no question they are useful.

that said: if we indeed get some kind of generics + (numeric) operators, then I could see myself using complex numbers without much hassle.

lpar commented 6 years ago

I'm not disputing that they're useful. I'm just pointing out that decimals are a lot more useful to almost everyone, and we don't have those, so apparently the usefulness bar is generally pretty high.

andlabs commented 6 years ago

So if we were to move complex numbers into a library and intrinsify the functions that you can perform on them, what would the performance costs be?

(And I'm still curious about how much more or less complicated the compiler itself will be this way...)

bradfitz commented 6 years ago

So if we were to move complex numbers into a library and intrinsify the functions that you can perform on them, what would the performance costs be?

Zero, ideally. As I said above:

The compiler could still recognize the complex package and intrinsify cmplx.Num & operations into today's machine instruction.

The generated code should be identical as today.

likebike commented 6 years ago

Complex numbers usually result from exponentiation (for example, square roots). If Go doesn't have a built-in exponentiation operator, then it's probably not necessary to have built-in complex numbers. Either we should have both, or neither.

Personally, I'd prefer to have both built-in because I consider them as important, fundamental mathematical operations. I personally use complex numbers often in my engineering Go projects.

mortdeus commented 6 years ago

I think the reason people don't use Go for mathematical applications is that we don't prioritize it. Our math library is essentially a straight across Go port of C's stdlib math.h.

And I think we could steal a lot of scientists who rely on python if we took a more straightforward approach to it.

For example, awhile back I proposed that we add some things to the math package. And I was shot down simply on the basis that there are hundreds and hundreds of mathematical functions out there and that if users really wanted them they can implement them themselves or use a 3rd party library.

I just find it funny that here, it was mentioned that using complex for mandlebrot is just about as silly as using recursion for factorial.

Isn't that the reason why we should implement a factorial function for gophers, so that they don't do it the wrong way?

Wasn't Go designed to make programmer's lives easier and code more efficient? Isn't that the primary reason why we go out of our way to build a standard library for users convenience?

I am just saying that complex doesn't seem to be used as often because the math package is essentially C's math library and why would anybody who is looking for a faster alternative than python ever choose Go when they can just write their performance critical code in C?

I personally think that instead of trying to get rid of complex, lets focus on making the math package the best, so that mathematicians have a reason to choose Go over other languages.

mortdeus commented 6 years ago

And I am also for making things like big numbers, decimals, and 1-bit binary types builtin.

sbinet commented 6 years ago

@mortdeus I personally think it's fair not to put all of netlib or cephes inside the stdlib's math package and instead gather a community around, say, gonum.org (and gonum.org/v1/gonum/mathext).

as one of the maintainers+devs of gonum.org and go-hep.org, I wouldn't mind dropping complex from the language:

I would reach for generics, operator overloading and intrinsics in another language. but this is Go, so I am open to alternatives.

also, I don't think that people use Python because of its math module, but because of numpy, scipy, ... ie all of the scientific ecosystem that wasn't built inside the stdlib (and could thus evolve with less constraints.) The python scientific ecosystem only needed 2 things from python-the-language:

I think, in the context of Go, 1) is out. but 2) should really be implemented.