# Integers

Arithmetic in smart contracts on TON is always done with integers and never with floating-point numbers since the floats are unpredictable (opens in a new tab). Therefore, the big accent goes on integers and their handling.

The only primitive number type in Tact is `Int`

, for $257$-bit signed integers.

It's capable of storing integers between $-2^{256}$ and $2^{256} - 1.$

## Notation

Tact supports various ways of writing primitive values of `Int`

as integer literals.

Most of the notations allow adding underscores (`_`

) in-between digits, except for:

- Representations in strings, as seen in nano-tons case.
- Decimal numbers written with a leading zero $0.$ Their use is generally discouraged, see below.

Additionally, several underscores in a row as in $4\_\_2$, or trailing underscores as in $42\_$ are **not** allowed.

### Decimal

Most common and most used way of representing numbers, using the decimal numeral system (opens in a new tab): $123456789.$

You can use underscores (`_`

) to improve readability: $123\_456\_789$ is equal to $123456789.$

Alternatively, you can prefix the number with one $0$, which prohibits use of underscores and only allows decimal digits: $0123 = 123.$
Note, that using this notation with leading zero is **strongly discouraged** due to possible confusion with octal integer literals in TypeScript, which is often used alongside Tact to develop and test contracts.

### Hexadecimal

Represent numbers using hexadecimal numeral system (opens in a new tab), denoted by the $\mathrm{0x}$ (or $\mathrm{0X}$) prefix: $\mathrm{0xFFFFFFFFF}.$

Use underscores (`_`

) to improve readability: $\mathrm{0xFFF\_FFF\_FFF}$ is equal to $\mathrm{0xFFFFFFFFF}.$

### Octal

Represent numbers using octal numeral system (opens in a new tab), denoted by the $\mathrm{0o}$ (or $\mathrm{0O}$) prefix: $\mathrm{0o777777777.}$

Use underscores (`_`

) to improve readability: $\mathrm{0o777\_777\_777}$ is equal to $\mathrm{0o777777777}.$

### Binary

Represent numbers using binary numeral system (opens in a new tab), denoted by the $\mathrm{0b}$ (or $\mathrm{0B}$) prefix: $\mathrm{0b111111111.}$

Use underscores (`_`

) to improve readability: $\mathrm{0b111\_111\_111}$ is equal to $\mathrm{0b111111111}.$

### NanoToncoin

Arithmetic with dollars requires two decimal places after the dot — those are used for the cents value. But how would we represent the number $$1.25$ if we're only able to work with integers? The solution is to work with *cents* directly. This way, $$1.25$ becomes $125$ cents. We simply memorize that the two rightmost digits represent the numbers after the decimal point.

Similarly, working with Toncoin, the main currency of TON Blockchain, requires nine decimal places instead of the two. One can say that nanoToncoin is the $\frac{1}{10^{9}}\mathrm{th}$ of the Toncoin.

Therefore, the amount of $1.25$ Toncoin, which can be represented in Tact as `ton("1.25")`

, is actually the number $1250000000$. We refer to such numbers as *nanoToncoin(s)* (or *nano-ton(s)*) rather than *cents*.

## Serialization

When encoding `Int`

values to persistent state (fields of contracts and traits), it's usually better to use smaller representations than $257$-bits to reduce storage costs (opens in a new tab). Usage of such representations is also called "serialization" due to them representing the native TL-B (opens in a new tab) types which TON Blockchain operates on.

The persistent state size is specified in every declaration of a state variable after the `as`

keyword:

```
contract SerializationExample {
// persistent state variables
oneByte: Int as int8 = 0; // ranges from -128 to 127 (takes 8 bit = 1 byte)
twoBytes: Int as int16; // ranges from -32,768 to 32,767 (takes 16 bit = 2 bytes)
init() {
// needs to be initialized in the init() because it doesn't have the default value
self.twoBytes = 55*55;
}
}
```

Integer serialization is also available for the fields of Structs and Messages, as well as in key/value types of maps:

```
struct StSerialization {
martin: Int as int8;
}
message MsgSerialization {
seamus: Int as int8;
mcFly: map<Int as int8, Int as int8>;
}
```

Motivation is very simple:

- Storing $1000$ $257$-bit integers in state costs (opens in a new tab) about $0.184$ TON per year.
- Storing $1000$ $32$-bit integers only costs $0.023$ TON per year by comparison.

### Serialization types

Name | TL-B (opens in a new tab) | Inclusive range | Space taken |
---|---|---|---|

`uint8` | `uint8` (opens in a new tab) | $0$ to $2^{8} - 1$ | $8$ bits = $1$ byte |

