Number Types
Note: The primitive int- and float- types are not implemented, and the standard number types (i32, f64 and the rest) are built into the compiler rather than defined in a library. A custom number type written as below, with a standard number type as its field in place of int-32, does work: a struct wrapping the number, methods on it, and its conversion from a number, Nbr32.from(somenbr), declared as below. The standard number types' from is built into the compiler with them, and so are an integer's bit methods, which are implemented.
The standard integer and float types (e.g., i32, f64) were introduced early in this reference, so that we could show examples of working expressions and code. Now we can reveal that these types are defined in the standard library using a more primitive number type.
This page covers:
- The primitive number types: int and float
- How to define additional number types beyond the standard collection
The 'int' and 'float' number types#
int-32 specifies a 32-bit integer. Any integer value from 1 to 128 may be specified after the dash, to establish the number of bits.
Similarly, float-32 is a 32-bit floating point number. Floats only support 32 and 64-bit values.
Unlike i32, int-32 does not define any methods for manipulating its values (e.g., adding and subtracting). It is also agnostic on the semantic interpretation of its values ... it does not even signify whether the integer value should be interpreted as signed or unsigned. Its lack of built-in methods and interpretation is what makes it an ideal foundation for defining new types with very different interpretations and methods.
An integer's bits#
Every integer type (u8 to u64, i8 to i64, usize and isize) has methods that count and move its bits. Each count is a u32, and each is defined for 0, as the width:
- x.countOnes()
- How many of its bits are 1.
- x.leadingZeros()
- How many bits are 0 above its highest 1: the width, for 0.
- x.trailingZeros()
- How many bits are 0 below its lowest 1: the width, for 0.
0x2Cu8.countOnes() // 3 1u64.leadingZeros() // 63 0u32.trailingZeros() // 32 (-1i16).countOnes() // 16: a signed integer's bits, the sign's among them
The rest take an amount n, a u32, and use it modulo the width, so every amount has an answer and 0, the width and twice the width give the same one:
- x.rotateLeft(n), x.rotateRight(n)
- The bits moved by n, those shifted out of one end coming in at the other.
- x.shlMasked(n), x.shrMasked(n)
- A shift by n modulo the width: the shift most processors do in one instruction. shrMasked fills with the sign bit on a signed integer and with zeros on an unsigned one. The operators << and >> differ only past the width, where they shift every bit out (Shift operators).
0x81u8.rotateLeft(1) // 0x03 1u32.shlMasked(33) // 2, a shift by 1 1u32 << 33u32 // 0 (-16i8).shrMasked(10) // -4, a shift by 2
The same operations are core's intrinsics, mem.countOnes(x) and the rest (Intrinsics), whose type parameter is any of these integer types.
Custom Number Types#
There are several reasons why we might want to define distinctly different number types than those provided by the standard library:
- One might want to use different types to distinguish between units of measure, thereby potentially avoiding problems like the loss of the $125 million Mars probe.
- Different number types could make different promises for undefined behavior (e.g., protecting against undesired overflows), or could guarantee the number values are bounded by some range.
- Integers that support intrinsic atomic operations can be separately typed, as the core package's Atomic[T] is.
Creating a new numeric integer type is nearly as simple as wrapping an primitive integer type as a field in a struct. For example:
struct Nbr32 {
pub nbr int-32;
}
Methods may be added to this struct to define the operations that may be performed on values of this type. These methods can make use of fast internal integer primitive functions (TBD).
Although numbers of one type are never implicitly converted into the equivalent number of another type, a conversion may always be asked for explicitly. A conversion is a method of the type converted to, named from and called on the type, which takes the value to convert:
imm converted = Nbr32.from(somenbr);
Every standard number type has one, which takes a value of any number type (Explicit Conversion). A custom number type declares its own, an ordinary function of the type:
struct Nbr32 {
pub nbr int-32;
pub fn from(n i32) Nbr32 {
new Nbr32(n);
}
}
from is the type's default conversion from another type. A conversion that differs from it, such as one that saturates at the type's limits or one that checks that the value fits, is an ordinary method with a name of its own.