跳转到内容

整数

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

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

表示法

Tact 支持多种方式编写 Int 的原始值作为整数字面量

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

  • 字符串表示法,如 NanoToncoins 案例所示。
  • 带前导零的十进制数 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}

NanoToncoins

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

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

因此, 1.251.25 Toncoin 的数量,可以用 Tact 表示为 ton("1.25"),实际上就是数字 12500000001250000000。 我们称这样的数字为_nanoToncoin(s)(或_nano-ton(s))而不是_美分

序列化

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

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

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

整数序列化也适用于 StructsMessages 的字段,以及 maps 的键/值类型:

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

动机很简单:

  • 在状态中存储 10001000257257 位的整数成本大约为每年 0.1840.184 TON。
  • 相比之下,存储 10001000 3232-bit 整数每年只需花费 0.0230.023 ton 。

常见序列化类型

名称TL-B包含的范围占用空间
uint8uint800 to 2812^{8} - 188 bits = 11 byte
uint16uint1600 to 21612^{16} - 11616 bits = 22 bytes
uint32uint3200 to 23212^{32} - 13232 bits = 44 bytes
uint64uint6400 to 26412^{64} - 16464 bits = 88 bytes
uint128uint12800 to 212812^{128} - 1128128 bits = 1616 bytes
uint256uint25600 to 225612^{256} - 1256256 bits = 3232 bytes
int8int827-2^{7} to 2712^{7} - 188 bits = 11 byte
int16int16215-2^{15} to 21512^{15} - 11616 bits = 22 bytes
int32int32231-2^{31} to 23112^{31} - 13232 bits = 44 bytes
int64int64263-2^{63} to 26312^{63} - 16464 bits = 88 bytes
int128int1282127-2^{127} to 212712^{127} - 1128128 bits = 1616 bytes
int256int2562255-2^{255} to 225512^{255} - 1256256 bits = 3232 bytes
int257int2572256-2^{256} to 225612^{256} - 1257257 bits = 3232 bytes + 11 bit
coinsVarUInteger 1600 to 212012^{120} - 1between 44 and 124124 bits,见下文

任意位宽类型

Available since Tact 1.5

除了 常见序列化类型,还可以使用前缀 intuint 后跟数字来指定任意位宽的整数。 例如,写入 int7 表示有符号 77- 位整数。

Int 类型的最小允许位宽是 11,而对于 int 前缀(有符号整数)最大是 257257,对于 uint 前缀(无符号整数)最大是 256256

名称TL-B包含的范围占用空间
uintXuintX00 to 2X12^{X} - 1XX bits, where XX is between 11 and 256256
intXintX2X1-2^{X - 1} to 2X112^{X - 1} - 1XX bits, where XX is between 11 and 257257

变量 coins 类型

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

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

  • len,一个 44位无符号大二进制整数,存储所提供值的字节长度
  • value,即所提供值的 8len8 * 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-bit带符号,因此不会发生溢出。

然而,这仍可能导致交易的计算阶段错误。 请看下面的例子:

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

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