Lexical elements: Imaginary literals

January 20, 2023

I understand in principle what imaginary numbers are. I used them during high school and university mathematics classes to get good grades.

I’ve never had a practical use for them.

I’ve certainly never used them in programming, in Go or otherwise.

All that said, I guess it’s cool that Go has first-class support for imaginary numbers, rather than it being an add-on library of some kind.

If you’re like me, and have never had the need to write software that understands complex numbers, you might skip today’s email, or better yet, jump over to 3Blue1Brown’s video that explains what complex and imaginary numbers are, and why they’re useful.

Imaginary literals

An imaginary literal represents the imaginary part of a complex constant.

What’s important to understand from this opening sentence is that imaginary literals are not meant to be used alone. They’re meant to be used in creating complex values or constants. So while you could pass around an imaginary value in your code willy nilly, it won’t generally do you any good without a more complete understanding of complex number theory, and how Go interacts with complex numbers, the latter of course, which we’ll get to in time.

It consists of an integer or floating-point literal followed by the lowercase letter i. The value of an imaginary literal is the value of the respective integer or floating-point literal multiplied by the imaginary unit i.

imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .

For backward compatibility, an imaginary literal’s integer part consisting entirely of decimal digits (and possibly underscores) is considered a decimal integer, even if it starts with a leading 0.

This paragraph is really the only “gotcha”. As discussed earlier this week normally 0123 would be interpreted as the Octal value 0o123 (or 83 in decimal), a historical exception is made for imaginary literals, such that a leading 0 is still treated as a decimal value.

0123i         // == 123i for backward-compatibility
0o123i        // == 0o123 * 1i == 83i
0xabci        // == 0xabc * 1i == 2748i
0x1p-2i       // == 0x1p-2 * 1i == 0.25i

Quotes from The Go Programming Language Specification, Version of June 29, 2022

Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content

Numeric type aliases

Numeric Types … byte alias for uint8 rune alias for int32 … To avoid portability issues all numeric types are defined types and thus distinct except byte, which is an alias for uint8, and rune, which is an alias for int32. As with other aliases in Go, this means that the same type simply has two (or perhaps more) identifiers, which are completely interchangeable. This means that byte is not a distinct type, simply backed by a uint8 type.

Architecture-specific Numeric Types

In addition to the architecture-independent numeric types, Go provides three architecture-specific numeric types. Although there are far fewer of them, they often get a lot more usage. Numeric Types … There is also a set of predeclared integer types with implementation-specific sizes: uint either 32 or 64 bits int same size as uint uintptr an unsigned integer large enough to store the uninterpreted bits of a pointer value What does it mean for a type’s size to be architecture-dependent?

Architecture-independent Numeric Types

Go provides several predeclared numeric types. Most of them have a fixed size, regardless of the architecture the program is compiled for or running on. Numeric Types An integer, floating-point, or complex type represents the set of integer, floating-point, or complex values, respectively. They are collectively called numeric types. The predeclared architecture-independent numeric types are: uint8 the set of all unsigned 8-bit integers (0 to 255) uint16 the set of all unsigned 16-bit integers (0 to 65535) uint32 the set of all unsigned 32-bit integers (0 to 4294967295) uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615) int8 the set of all signed 8-bit integers (-128 to 127) int16 the set of all signed 16-bit integers (-32768 to 32767) int32 the set of all signed 32-bit integers (-2147483648 to 2147483647) int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807) float32 the set of all IEEE-754 32-bit floating-point numbers float64 the set of all IEEE-754 64-bit floating-point numbers complex64 the set of all complex numbers with float32 real and imaginary parts complex128 the set of all complex numbers with float64 real and imaginary parts byte alias for uint8 rune alias for int32 The value of an n-bit integer is n bits wide and represented using two’s complement arithmetic.