整数
TON 智能合约中的算术运算始终使用整数,从不使用浮点数,因为浮点数是不可预测的。 因此,重点应放在整数及其处理上。
Int
是一个 257 位的有符号整数类型。
它能够存储 和 之间的整数。
表示法
Tact 支持多种方式编写 Int
的原始值作为整数字面量。
大多数符号允许在数字之间添加下划线 (_
),但下列符号除外:
- 字符串表示法,如 NanoToncoins 案例所示。
- 带前导零的十进制数 。一般不鼓励使用,参见 下文。
此外,不允许在 中连续使用多个下划线,或在 中使用尾部下划线。
十进制
最常见、最常用的数字表示方法,使用十进制数字系统: 。
您可以使用下划线(_
)来提高可读性: 等于 。
十六进制
使用十六进制数字系统表示数字,用 (或 )前缀表示:。
使用下划线(_
)提高可读性: 等于 。
八进制
使用八进制数字系统表示数字,用 (或 )前缀表示:。
使用下划线(_
)提高可读性: 等于 。
二进制
使用二进制数字系统表示数字,用 (或 )前缀表示:。
使用下划线(_
)提高可读性: 等于 。
NanoToncoins
与美元的运算要求小数点后保留两位小数——这些用于表示美分(cents)的值。 但是,如果我们只能用整数来表示数字 $ ,我们该如何表示呢? 解决的办法是直接使用 cents。 这样, $ 就变成了 美分。 我们只需记住最右边的两位数代表小数点后的数字。
同样,在使用 TON 区块链的主要货币 Toncoin 时,需要九位小数,而不是两位小数。 可以说,nanoToncoin是Toncoin的 。
因此, Toncoin 的数量,可以用 Tact 表示为 ton("1.25")
,实际上就是数字 。 我们称这样的数字为_nanoToncoin(s)(或_nano-ton(s))而不是_美分。
序列化
将 Int
值编码为持久状态(contracts 和 traits 的字段)时,通常最好使用比 -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; }}
整数序列化也适用于 Structs 和 Messages 的字段,以及 maps 的键/值类型:
struct StSerialization { martin: Int as int8;}
message MsgSerialization { seamus: Int as int8; mcFly: map<Int as int8, Int as int8>;}
动机很简单:
- 在状态中存储 个 位的整数成本大约为每年 TON。
- 相比之下,存储 -bit 整数每年只需花费 ton 。
常见序列化类型
名称 | TL-B | 包含的范围 | 占用空间 |
---|---|---|---|
uint8 | uint8 | to | bits = byte |
uint16 | uint16 | to | bits = bytes |
uint32 | uint32 | to | bits = bytes |
uint64 | uint64 | to | bits = bytes |
uint128 | uint128 | to | bits = bytes |
uint256 | uint256 | to | bits = bytes |
int8 | int8 | to | bits = byte |
int16 | int16 | to | bits = bytes |
int32 | int32 | to | bits = bytes |
int64 | int64 | to | bits = bytes |
int128 | int128 | to | bits = bytes |
int256 | int256 | to | bits = bytes |
int257 | int257 | to | bits = bytes + bit |
coins | VarUInteger 16 | to | between and bits,见下文 |
任意位宽类型
Available since Tact 1.5除了 常见序列化类型,还可以使用前缀 int
或 uint
后跟数字来指定任意位宽的整数。 例如,写入 int7
表示有符号 - 位整数。
Int
类型的最小允许位宽是 ,而对于 int
前缀(有符号整数)最大是 ,对于 uint
前缀(无符号整数)最大是 。
名称 | TL-B | 包含的范围 | 占用空间 |
---|---|---|---|
uintX | uintX | to | bits, where is between and |
intX | intX | to | bits, where is between and |
变量 coins
类型
在 Tact 中,coins
是TL-B表示法中VarUInteger 16
的别名,即根据存储给定整数所需的最佳字节数,它的位长是可变的,通常用于存储nanoToncoin金额。
这种序列化格式包括两个 TL-B 字段:
len
,一个 位无符号大二进制整数,存储所提供值的字节长度value
,即所提供值的 位无符号大二进制表示法
也就是说,序列化为 coins
的整数占用 至 位(len
为 位,value
为 至 字节),其值范围为 至 之间。
例如
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 的数学默认是安全的。
请注意,在同一计算中混合使用 不同状态大小 的变量是没有问题的。 在运行时,无论是什么,它们都是相同类型的——-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
,只占用一个字节,范围从 到 ,即 。 在运行时计算中使用时,不会发生溢出,所有计算结果都是 - 位有符号整数。 但是,就在我们决定将 tmp
的值存储回 oneByte
的那一刻,我们收到了一个错误,退出码 5,错误信息如下:整数超出预期范围。