跳转到内容

整数

TON 智能合约中的算术运算始终使用整数,从不使用浮点数,因为浮点数是不可预测的。 因此,重点应放在整数及其处理上。

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

符号

Tact 支持以各种方式将 Int 的基元值写成 整数字面量

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

  • 字符串表示法,如 纳吨 案例所示。
  • 带前导零的十进制数 00。一般不鼓励使用,参见 下文

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

十进制

最常见、最常用的数字表示方法,使用十进制数字系统123456789123456789
您可以使用下划线(_)来提高可读性: 123_456_789123\_456\_789 等于 123456789123456789

十六进制

使用十六进制数字系统表示数字,用 0x\mathrm{0x}(或 0X\mathrm{0X})前缀表示:0xFFFFFFFFF\mathrm{0xFFFFFFFFF}。 使用下划线(_)提高可读性:0xFFF_FFFF_FFF\mathrm{0xFFF\_FFFF\_FFF} 等于 0xFFFFFFFFF\mathrm{0xFFFFFFFFF}

八进制

使用八进制数字系统表示数字,用 0o\mathrm{0o}(或 0O\mathrm{0O})前缀表示:0o777777777\mathrm{0o777777777}。 使用下划线(_)提高可读性:0o777_777_777\mathrm{0o777\_777\_777} 等于 0o777777777\mathrm{0o777777777}

二进制

使用二进制数字系统表示数字,用 0b\mathrm{0b}(或 0B\mathrm{0B})前缀表示:0b111111111\mathrm{0b111111111}。 使用下划线(_)提高可读性:0b111_111_111\mathrm{0b111\_111\_111} 等于 0b111111111\mathrm{0b111111111}

NanoToncoin

美元的算术运算需要在圆点后保留两位小数,这两位小数用于美分的计算。但是,如果我们只能用整数来表示数字 $1.251.25,我们该如何表示呢?解决的办法是直接使用 cents。这样,$1.251.25 就变成了 125125 美分。我们只需记住最右边的两位数代表小数点后的数字。

同样,在使用 TON 区块链的主要货币 Toncoin 时,需要九位小数,而不是两位小数。可以说,纳米通币是通币的 1109\frac{1}{10^{9}}

因此,1.251.25 Toncoin 的数量,可以用 Tact 表示为 ton("1.25"),实际上就是数字 12500000001250000000。我们把这些数字称为 纳米吨币(或 纳米吨),而不是 美分

序列化

Int 值编码为持久状态(contractstraits 的字段)时,通常最好使用比 257257-bits 更小的表示形式,以降低存储成本。 这些表示法的使用也被称为 “序列化”,因为它们代表了 TON 区块链运行的本地TL-B类型。

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

contract SerializationExample {
// 持久状态变量
oneByte: Int as int8 = 0; // 范围从 -128 到 127(占用 8 位 = 1 字节)
twoBytesint as int16; // 范围从 -32,768 到 32,767 (占用 16 位 = 2 个字节)
init() {
// 需要在 init() 中初始化,因为它没有默认值
self.twoBytes = 55*55;
}
}

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

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

动机很简单:

  • 在州成本中存储 10001000 257257- 位整数每年约需 0.1840.184 ton 。
  • 相比之下,存储 10001000 3232-bit 整数每年只需花费 0.0230.023 ton 。

序列化类型

名称TL-B包容性范围占用空间
uint8[uint8][tlb-构建]002812^{8} - 188 位 = 11 字节
uint16uint160021612^{16} - 11616 位 = 22 字节
uint32uint320023212^{32} - 13232 位 = 44 字节
uint64uint640026412^{64} - 16464 位 = 88 字节
uint128uint12800212812^{128} - 1128128 位 = 1616 字节
uint256uint25600225612^{256} - 1256256 位 = 3232 字节
int8[int8][tlb-构建]27-2^{7}2712^{7} - 188 位 = 11 字节
int16int16215-2^{15}21512^{15} - 11616 位 = 22 字节
int32int32231-2^{31}23112^{31} - 13232 位 = 44 字节
int64int64263-2^{63}26312^{63} - 16464 位 = 88 字节
int128int1282127-2^{127}212712^{127} - 1128128 位 = 1616 字节
int256int2562255-2^{255}225512^{255} - 1256256 位 = 3232 字节
int257int2572256-2^{256}225612^{256} - 1257257 位 = 3232 字节 + 11
coins[VarUInteger 16][varuint]00212012^{120} - 144124124 位之间,见下文

变量 coins 类型

在 Tact 中,coinsTL-B表示法中[VarUInteger 16][varuint]的别名,即根据存储给定整数所需的最佳字节数,它的位长是可变的,通常用于存储nanoToncoin金额。

这种序列化格式包括两个 TL-B 字段

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

也就是说,序列化为 coins 的整数占用 44124124 位(len44 位,value001515 字节),其值范围为 00212012^{120} - 1 之间。

例如

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
}

业务

所有数字的运行时计算都是在 257 位完成的,因此 溢出 非常罕见。 不过,如果任何数学运算出现溢出,就会抛出异常,事务也会失败。 可以说,Tact 的数学默认是安全的。

请注意,在同一计算中混合使用 [不同状态大小](#序列化)的变量是没有问题的。 在运行时,无论如何,它们都是相同的类型 - 257257-比特签名,因此不会发生溢出。

不过,这仍可能导致在事务的计算阶段出现***错误。 请看下面的例子:

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 tmpint = self.oneByte * 256; // 没有运行时溢出
self.oneByte = tmp; // 哎呀,tmp 值超出了 oneByte 的预期范围
}
}

这里,“oneByte” 被序列化为 uint8,只占用一个字节,范围从 002812^8 - 1,即 255255。在运行时计算中使用时,不会发生溢出,所有计算结果都是 257257 位有符号整数。但是,就在我们决定将 tmp 的值存储回 oneByte 的那一刻,我们收到了一个错误,退出代码 5,错误信息如下:整数超出预期范围。