---
title: Integers
description: "Arithmetic in smart contracts on TON is always done with integers and never with floats"
---

import { Badge } from '@astrojs/starlight/components';

Arithmetic in smart contracts on TON is always performed with integers and never with floating-point numbers, since floats are [unpredictable](https://learn.microsoft.com/en-us/cpp/build/why-floating-point-numbers-may-lose-precision). Therefore, a strong emphasis is placed on integers and their handling.

The only primitive number type in Tact is `Int{:tact}`, 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{:tact}` as [integer literals](/book/expressions#integer-literals).

Most of the notations allow adding underscores (`_`) between digits, except for:
* Representations in strings, as seen in the [nano-tons](#nanotoncoin) case.
* Decimal numbers written with a leading zero $0.$ Their use is generally discouraged; see [below](#decimal).

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

### Decimal

The most common and widely used way of representing numbers is using the [decimal numeral system](https://en.wikipedia.org/wiki/Decimal): $123456789.$\
You can use underscores (`_`) to improve readability: $123\_456\_789$ is equal to $123456789.$

:::caution
  Alternatively, you can prefix the number with one $0$, which prohibits the use of underscores and only allows decimal digits: $0123 = 123.$
  Note that using this notation with a 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 the [hexadecimal numeral system](https://en.wikipedia.org/wiki/Hexadecimal), 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 the [octal numeral system](https://en.wikipedia.org/wiki/Octal), 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 the [binary numeral system](https://en.wikipedia.org/wiki/Binary_number), 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 — these are used for the cents value. But how would we represent the number \$$1.25$ if we are only able to work with integers? The solution is to work with _cents_ directly. In this way, \$$1.25$ becomes $125$ cents. We simply remember 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 two. One can say that nanoToncoin is one-billionth ($\frac{1}{10^{9}}$) of a Toncoin.

Therefore, the amount of $1.25$ Toncoin, which can be represented in Tact as [`ton("1.25"){:tact}`](/ref/core-comptime#ton), is actually the number $1250000000$. We refer to such numbers as _nanoToncoin(s)_ (or _nano-ton(s)_) rather than _cents_.

## Serialization

When encoding `Int{:tact}` values to persistent state (fields or parameters of [contracts](/book/contracts) and fields of [traits](/book/types#traits)), it is usually better to use smaller representations than 257 bits to reduce [storage costs](https://docs.ton.org/develop/smart-contracts/fees#storage-fee). The use of such representations is also called "serialization" because they represent the native [TL-B][tlb] types on which TON Blockchain operates.

The persistent state size is specified in every declaration of a state variable after the `as{:tact}` keyword:

```tact
contract SerializationExample {
    // persistent state variables
    oneByte: Int as int8 = 0; // ranges from -128 to 127 (takes 8 bits = 1 byte)
    twoBytes: Int as int16;   // ranges from -32,768 to 32,767 (takes 16 bits = 2 bytes)

    init() {
        // needs to be initialized in the init() because it does not have a default value
        self.twoBytes = 55*55;
    }
}
```

Integer serialization is also available for the fields of [structs](/book/structs-and-messages#structs) and [Messages](/book/structs-and-messages#messages), as well as in the key/value types of [maps](/book/maps):

```tact
struct StSerialization {
    martin: Int as int8;
}

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

The motivation is very simple:
* Storing 1000 257-bit integers in the state [costs](https://docs.ton.org/develop/smart-contracts/fees#how-to-calculate-fees) about 0.184 TON per year.
* Storing 1000 32-bit integers only costs 0.023 TON per year by comparison.

:::note

  Serialization limits apply only to the contract state between transactions and are **not** imposed on the temporary [TVM][tvm] memory, which operates only on 257-bit integers.

  Attempts to assign out-of-bounds values will result in [exit code 5](/book/exit-codes#5) being thrown at the very end of the [compute phase](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase): `Integer out of expected range`.

:::

### Common serialization types

Name             | [TL-B][tlb]                 | Inclusive range             | Space taken
:--------------: | :-------------------------: | :-------------------------: | :------------------------:
`uint8{:tact}`   | [`uint8`][tlb-builtin]      | $0$ to $2^{8} - 1$          | $8$ bits = $1$ byte
`uint16{:tact}`  | [`uint16`][tlb-builtin]     | $0$ to $2^{16} - 1$         | $16$ bits = $2$ bytes
`uint32{:tact}`  | [`uint32`][tlb-builtin]     | $0$ to $2^{32} - 1$         | $32$ bits = $4$ bytes
`uint64{:tact}`  | [`uint64`][tlb-builtin]     | $0$ to $2^{64} - 1$         | $64$ bits = $8$ bytes
`uint128{:tact}` | [`uint128`][tlb-builtin]    | $0$ to $2^{128} - 1$        | $128$ bits = $16$ bytes
`uint256{:tact}` | [`uint256`][tlb-builtin]    | $0$ to $2^{256} - 1$        | $256$ bits = $32$ bytes
`int8{:tact}`    | [`int8`][tlb-builtin]       | $-2^{7}$ to $2^{7} - 1$     | $8$ bits = $1$ byte
`int16{:tact}`   | [`int16`][tlb-builtin]      | $-2^{15}$ to $2^{15} - 1$   | $16$ bits = $2$ bytes
`int32{:tact}`   | [`int32`][tlb-builtin]      | $-2^{31}$ to $2^{31} - 1$   | $32$ bits = $4$ bytes
`int64{:tact}`   | [`int64`][tlb-builtin]      | $-2^{63}$ to $2^{63} - 1$   | $64$ bits = $8$ bytes
`int128{:tact}`  | [`int128`][tlb-builtin]     | $-2^{127}$ to $2^{127} - 1$ | $128$ bits = $16$ bytes
`int256{:tact}`  | [`int256`][tlb-builtin]     | $-2^{255}$ to $2^{255} - 1$ | $256$ bits = $32$ bytes
`int257{:tact}`  | [`int257`][tlb-builtin]     | $-2^{256}$ to $2^{256} - 1$ | $257$ bits = $32$ bytes + $1$ bit
`coins{:tact}`   | [`VarUInteger 16`][varuint] | $0$ to $2^{120} - 1$        | Between $4$ and $124$ bits, [see below](#serialization-varint)

### Arbitrary types of fixed bit-width {#serialization-fixed}

<Badge text="Available since Tact 1.5" variant="tip" size="medium"/><p/>

In addition to [common serialization types](#common-serialization-types), it is possible to specify arbitrary bit-width integers by using the prefix `int` or `uint`, followed by digits. For example, writing `int7{:tact}` refers to a signed $7$-bit integer.

The minimum allowed bit-width of an `Int{:tact}` type is $1$, while the maximum is $257$ for the `int` prefix (signed integers) and $256$ for the `uint` prefix (unsigned integers).

Name             | [TL-B][tlb]            | Inclusive range                 | Space taken
:--------------: | :--------------------: | :-----------------------------: | :---------:
`uintX{:tact}`   | [`uintX`][tlb-builtin] | $0$ to $2^{X} - 1$              | $X$ bits, where $X$ is between $1$ and $256$
`intX{:tact}`    | [`intX`][tlb-builtin]  | $-2^{X - 1}$ to $2^{X - 1} - 1$ | $X$ bits, where $X$ is between $1$ and $257$

### Types of variable bit-width {#serialization-varint}

Name           | [TL-B][tlb]                 | Inclusive range      | Space taken
:------------: | :-------------------------: | :------------------: | :-------------------------
`coins{:tact}` | [`VarUInteger 16`][varuint] | $0$ to $2^{120} - 1$ | between $4$ and $124$ bits

In Tact, the variable `coins{:tact}` format is an alias to [`VarUInteger 16`][varuint] in [TL-B][tlb] 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](/book/integers#nanotoncoin) amounts.

This serialization format consists of two [TL-B fields](https://docs.ton.org/develop/data-formats/tl-b-language#field-definitions):

* `len`, a $4$-bit unsigned big-endian integer storing the byte length of the provided value
* `value`, an $8 * len$-bit unsigned big-endian representation of the provided value

That is, integers serialized as `coins{:tact}` 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:

```tact
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
}
```

Name               | [TL-B][tlb]                 | Inclusive range             | Space taken
:----------------: | :-------------------------: | :-------------------------: | :-------------------------
`varuint16{:tact}` | [`VarUInteger 16`][varuint] | same as `coins{:tact}`      | same as `coins{:tact}`
`varint16{:tact}`  | `VarInteger 16`             | $-2^{119}$ to $2^{119} - 1$ | between $4$ and $124$ bits
`varuint32{:tact}` | [`VarUInteger 32`][varuint] | $0$ to $2^{248} - 1$        | between $5$ and $253$ bits
`varint32{:tact}`  | `VarInteger 32`             | $-2^{247}$ to $2^{247} - 1$ | between $5$ and $253$ bits

<p/><Badge text="Available since Tact 1.6" variant="tip" size="medium"/><p/>

The `varuint16{:tact}` format is equivalent to [`coins{:tact}`](#serialization-varint). Its signed variant, `varint16{:tact}`, has the same memory layout except for the signed `value` field, which allows a different range of values: from $-2^{119}$ to $2^{119} - 1$, including both endpoints.

To store greater values, use the `varuint32{:tact}` and `varint32{:tact}` formats. These are serialized almost identically to `coins{:tact}` and other smaller variable integer formats but use a $5$-bit `len` field for storing the byte length. This allows the `value` to use up to $248$ bits for storing the actual number, meaning that both `varuint32{:tact}` and `varint32{:tact}` can occupy up to $253$ bits in total.

Examples:

```tact
struct BradBit {
    // len: 00000, 5 bits
    // value: none!
    // in total: 5 bits
    a: Int as varuint32 = 0; // 00000

    // len: 00001, 5 bits
    // value: 00000001, 8 bits
    // in total: 13 bits
    b: Int as varuint32 = 1; // 00001 00000001

    // len: 00010, 5 bits
    // value: 00000001 00000010, 16 bits
    // in total: 21 bits
    c: Int as varuint32 = 258; // 00010 00000001 00000010

    // len: 11111, 5 bits
    // value: two hundred and forty-eight 1's in binary
    // in total: 253 bits
    d: Int as varuint32 = pow(2, 248) - 1; // two hundred and forty-eight 1's in binary
}
```

:::note

  Read more on serialization here: [Compatibility with FunC](/book/func#convert-serialization)

:::

## Operations

All runtime calculations with numbers are performed at 257 bits, so [overflows](https://en.wikipedia.org/wiki/Integer_overflow) 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](#serialization) in the same calculation. At runtime, they are all the same type no matter what — 257-bit signed integers, so overflows won't occur at this stage.

However, this can still lead to **errors** in the [compute phase](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase) of the transaction. Consider the following example:

```tact
contract ComputeErrorsOhNo {
    oneByte: Int as uint8; // persistent state variable, max value is 255

    init() {
        self.oneByte = 255; // initial value is 255, everything fits
    }

    // Empty receiver for the deployment
    receive() {
        // Forward the remaining value in the
        // incoming message back to the sender
        cashback(sender());
    }

    receive("lets break it") {
        let tmp: Int = self.oneByte * 256; // no runtime overflow
        self.oneByte = tmp; // whoops, tmp value is out of expected range of oneByte
    }
}
```

Here, `oneByte` is serialized as a [`uint8`](#common-serialization-types), which occupies only one byte and ranges from $0$ to $2^{8} - 1$, which is $255$. When used in runtime calculations, no overflow occurs since everything is calculated as $257$-bit signed integers. However, the moment we decide to store the value of `tmp` back into `oneByte`, we get an error with [exit code 5](/book/exit-codes#5), which states the following: `Integer out of expected range`.

:::caution

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

:::

[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview
[tlb]: https://docs.ton.org/develop/data-formats/tl-b-language
[tlb-builtin]: https://docs.ton.org/develop/data-formats/tl-b-language#built-in-types
[varuint]: https://docs.ton.org/develop/data-formats/msg-tlb#varuinteger-n
