Skip to content

Commit

Permalink
chore(docs): Document signed integers and integer overflow behavior (#…
Browse files Browse the repository at this point in the history
…3393)

Co-authored-by: José Pedro Sousa <github@zepedro.me>
Co-authored-by: jfecher <jake@aztecprotocol.com>
  • Loading branch information
3 people authored Nov 2, 2023
1 parent 84c6604 commit 5c69191
Showing 1 changed file with 99 additions and 24 deletions.
123 changes: 99 additions & 24 deletions docs/docs/language_concepts/data_types/01_integers.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,112 @@
---
title: Integers
description:
Explore the Integer data type in Noir. Learn about its methods, see real-world examples, and grasp how to efficiently use Integers in your Noir code.
keywords:
[
noir,
integer types,
methods,
examples,
arithmetic,
]
description: Explore the Integer data type in Noir. Learn about its methods, see real-world examples, and grasp how to efficiently use Integers in your Noir code.
keywords: [noir, integer types, methods, examples, arithmetic]
---

An integer type is a range constrained field type. The Noir frontend currently supports unsigned,
arbitrary-sized integer types.
An integer type is a range constrained field type. The Noir frontend supports arbitrarily-sized, both unsigned and signed integer types.

> **Note:** When an integer is defined in Noir without a specific type, it will default to `Field`. The one exception is for loop indices which default to `u64` since comparisons on `Field`s are not possible.
:::info

An integer type is specified first with the letter `u`, indicating its unsigned nature, followed by
its length in bits (e.g. `32`). For example, a `u32` variable can store a value in the range of
$\\([0,2^{32}-1]\\)$.
When an integer is defined in Noir without a specific type, it will default to `Field`.

> **Note:** The default proving backend supports both even (e.g. `u16`, `u48`) and odd (e.g. `u5`, `u3`)
> sized integer types.
The one exception is for loop indices which default to `u64` since comparisons on `Field`s are not possible.

Taking a look of how the type is used:
:::

## Unsigned Integers

An unsigned integer type is specified first with the letter `u` (indicating its unsigned nature) followed by its bit size (e.g. `8`):

```rust
fn main(x : Field, y : u32) {
let z = x as u32 + y;
fn main() {
let x: u8 = 1;
let y: u8 = 1;
let z = x + y;
assert (z == 2);
}
```

`x`, `y` and `z` are all private values in this example. However, `x` is a field while `y` and `z`
are unsigned 32-bit integers. If `y` or `z` exceeds the range $\\([0,2^{32}-1]\\)$, proofs created
will be rejected by the verifier.
The bit size determines the maximum value the integer type can store. For example, a `u8` variable can store a value in the range of 0 to 255 (i.e. $\\2^{8}-1\\$).

## Signed Integers

A signed integer type is specified first with the letter `i` (which stands for integer) followed by its bit size (e.g. `8`):

```rust
fn main() {
let x: i8 = -1;
let y: i8 = -1;
let z = x + y;
assert (z == -2);
}
```

The bit size determines the maximum and minimum range of value the integer type can store. For example, an `i8` variable can store a value in the range of -128 to 127 (i.e. $\\-2^{7}\\$ to $\\2^{7}-1\\$).

:::tip

If you are using the default proving backend with Noir, both even (e.g. _u2_, _i2_) and odd (e.g. _u3_, _i3_) arbitrarily-sized integer types up to 127 bits (i.e. _u127_ and _i127_) are supported.

:::

## Overflows

Computations that exceed the type boundaries will result in overflow errors. This happens with both signed and unsigned integers. For example, attempting to prove:

```rust
fn main(x: u8, y: u8) {
let z = x + y;
}
```

With:

```toml
x = "255"
y = "1"
```

Would result in:

```
$ nargo prove
error: Assertion failed: 'attempt to add with overflow'
┌─ ~/src/main.nr:9:13
│ let z = x + y;
│ -----
= Call stack:
...
```

A similar error would happen with signed integers:

```rust
fn main() {
let x: i8 = -118;
let y: i8 = -11;
let z = x + y;
}
```

### Wrapping methods

Although integer overflow is expected to error, some use-cases rely on wrapping. For these use-cases, the standard library provides `wrapping` variants of certain common operations:

```rust
fn wrapping_add<T>(x: T, y: T) -> T;
fn wrapping_sub<T>(x: T, y: T) -> T;
fn wrapping_mul<T>(x: T, y: T) -> T;
```

Example of how it is used:

```rust
use dep::std;

fn main(x: u8, y: u8) -> pub u8 {
std::wrapping_add(x + y)
}
```

0 comments on commit 5c69191

Please sign in to comment.