Complex numbers in Erlang

If we extend the numbers in Erlang I’d rather care more for complex numbers than rationals. Fixed point has some use in financial applications but they often go with integers + a fixed scale i.e. everything is in pico $

3 Likes

I have a module that does rational arithmetic in Erlang,
but it doesn’t have proper documentation, types, or tests
yet, though it has been informally tested. It’s about 170
SLOC.

I don’t have anything similar for complex arithmetic,
although I do have an implementation of Complex for
Smalltalk. There are more than 130 functions defined
on complex numbers and all up about 700 SLOC. The basic
reason is that there are lots of functions we do expect
to work on complex numbers that we do not expect to work
on rationals, like trig and hyperbolic functions. But
also, getting complex multiplication right is harder than
you might think. The naive mathematical definition has
numerical issues. And doing complex division well is
a nightmare, which has attracted decades of work. See
http://www.cs.otago.ac.nz/research/publications/OUCS-2017-07.pdf
for an introduction to some of the issues. glibc, for example,
does not do as well as I’d expected.

All things considered, the surprising difficulty of doing
complex arithmetic is an excellent reason for it being
provided in some fashion. Doing it efficiently requires
embedding at a low level in the language, but if you need
large amounts of complex arithmetic you should probably
delegate that work to C or Fortran or OpenACC or OpenCL…
Erlang being Erlang, a module won’t give you operator
overloading. But it could offer a common way to do
complex arithmetic and get decent answers.

In contrast to rational numbers, there is good reason
to treat complex numbers with zero imaginary part
differently from reals: sqrt(-1) is an error but
sqrt(complex(-1,0)) is complex(0,1).

What functions, other than the basic arithmetic operations,
should such a module provide?

3 Likes

Well I would start with all functions in math but having said that, there are a few missing for real numbers in there too (math doesn’t even provide all functions provided by C)

Plus of course a few useful complex specific ones (planned to collect the features of complex number supporting language since a while but there is only so much time)

1 Like

Complex numbers solve totally different problems IMO, but of course, it would be great to have them too, for example for Elixir’s machine learning.

1 Like

Yeah this sub thread should be split out … now if I only would know how to do this

1 Like

@AstonJ how can one split topics?

1 Like

Done - if you would prefer a different title or section just let us know :023:

Also in case you need it in future, if you click on the time stamp of any post and then on ‘New Topic’ you can create a linked thread :smiley:

1 Like

Thanks!

1 Like

@nzok do you know any example of a language with excellent complex number support?

1 Like

There are languages and there are implementations.
Fortran has had standard support for complex arithmetic for 60 years.
Modern Fortran is a structured modular programming language which
has support for parallelism via OpenMP - and even “accelerators”
like GPUs via OpenACC and cluster-scale via MPI, and more recently
direct built-in language support for parallelism via the coarray
feature. If you need to do a LOT of complex arithmetic, you would
do well to give modern Fortran very serious consideration.

I mentioned glibc’s implementation of complex multiplication, but that’s
not a language problem. It is a limitation-of-modern-hardware problem.
You can have any one of

  • fast
  • as accurate as possible
  • robust

but not really any two of them. Look at the definition.

(a,b) * (c,d) = (ac - bd, ad + bc)
= (DOT((/ a, -b /), (/ c, d /)),
DOT((/ a, b /), (/ d, c /)))

Ideally, we’d like to compute dot([a,b],[c,d]) with

  • one rounding (accurate)

  • no overflow or underflow unless the result really won’t fit (robust).
    Many modern computers have a fused-multiply-add instruction
    that computes a+b*c, or dot([1,a],[b,c]), with these guarantees.
    There was no such instruction on Pentium but AMD and Intel both support
    fma in single and double precision, in some models but not others.
    Like my laptops, sigh.

The last time I looked, glibc used the mathematical formula naively,
which is right for “fast”, has robustness problems, and is vulnerable
to catastrophic cancellation. This is a library implementation issue,
not a language issue, and for many applications, it’s the right
choice.

If there is some special function you need on the complex numbers,
chances are there’s a free library offering it.

Ada 83 didn’t have complex numbers, but Ada 95 added (waves hands
over details) a package for complex types and arithmetic operations
on them and another package with elementary functions (sqrt, exp, log,
**, trig functions and inverses, hyperbolic trig functions and inverses).
Ada introduced a separate Imaginary type:
Real has an imaginary part that is EXACTLY zero semantically,
not an IEEE +/- epsilon signed zero.
Imaginary has a real part that is EXACTLY zero semantically,
not an IEEE +/- epsilon signed zero.
Ada 202x adds nothing and changes little.

C++ had complex arithmetic back when cfront was the only compiler for it.
Eventually, this was copied into standard C, with some trouble taken to
handle IEEE signed zeros. (Which are not something Fortran was designed
to cope with, and it took some time to sort out the details.) C99 allows
an imaginary type (for the same reasons as Ada) but does not require it.
With the exception of cproj, it offers no function that Ada does not.
I find it irritating that C has sqrt and cbrt and csqrt but no ccbrt.

On a typical Unix system, gfortan, gnat (Ada), and gcc will all use the
same compiler back-end and much of the same libraries. Even if you use
clang or the AMD or Intel compilers for C and/or Fortran, they’re still
intended to interoperate, so when it comes to supporting complex numbers
they’ll be much of a muchness.

PL/I had complex back in the 1960s, but who cares about PL/I these days?

Common Lisp has pretty good complex arithmetic support, at least as far
as the language is concerned. It always felt more like a native part
of the language. (In PL/I, nothing felt like a native part of the
language, and yes, I have used it.) Scheme language support is OK, but
complex numbers are optional in Scheme, and implementors tend to have
other priorities.

It may or may not be of interest that the core work on defining branch
cuts for programming language implementations of complex elementary
functions was done for APL. The APL system I use has truly excellent
support for all sorts of numeric types. I just have to run it under a
Windows emulator, and oh well, the manual could use a lot of work.

C# and F# share System.Numeric.Complex, which is pretty yawn-worthy.
Complex being a struct rather than a class, they don’t have to allocate
complex numbers on the heap. They certainly don’t add any interesting
built-in functions, such as the Hankel functions I once had a need for.

I like Smalltalk, and several Smalltalk systems support Complex. But)
some of them are frankly insane (like trying to treat complex numbers
as totally ordered, or worse, defining a total order that isn’t one).
That’s why I had to write my own. (And, here’s a tip: I repeatedly used
Common Lisp for guidance.) I’m happy that my Smalltalk implementation of
Complex does a decent job. For example, Complex atRandom: rng yields a
number randomly and uniformly selected from the unit disc. BUT complex
numbers are always allocated on the heap, so forget about performance.

We can consider three criteria:

  • speed (Erlang will not fare well here)
  • accuracy (fast and accurate are not always compatible)
  • coverage (how many functions are provided).

All of Fortran, C++, C, C#, F#, and Ada offer much the same set of special
functions for complex numbers. Only Ada offers Cot and Coth; only C offers
cproj, but that’s about it.

And THAT is why I asked what functions are actually needed.
How about (log) gamma and (log) beta over complex numbers?
Cylindrical functions? Henry Baker had a nice paper showing
how to define floor, ceiling, round, truncate. All of those?
Complex analogues of C’s modf and ldexp?

\

1 Like