Literal
A literal is a value written straight into the source: 42, 2.5, 'A', true. This lesson is about the numeric ones — the many ways one number can be spelled — and about a rule that surprises people: how a literal gets its type.
Four bases
A prefix picks the base. The base is only how the source spells the value, so all four variables hold the same 255:
let decimal = 255;
let hex = 0xFF;
let octal = 0o377;
let binary = 0b11111111;
| Prefix | Base | Typical use |
|---|---|---|
| none | 10 | Everyday numbers |
0x | 16 | Colours, memory addresses, byte values |
0o | 8 | File permissions |
0b | 2 | Bit flags and masks |
Digit separators
An underscore between digits is ignored. It groups them the way a comma does on paper:
let population = 8_100_000_000;
let mask = 0xFFFF_0000;
let flags = 0b1010_0101;
Exponents
A float literal may carry an exponent: e8 means "times ten to the eighth".
let light = 2.998e8;
let charge = 1.6e-19;
Suffixes
A suffix fixes the type inside the literal itself:
let small = 200u8;
let wide = 5i64;
let single = 0.5f32;
let half = 0.5f16;
u8 makes a uint8, i64 an int64, f32 a float32 and f16 a float16; plain u and i mean uint and int. Every width has one:
| Signed | Unsigned | Floating-point |
|---|---|---|
i8 … i512 | u8 … u512 | f8, f16, f32, f64, f80, f128, f256, f512 |
i for int | u for uint |
A literal of any width is read exactly, however long. This one is too big for 64 bits, so without its suffix it would be refused as out of range for int:
let huge = 340_282_366_920_938_463_463_374_607_431_768_211_455u128;
How a literal gets its type
This is the part worth slowing down for:
flowchart LR
lit["An unsuffixed literal,<br/>such as `100`"] --> q{"Is it next to a value<br/>that already has a type?"}
q -- "no — it stands alone<br/>or beside another literal" --> def["`int`, or `float64` for `2.5`"]
q -- "yes" --> partner["It takes that value's type"]
partner --> fit{"Does it fit that type?"}
fit -- "yes" --> ok["Compiled in that type"]
fit -- "no" --> err["`error: integer literal is`<br/>`out of range for type …`"]Here 100 sits next to level, a uint8, so it becomes a uint8 too, and the sum is worked out in eight bits. 300 does not fit in eight bits: the result wraps around past 255 and lands on 44.
let level: uint8 = 200;
let raised = level + 100;
Two literals together have no typed partner, so both are int and the sum is plain 300:
let plain = 200 + 100;
Wrapping is covered properly in Arithmetic and Wrapping arithmetic.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A literal is a value written straight into the source: 42, 2.5, 'A', true. This lesson is about
// the numeric ones — the many ways one number can be spelled, and how a literal gets its type.
import Io::PrintLine;
func Main() -> int {
// The same number in four bases. A prefix picks the base: `0x` for hexadecimal, `0o` for
// octal and `0b` for binary. The base is only how the source spells the value, so all four
// variables hold the same 255 and print it the same way.
let decimal = 255;
let hex = 0xFF;
let octal = 0o377;
let binary = 0b11111111;
PrintLine("bases {} {} {} {}", decimal, hex, octal, binary);
// An underscore between digits is ignored. It groups them the way a comma does on paper.
let population = 8_100_000_000;
let mask = 0xFFFF_0000;
let flags = 0b1010_0101;
PrintLine("separators {} {} {}", population, mask, flags);
// A float literal may carry an exponent: `e8` means "times ten to the eighth".
let light = 2.998e8;
let charge = 1.6e-19;
PrintLine("exponents {} {}", light, charge);
// A suffix fixes the type inside the literal itself: `u8` makes a uint8, `i64` an int64 and
// `f32` a float32. Plain `u` and `i` mean `uint` and `int`. Every width has its suffix, from
// `i8` to `i512`, `u8` to `u512` and `f8` to `f512`, and a literal of any width is read
// exactly: the one below is too big for 64 bits but fits a uint128.
let small = 200u8;
let wide = 5i64;
let single = 0.5f32;
let half = 0.5f16;
PrintLine("suffixes {} {} {} {}", small, wide, single, half);
let huge = 340_282_366_920_938_463_463_374_607_431_768_211_455u128;
PrintLine("u128 {}", huge);
// With no suffix, a literal standing alone is an `int` or a `float64`. Next to a value that
// already has a type, it takes that value's type instead. Here 100 becomes a uint8, the sum
// is worked out in eight bits, and 300 does not fit in eight bits: the result wraps around
// past 255 and lands on 44.
let level: uint8 = 200;
let raised = level + 100;
PrintLine("uint8 {}", raised);
// Two literals together have no typed partner, so both are `int` and the sum is plain 300.
let plain = 200 + 100;
PrintLine("int {}", plain);
// Because the literal takes the other side's type, it must also fit that type. These two are
// refused rather than quietly turned into something else:
//
// let over = level + 300;
// error: integer literal is out of range for type 'uint8'
//
// let count: uint = 3;
// let missing = count == -1;
// error: integer literal is out of range for type 'uint'
return 0;
}
Run it
cd Examples/Basics/Literal
rux run
bases 255 255 255 255
separators 8100000000 4294901760 165
exponents 299800000.0 1.6e-19
suffixes 200 5 0.5 0.5
u128 340282366920938463463374607431768211455
uint8 44
int 300
Common mistakes
Try it yourself
- Print
0b1111_0000,0xF0and0o360. Are they the same number? - Write the speed of light in km/s as
2.998e5and the mass of an electron as9.109e-31. - Change
level + 100tolevel + 300and read the error. Then make it compile by givinglevela wider type.
Learn more
- Literals in the Rux Reference
- Integer and Float — the types literals become
- Format number — printing numbers in hexadecimal and binary