---
title: 整数
description: TON 智能合约中的算术运算始终使用整数，从不使用浮点数
---

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

TON 智能合约中的算术运算始终使用整数，从不使用浮点数，因为浮点数是[不可预测的](https://learn.microsoft.com/en-us/cpp/build/why-floating-point-numbers-may-lose-precision)。 因此，重点应放在整数及其处理上。

`Int{:tact}` 是一个 257 位的有符号整数类型。
它能够存储 $-2^{256}$ 和 $2^{256} - 1$ 之间的整数。

## 表示法

Tact 支持多种方式编写 `Int{:tact}` 的原始值作为[整数字面量](/zh-cn/book/expressions#integer-literals)。

大多数符号允许在数字之间添加下划线 (`_`)，但下列符号除外：

- 字符串表示法，如 [NanoToncoins](#nanotoncoin) 案例所示。
- 带前导零的十进制数 $0$。一般不鼓励使用，参见 [下文](#decimal)。

此外，**不允许**在 $4\_\_2$ 中连续使用多个下划线，或在 $42\_$ 中使用尾部下划线。

### 十进制 {#decimal}

最常见、最常用的数字表示方法，使用[十进制数字系统](https://en.wikipedia.org/wiki/Decimal)： $123456789$。\
您可以使用下划线（`_`）来提高可读性： $123\_456\_789$ 等于 $123456789$。

:::caution
  或者，您也可以在数字前加上一个 $0$，这样就禁止使用下划线，只允许使用十进制数字：$0123 = 123.$
  注意，**强烈建议**使用这种带前导零的符号，因为可能会与 TypeScript 中的八进制整数字面混淆，而 TypeScript 通常与 Tact 一起用于开发和测试合约。
:::

### 十六进制 {#hexadecimal}

使用[十六进制数字系统](https://en.wikipedia.org/wiki/Hexadecimal)表示数字，用 $\mathrm{0x}$（或 $\mathrm{0X}$）前缀表示：$\mathrm{0xFFFFFFFFF}$。
使用下划线（`_`）提高可读性：$\mathrm{0xFFF\_FFFF\_FFF}$ 等于 $\mathrm{0xFFFFFFFFF}$。

### 八进制 {#octal}

使用[八进制数字系统](https://en.wikipedia.org/wiki/Octal)表示数字，用 $\mathrm{0o}$（或 $\mathrm{0O}$）前缀表示：$\mathrm{0o777777777}$。
使用下划线（`_`）提高可读性：$\mathrm{0o777\_777\_777}$ 等于 $\mathrm{0o777777777}$。

### 二进制 {#binary}

使用[二进制数字系统](https://en.wikipedia.org/wiki/Binary_number)表示数字，用 $\mathrm{0b}$（或 $\mathrm{0B}$）前缀表示：$\mathrm{0b111111111}$。
使用下划线（`_`）提高可读性：$\mathrm{0b111\_111\_111}$ 等于 $\mathrm{0b111111111}$。

### NanoToncoins {#nanotoncoin}

与美元的运算要求小数点后保留两位小数——这些用于表示美分(cents)的值。 但是，如果我们只能用整数来表示数字 \$$1.25$ ，我们该如何表示呢？ 解决的办法是直接使用 _cents_。 这样， \$$1.25$ 就变成了 $125$ 美分。 我们只需记住最右边的两位数代表小数点后的数字。

同样，在使用 TON 区块链的主要货币 Toncoin 时，需要九位小数，而不是两位小数。 可以说，nanoToncoin是Toncoin的 $\frac{1}{10^{9}}\mathrm{th}$。

因此， $1.25$ Toncoin 的数量，可以用 Tact 表示为 [`ton("1.25"){:tact}`](/zh-cn/ref/core-comptime#ton)，实际上就是数字 $1250000000$。 我们称这样的数字为_nanoToncoin(s)_（或_nano-ton(s)）而不是_美分_。

## 序列化 {#serialization}

将 `Int{:tact}` 值编码为持久状态（[contracts](/zh-cn/book/contracts) 和 [traits](/zh-cn/book/types#traits) 的字段）时，通常最好使用比 $257$-bits 更小的表示形式，以降低[存储成本](https://docs.ton.org/develop/smart-contracts/fees#storage-fee)。 这些表示法的使用也被称为 "序列化"，因为它们代表了 TON 区块链运行的本地[TL-B][tlb]类型。

持久状态大小在状态变量的每个声明中都会在 `as{:tact}`关键字后指定：

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

整数序列化也适用于 [Structs](/zh-cn/book/structs-and-messages#structs) 和 [Messages](/zh-cn/book/structs-and-messages#messages) 的字段，以及 [maps](/zh-cn/book/maps) 的键/值类型：

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

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

动机很简单：

- 在状态中存储 $1000$ 个 $257$ 位的整数[成本](https://docs.ton.org/develop/smart-contracts/fees#how-to-calculate-fees)大约为每年 $0.184$ TON。
- 相比之下，存储 $1000$ $32$-bit 整数每年只需花费 $0.023$ ton 。

### 常见序列化类型 {#common-serialization-types}

|  名称              | [TL-B][tlb]                 |      包含的范围                  |                          占用空间                          |
| :--------------: | :-------------------------: | :-------------------------: | :----------------------------------------------------: |
| `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，[见下文](#serialization-coins) |

### 任意位宽类型

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

除了 [常见序列化类型](#common-serialization-types)，还可以使用前缀 `int` 或 `uint` 后跟数字来指定任意位宽的整数。 例如，写入 `int7{:tact}` 表示有符号 $7$- 位整数。

`Int{:tact}` 类型的最小允许位宽是 $1$，而对于 `int` 前缀（有符号整数）最大是 $257$，对于 `uint` 前缀（无符号整数）最大是 $256$。

|  名称              | [TL-B][tlb]            |      包含的范围                      |                     占用空间                     |
| :--------------: | :--------------------: | :-----------------------------: | :------------------------------------------: |
| `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$ |

### 变量 `coins` 类型 {#serialization-coins}

在 Tact 中，`coins{:tact}` 是[TL-B][tlb]表示法中[`VarUInteger 16`][varuint]的别名，即根据存储给定整数所需的最佳字节数，它的位长是可变的，通常用于存储[nanoToncoin](/zh-cn/book/integers#nanotoncoin)金额。

这种序列化格式包括两个 [TL-B 字段](https://docs.ton.org/develop/data-formats/tl-b-language#field-definitions)：

- `len`，一个 $4$位无符号大二进制整数，存储所提供值的字节长度
- `value`，即所提供值的 $8 * len$ 位无符号大二进制表示法

也就是说，序列化为 `coins{:tact}` 的整数占用 $4$ 至 $124$ 位（`len`为$4$ 位，`value`为 $0$ 至 $15$ 字节），其值范围为 $0$ 至 $2^{120} - 1$ 之间。

例如

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

:::note

  点击此处阅读有关序列化的更多信息：[与 FunC 兼容](/zh-cn/book/func#convert-serialization)

:::

## 操作 {#operations}

所有数字的运行时计算都是在 257 位完成的，因此 [溢出](https://en.wikipedia.org/wiki/Integer_overflow) 非常罕见。  不过，如果任何数学运算出现溢出，就会抛出异常，事务也会失败。 可以说，Tact 的数学默认是安全的。

请注意，在同一计算中混合使用 [不同状态大小](#serialization) 的变量是没有问题的。  在运行时，无论是什么，它们都是相同类型的——$257$-bit带符号，因此不会发生溢出。

然而，这仍可能导致交易的[计算阶段](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase)**错误**。 请看下面的例子：

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

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

    receive() { cashback(sender()) } // for the deployment

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

这里，`oneByte` 被序列化为 [`uint8`](#common-serialization-types)，只占用一个字节，范围从 $0$ 到 $2^8 - 1$，即 $255$。 在运行时计算中使用时，不会发生溢出，所有计算结果都是 $257$- 位有符号整数。 但是，就在我们决定将 `tmp` 的值存储回 `oneByte` 的那一刻，我们收到了一个错误，[退出码 5](/zh-cn/book/exit-codes#5)，错误信息如下：整数超出预期范围。

:::caution
  因此，在使用序列化时，对数字要**非常**小心，并始终仔细检查计算结果。
:::

[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
