What You Should Know About Unsigned, Signed Integers and Casting in Rust
By: Shinichi Okada
Tags: Programming, Rust, Rustlang, Data Type, Software Development

Unsigned Integer Types

Unsigned integer types in Rust start with u and it has 8, 16, 32, 64, and 128-bit. The minimum and maximum values are from 0 to 2ⁿ-1.

For example u8 has 0 to 2⁸-1, which is 255. The following table shows all the details for unsigned integers.

Rust unsigned integer types. Image by the author.

Finding max values of unsigned integer types. Try this Rust lang code online.

Output:

u8 has the max value of 255.

u16 has the max value of 65535.

u32 has the max value of 4294967295.

u64 has the max value of 18446744073709551615.

u128 has the max value of 340282366920938463463374607431768211455.

If you try to assign a negative number to an unsigned type, it will fail.

fn main() {

let u:u32 = -1;

println!("{} in binary is {:b}", u, u);

} error[E0600]: cannot apply unary operator `-` to type `u32`

--> main.rs" data-line="2" data-column="17">main.rs:2:17

|

2 | let u:u32 = -1;

| ^^ cannot apply unary operator `-`

|

= note: unsigned values cannot be negated

- is one of Rust's unary operators and it is the negation operator for signed integer types and floating-point types.

Signed Integer Types

The default integer type in Rust is i32 . Signed integer types in Rust start with i and it has 8, 16, 32, 64, and 128-bit. The minimum and maximum values are from -(2ⁿ⁻¹) to 2ⁿ⁻¹-1. The -1 in the n-1 is the sign bit (positive or negative), which we cover in the next section.

For example i8 has -(2⁷) to 2⁷-1, which is -128 to 127. The following table shows all the details for signed integers.

Rust signed integer types. Image by the author.

Finding min and max values of signed integer types. Try this Rust lang code online.

Output:

i8 has the min value of -128.

i8 has the max value of 127.

i16 has the min value of -32768.

i16 has the max value of 32767.

i32 has the min value of -2147483648.

i32 has the max value of 2147483647.

i64 has the min value of -9223372036854775808.

i64 has the max value of 9223372036854775807.

i128 has the min value of -170141183460469231731687303715884105728.

i128 has the max value of 170141183460469231731687303715884105727.

Signed, Ones’ Complement and Two’s Complement

In computing, signed number representations are required to encode negative numbers in binary number systems. Let’s examine sign-and-magnitude, ones’ complement, and two’s complement.

Sign-and-Magnitude

Sign-and-Magnitude is also called Signed Magnitude. The first bit (called the most significant bit or MSB) tells if it is positive by 0 or a negative by 1. The rest is called magnitude bits.

4-bit signed magnitude of +5. Image by the author.

As I mentioned it before that signed integer types have the min and the max from -(2ⁿ⁻¹) to 2ⁿ⁻¹-1 where n stands for the number of bits. Since we use the first bit for the positive and negative signs we have n-1 in the 2ⁿ⁻¹.

For 4-bit the min and max are from -(2³) to 2³–1, which is -8 to +7.

As you see in the diagram above, the positive and the negative have the same digits except for the sign bit.

The problem of the signed magnitude is that there are two zeros, 0000 and 1000 .

Ones’ Complement

The first bit (MSB) is the same as the signed magnitude. It tells a positive by 0 or negative by 1. The rest of the bits will take the compliment, which means if it is 1 the complement is 0 and if it is 0, then the complement is 1.

Ones’ complement of +5 (4-bit). Image by the author.

The signed ones’ complement has the same problem as the signed magnitude. There are two zeros, 0000 and 1111 .

Two’s Complement

In the binary numbering system, the radix (base) is two. That’s why the radix complement is called two’s complement and the diminished radix complement is called ones’ complement.

Two’s complement can avoid multiple representations of 0, and it avoids keeping track of carrying bits in case of overflow.

Two’s complement of +5 (4-bit). Image by the author.

We again use the first bit (MSB) for the + and - signs. We take the complement of the number and we add 1 to get the opposite number. This works from positive to negative and vice versa.

Two’s complement of -5 (4-bit). Image by the author.

For zero that is 0000 in the binary, the complement is 1111 and adding 1 results in 1 0000 . The 1 is called an "overflow" bit. Overflow occurs when the sum of the most significant (left-most) column produces a carry forward. This overflow or carry bit can be ignored.

