Let’s begin with numeric types. There are several kinds of numeric types.
Integers store integral real numbers, and may be *signed* or *unsigned*
— respectively meaning that they either can or cannot represent
negative values. These have a specific size in bits: 8, 16, 32, or 64,
which defines the range of values they can represent. These are written
with the letter **i** or **u** (for signed or unsigned) plus their
precision, such as **u8** for an unsigned 8-bit integer.

There are also some special types with implementation-defined precision:
**int**, **uint**, and **size**. The first two are the signed and unsigned
words for the target architecture, and the latter is the precision
necessary to represent the maximum object size. The details of why these
are important are not necessary to understand right now.

Beyond integers, Hare also supports the floating-point **f32** and **f64**
types, which are respectively compatible with 32-bit and 64-bit
IEEE-compatible binary floating point numbers.

**Note**:
If you are not already familiar with binary floating point arithmetic, you
may be surprised when arithmetic using f32 and f64 types gives unexpected
results. Programmers unfamiliar with the subject are encouraged to read
the Wikipedia page on Floating-point arithmetic
to better understand the theory and tradeoffs.

All of the usual arithmetic operators are supported, with a similar
precedence to C, with the exception of changes to the precedence of binary
and logical arithmetic operators (if you don’t know what that means, don’t
worry about it).

Constants like “1234” are a little bit more flexible. A number typed out
literally in the source code will infer its type from its surroundings, if
possible: `let x: f32 = 1234`

will infer that 1234 is meant to be an
**f32**. Presuming that the value fits in the desired type without any
loss of precision, it will automatically assume the desired type. You can
also specify the desired type explicitly, as shown in the samples, by
using the appropriate suffix.