`uint16` | `uint16` (opens in a new tab) | $0$ to $2^{16} - 1$ | $16$ bits = $2$ bytes |

`uint32` | `uint32` (opens in a new tab) | $0$ to $2^{32} - 1$ | $32$ bits = $4$ bytes |

`uint64` | `uint64` (opens in a new tab) | $0$ to $2^{64} - 1$ | $64$ bits = $8$ bytes |

`uint128` | `uint128` (opens in a new tab) | $0$ to $2^{128} - 1$ | $128$ bits = $16$ bytes |

`uint256` | `uint256` (opens in a new tab) | $0$ to $2^{256} - 1$ | $256$ bits = $32$ bytes |

`int8` | `int8` (opens in a new tab) | $-2^{7}$ to $2^{7} - 1$ | $8$ bits = $1$ byte |

`int16` | `int16` (opens in a new tab) | $-2^{15}$ to $2^{15} - 1$ | $16$ bits = $2$ bytes |

`int32` | `int32` (opens in a new tab) | $-2^{31}$ to $2^{31} - 1$ | $32$ bits = $4$ bytes |

`int64` | `int64` (opens in a new tab) | $-2^{63}$ to $2^{63} - 1$ | $64$ bits = $8$ bytes |

`int128` | `int128` (opens in a new tab) | $-2^{127}$ to $2^{127} - 1$ | $128$ bits = $16$ bytes |

`int256` | `int256` (opens in a new tab) | $-2^{255}$ to $2^{255} - 1$ | $256$ bits = $32$ bytes |

`int257` | `int257` (opens in a new tab) | $-2^{256}$ to $2^{256} - 1$ | $257$ bits = $32$ bytes + $1$ bit |

`coins` | `VarUInteger 16` (opens in a new tab) | $0$ to $2^{120} - 1$ | between $4$ and $124$ bits, see below |

### Variable `coins`

type

In Tact, `coins`

is an alias to `VarUInteger 16`

(opens in a new tab) in TL-B (opens in a new tab) representation, i.e. it takes a variable bit length depending on the optimal number of bytes needed to store the given integer and is commonly used for storing nanoToncoin amounts.

This serialization format consists of two TL-B fields (opens in a new tab):

`len`

, a $4$-bit unsigned big-endian integer storing the byte length of the value provided`value`

, a $8 * len$-bit unsigned big-endian representation of the value provided

That is, integers serialized as `coins`

occupy between $4$ and $124$ bits ($4$ bits for `len`

and $0$ to $15$ bytes for `value`

) and have values in the inclusive range from $0$ to $2^{120} - 1$.

Examples:

```
struct Scrooge {
// len: 0000, 4 bits (always)
// value: none!
// in total: 4 bits
a: Int as coins = 0; // 0000
// len: 0001, 4 bits
// value: 00000001, 8 bits
// in total: 12 bits
b: Int as coins = 1; // 0001 00000001
// len: 0010, 4 bits
// value: 00000001 00000010, 16 bits
// in total: 20 bits
c: Int as coins = 258; // 0010 00000001 00000010
// len: 1111, 4 bits
// value: hundred twenty 1's in binary
// in total: 124 bits
d: Int as coins = pow(2, 120) - 1; // hundred twenty 1's in binary
}
```

Read more on serialization here: Compatibility with FunC

## Operations

All runtime calculations with numbers are done at 257-bits, so overflows (opens in a new tab) are quite rare. Nevertheless, if any math operation overflows, an exception will be thrown, and the transaction will fail. You could say that Tact's math is safe by default.

Note, that there is no problem with mixing variables of different state sizes in the same calculation. At runtime they are all the same type no matter what — $257$-bit signed, so overflows won't happen then.

However, this can still lead to **errors** in the compute phase (opens in a new tab) of the transaction. Consider the following example:

```
import "@stdlib/deploy";
contract ComputeErrorsOhNo with Deployable {
oneByte: Int as uint8; // persistent state variable, max value is 255
init() {
self.oneByte = 255; // initial value is 255, everything fits
}
receive("lets break it") {
let tmp: Int = self.oneByte * 256; // no runtime overflow
self.oneByte = tmp; // whoops, tmp value is out of the expected range of oneByte
}
}
```

Here, `oneByte`

is serialized as a `uint8`

, which occupies only one byte and ranges from $0$ to $2^{8} - 1$, which is $255$. And when used in runtime calculations no overflow happens and everything is calculated as a $257$-bit signed integers. But the very moment we decide to store the value of `tmp`

back into `oneByte`

we get an error with the exit code 5, which states the following: `Integer out of the expected range`

.

Therefore, be **very** careful with numbers and always double-check calculations when using serialization.