The following table shows the comparison of three signed number representations.

4-bit Signed Binary Number Comparison

4-bit Signed Binary Number Comparison. Image by the author.

Rust signed two’s complement integer types

Now let’s compare negative numbers of signed two’s complement from the above table (from -1 to -7) to Rust’s signed integers.

Output:

-1 in binary is 11111111111111111111111111111111

-2 in binary is 11111111111111111111111111111110

-3 in binary is 11111111111111111111111111111101

-4 in binary is 11111111111111111111111111111100

-5 in binary is 11111111111111111111111111111011

-6 in binary is 11111111111111111111111111111010

-7 in binary is 11111111111111111111111111111001

They are exactly the same except Rust uses the default 32-bit. Rust’s signed integer types are called the signed two’s complement integer types.

Casting in Rust

Casting means changing the data type of a piece of data from one type to another.

as keyword turns primitive types into other primitive types. We can use as keyword to solve the code in the introduction.

Casting u16 to i16. Try this Rust lang code online.

When you cast from a small length to a larger length, for example from 8-bit to 16-bit, there won’t be any problem but when you cast down, you may have a problem.

Casting to an Unsigned Type

Method 1

When you cast to an unsigned type, T, T::MAX + 1 is added or subtracted until the value fits into the new type.

Example 1: Casting 1000 from the default, i32 to u8

u8 has the max number of 255, so we subtract 255+1, which is 256 from 1000 until it gets less than 255.

1000 - 256 = 744

1000 - 256 - 256 = 488

1000 - 256 - 256 - 256 = 232

Casting 1000 i32 to u8. Try this Rust lang code online.

Output:

1000 as a u8 is : 232

Method 2

1000₁₀ in decimal is 11 1110 1000₂ in binary. We can take the last 8 bits, which is 1110 1000 and it is 232 in decimal.

An interesting number is 256₁₀.

Casting 256 i32 to u8. Try this Rust lang code online.

Output:

256 as a u8 is : 0

256₁₀ is 100000000₂ in binary. If you take the last 8 bits it is 00000000 .

Casting to a Signed Type

Method 1

This is the same as above but you need to be aware that the min/max number of the data type.

i8 's min and max numbers are -128 and 127. So the number needs to be between them.

Casting 128 to i8 :

128-256=-128

Casting 1000 to i8:

1000 - 256 = 744

1000 - 256 - 256 = 488

1000 - 256 - 256 - 256 = 232

1000 - 256 - 256 - 256 - 256 = -24

Casting i32 numbers to i8. Try this Rust lang code online.

Output:

128 as a i8 is : -128

1000 as a i8 is : -24

232 as a i8 is : -24

257 as a i8 is : 1

130 as a i8 is : -126

514 as a i8 is : 2

Method 2

1000₁₀ in decimal is 11 1110 1000₂ in binary. The MSB within 8-bit is 1, so it is a negative number. Then use 2’s complement. The complement is 00010111₂ and we add 1₂ becomes 00011000₂. Finally, it is -24₁₀.

Image by the author.

When the MSB within 8-bit is 0, it is a positive number. Then take the first 8 least significant bits (LSB).

Image by the author.

Bitwise Negation

Rust uses ! for the Bitwise Negation (Bitwise NOT). This produces different results depends on the type.

Output:

u is 2

u in binary is 10

i is 2

i in binary is 10

Bitwise negation !u is 4294967293

Bitwise negation !i is -3

As you see !2 with the unsigned type returns 4294967293 and with the signed type returns -3 .

The bitwise negation on a signed integer returns the two’s complement as we saw previously in Rust signed two’s complement integer types.

Adding a Negative Number

The subtraction is the same as adding a negative number.

5 - 2 = 5 + (-2) = 3

This applies to binary as well.

0101 - 0010

= 0101 + (-0010) // (1)

= 0101 + 1110 // (2)

= 0011 // this is 3 in decimal number.

We find -0010 by finding the two’s complement of 0010 that is 1110 .

Conclusion

We covered Rust integer types, three types of signed binary numbers, casting to an unsigned type and a signed type. I hope you have a better idea of what’s going on when you are dealing with Rust integers and casting.

Please stay tuned for the next post.

Newsletter

Get full access to every story on Medium by becoming a member.