Lexical elements: Floating-point literals
January 18, 2023
Ah, who doesn’t love floating point numbers?
Go gives us two ways to define floating point number literals: in decimal, or in hexidecimal.
I have never been tempted to write a floating point number literal in hexidecimal. I imagine this is mostly used by those interested in precision control of the IEEE 754 values as understood by the floating point implementation. (If this is something you’ve ever used, I’d love to hear from you: What was your context?) The details are of course in the spec, for your perusal, but I’m going to focus on the decimial representation. Also, once you understand the details of the decimal notation, the hex notitation is easy-peasy.
Floating-point literals
A floating-point literal is a decimal or hexadecimal representation of a floating-point constant.
A decimal floating-point literal consists of an integer part (decimal digits), a decimal point, a fractional part (decimal digits), and an exponent part (
e
orE
followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; one of the decimal point or the exponent part may be elided. An exponent value exp scales the mantissa (integer and fractional part) by 10exp.
There’s a fair amount to unpack there. Let’s look at an example (borrowed from the quoted text below) to disect it a bit.

In this image, the integer part (6
) is represented in red, the decimal point (.
) in black, and the fractional part (67428
) in blue.
This part should be familiar to virtually everyone. It’s just a decimal number with a fractional part. And for most purposes, this is sufficient when programming (in Go, or most other languages). Most human-scale numbers (distances traveled, money spent, weight lost, etc) are easily represented as integers or integers with a fractional part.
Of course we don’t only deal with human-scale numbers. Sometimes we have really big numbers, or really small numbers. And this is where that exponential notation comes into play.
The e
character (alternately capitalized as E
), indicates that the remainder of the floating-point literal is the exponent part. And this is interpreted as multiplying the preceeding part by 10 taken to the exponential power. Or in our case:

So this is a really small number. If we were to write it using standard notation it would be 0.0000000000667428
. That’s easy to get wrong, by accidentially repeating or omitting a 0
, for example.
Now that was a complete example. The spec explains that a few bits are optional, too. “One of the integer part or the fractional part may be elided.” This means that these are both valid expressions of floating-point literals:
.67428e-11 6.e-11
Then, "one of the decimal point or the exponent part may be elided". This means our second example above can be simplified, by eliding the decimal place.
>```
> 6e-11
>```
And now for the sure hearted, here's what the spec has to say about hexidecimal floating-point literals:
> A hexadecimal floating-point literal consists of a `0x` or `0X` prefix, an integer part (hexadecimal digits), a radix point, a fractional part (hexadecimal digits), and an exponent part (`p` or `P` followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; the radix point may be elided as well, but the exponent part is required. (This syntax matches the one given in IEEE 754-2008 §5.12.3.) An exponent value exp scales the mantissa (integer and fractional part) by 2<sup>exp</sup>.
>
> For readability, an underscore character \_ may appear after a base prefix or between successive digits; such underscores do not change the literal value.
>
> ```ebnf
> float_lit = decimal_float_lit | hex_float_lit .
>
> decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
> decimal_digits decimal_exponent |
> "." decimal_digits [ decimal_exponent ] .
> decimal_exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .
>
> hex*float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent .
> hex_mantissa = [ "*" ] hex*digits "." [ hex_digits ] |
> [ "*" ] hex_digits |
> "." hex_digits .
> hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
> ```
>
> ```
> 0.
> 72.40
> 072.40 // == 72.40
> 2.71828
> 1.e+0
> 6.67428e-11
> 1E6
> .25
> .12345E+5
> 1_5. // == 15.0
> 0.15e+0_2 // == 15.0
>
> 0x1p-2 // == 0.25
> 0x2.p10 // == 2048.0
> 0x1.Fp+0 // == 1.9375
> 0X.8p-0 // == 0.5
> 0X_1FFFP-16 // == 0.1249847412109375
> 0x15e-2 // == 0x15e - 2 (integer subtraction)
>
> 0x.p1 // invalid: mantissa has no digits
> 1p-2 // invalid: p exponent requires hexadecimal mantissa
> 0x1.5e-2 // invalid: hexadecimal mantissa requires p exponent
> 1_.5 // invalid: _ must separate successive digits
> 1._5 // invalid: _ must separate successive digits
> 1.5_e1 // invalid: _ must separate successive digits
> 1.5e_1 // invalid: _ must separate successive digits
> 1.5e1_ // invalid: _ must separate successive digits
> ```
Quotes from _The Go Programming Language Specification, Version of June 29, 2022_
Related Content

Empty structs
We finally we have enough knowledge for the EBNF format not to seem completely foreign, so let’s jump back and take a look at that, with the examples provided in the spec… Struct types … StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName [ TypeArgs ] . Tag = string_lit . // An empty struct.

Struct tags
Struct types … A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored. struct { x, y float64 "" // an empty tag string is like an absent tag name string "any string is permitted as a tag" _ [4]byte "ceci n'est pas un champ de structure" } // A struct corresponding to a TimeStamp protocol buffer.

Struct method promotion
Yesterday we saw an example of struct field promotion. But methods (which we haven’t really discussed yet) can also be promoted. Struct types … Given a struct type S and a named type T, promoted methods are included in the method set of the struct as follows: If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.