仓颉编程语言青少年基础教程:数据类型
本篇介绍了仓颉编程语言的数据类型系统,重点阐述了数值类型的分类和数值类型(包括整数类型与浮点数类型),其它类型以后介绍。
概述
了解仓颉编程语言的数据类型,是学习和使用这门语言的基础。仓颉作为一款面向全场景智能的静态类型、强类型语言,它的类型系统在保证程序安全性和表达丰富性方面做了不少设计。
从不同的角度可以进行不同的分类
仓颉编程语言的数据类型分为不可变类型(immutable type)和可变类型(mutable type)。
不可变类型包括数值类型(分为整数类型和浮点数类型)、字符(Rune)类型、Bool 类型、Unit 类型、Nothing 类型、字符串(String)类型、元组(Tuple)类型、区间(Range)类型、函数(Function)类型、enum 类型;可变类型包括 Array 类型、值类型数组(VArray)类型、struct 类型、class 类型和 interface 类型。
不可变类型和可变类型的区别在于:不可变类型的值,其数据值一经初始化后就不会发生变化;可变类型的值,其数据值初始化后仍然有可以修改的方法。
1.不可变类型(immutable)
共性:实例一旦创建,没有任何公开 API 能“原地”改它;所有“修改”操作都返回新实例。
• 数值
– 整数:Int8 / Int16 / Int32 / Int64 / UInt8 / UInt16 / UInt32 / UInt64 / Int / UInt
– 浮点:Float32 / Float64
• Rune (字符类型、Unicode 标量值)
• Bool (true / false)
• Unit (空元组,类似 ())
• Nothing (底类型,无值;常用于“永不返回”的函数)
• String (UTF-8 不可变字符串)
• 元组 Tuple
– 元素个数、类型固定;可整体解构,但无法修改其中任一元素。
• Range (区间类型):区间类型是一个泛型,使用 Range<T> 表示
• 函数类型 Function
– 函数对象本身不可变;捕获的变量是否可变由捕获语义决定。
• enum(枚举类型)
2.可变类型(mutable)
共性:提供 公开 mutating 方法 或 可变字段,允许在同一内存地址上修改数据。
• Array<T>(引用类型数组)其中T表示 Array 的元素类型
• VArray<T, $N> (值类型数组)
• struct
– 默认所有字段可变;可用 let/var 或 mut 关键字细化字段级可变性。
• class
– 引用语义;实例可变;字段默认可变,可用 private(set) 限制写权限。
• interface
– 描述能力,本身无存储;实现类/实例是否可变由实现决定。
– 但接口里可以声明 mutating 方法,强制实现者提供可变实现。
也可以分为:基本数据类型、复合数据类型、特殊底层、高级类型
(一)基本数据类型
(1)数值类型
• 整数类型:Int8(8 位)、Int16(16 位)、Int32(32 位)、Int64(64 位),默认整数类型为Int(根据平台自动适配位数)。
• 无符号整数:UInt8、UInt16、UInt32、UInt64,适用于非负数值场景(如索引、位运算)。
• 浮点数:Float32(单精度)、Float64(双精度,默认浮点类型),支持科学计数法(如1.2e3)。
• 特点:强类型设计,不允许隐式类型转换(如Int与Float需显式转换)。
(2)布尔类型
• 关键字Bool,取值为true或false,主要用于条件判断。
• 不支持与整数的隐式转换(如0不能代表false),确保逻辑清晰。
(3)Rune与字符串
• Rune:表示单个 Unicode 字符。
• 字符串(String):不可变序列,用双引号包裹(如"hello"),支持字符串拼接(+)、插值(如"value: ${x}")。
(二)复合数据类型
复合类型用于组织多个数据,支持复杂结构的表达:
(1)元组(Tuple)
(2)数组(Array)
(3)值类型数组(VArray)
(4)结构体(Struct)
(5)枚举(Enum)
(三)特殊类型
(1)Unit(仅有一个值 (),表示“无意义返回值”)
(2)Nothing(无实例,表示“永不返回”)
(四)高级或其它类型
(1)函数
(2)类(Class)
(3)接口(Interface)
下面将适当展开介绍,先从数值类型说起。
数值类型
数值类型包括整数类型与浮点数类型,分别用于表示整数和浮点数。整数类型包含有符号(signed)整数类型和无符号(unsigned)整数类型,其中,有符号整数类型包括 Int8、Int16、Int32、Int64 和 IntNative,分别用于表示编码长度为 8-bit、16-bit、32-bit、64-bit 和平台相关大小的有符号整数值的类型;无符号整数类型包括 UInt8、UInt16、UInt32、 UInt64 和 UIntNative,分别用于表示编码长度为 8-bit、16-bit、32-bit、 64-bit 和平台相关大小的无符号整数值的类型。浮点数类型包括 Float16、Float32 和 Float64,分别用于表示编码长度为 16-bit、32-bit 和 64-bit 的浮点数的类型。其中IntNative 和UIntNative依赖于平台的(platform dependent).
整数类型
整数类型分为有符号(signed)整数类型和无符号(unsigned)整数类型。
有符号整数类型包括 Int8、Int16、Int32、Int64 和 IntNative,分别用于表示编码长度为 8-bit、16-bit、32-bit、64-bit 和IntNative(平台相关大小的有符号整数值的类型)。
无符号整数类型包括 UInt8、UInt16、UInt32、UInt64 和 UIntNative,分别用于表示编码长度为 8-bit、16-bit、32-bit、64-bit 和UIntNative(平台相关大小的无符号整数值的类型)。
整数类型字面量在没有类型上下文的情况下默认推断为 Int64 类型,可以避免不必要的类型转换。
为了方便使用,仓颉编程语言中特别提供了一些类型别名:
– Byte 类型作为 UInt8 的类型别名,Byte 与 UInt8 完全等价。
– Int/UInt 类型分别作为 Int64/UInt64 的类型别名,Int 与 Int64 完全等价,UInt 与 UInt64 完全等价。
例如:
main(): Int64 {
let y = 1234567890123456789
println("y = ${y}")
let a: UInt8 = 128
let b: Byte = a // ok
println("a = ${a}")
println("b = ${b}")
let c: Int64 = 9223372036854775807
let d: Int = c // ok
println("c = ${c}")
println("d = ${d}")
let e: UInt64 = 18446744073709551615
let f: UInt = e // ok
println("e = ${e}")
println("f = ${f}")
return 0
}
编译运行输出:
整数类型字面量
整数类型字面量有 4 种进制表示形式:二进制(使用 0b 或 0B 前缀)、八进制(使用 0o 或 0O 前缀)、十进制(没有前缀)、十六进制(使用 0x 或 0X 前缀)。例如,对于十进制数 24,表示成二进制是 0b00011000(或 0B00011000),表示成八进制是 0o30(或 0O30),表示成十六进制是 0x18(或 0X18)。
在各进制表示中,可以使用下划线 _ 充当分隔符的作用,方便识别数值的位数,如 0b0001_1000。
在使用整数类型字面量时,可以通过加入后缀来明确整数字面量的类型,后缀与类型的对应为:
i8 Int8
i16 Int16
i32 Int32
i64 Int64
u8 UInt8
u16 UInt16
u32 UInt32
u64 UInt64
示例:
main(): Int64 {
// 十进制(默认 Int64)
let a = 42; // Int64
let b = 1_000_000i32; // Int32 1000000
// 二进制
let c = 0b0001_1000; // Int64 24
let d = 0B1010_1111u8; // UInt8 175
// 八进制
let e = 0o30; // Int64 24
let f = 0O7_777u16; // UInt16 4095
// 十六进制
let g = 0x18; // Int64 24
let h = 0xFF_u64; // UInt64 255
let i = 0XCAFE_BABEu32;// UInt32 0xCAFEBABE
println("a = ${a}")
println("b = ${b}")
println("c = ${c}")
println("d = ${d}")
println("e = ${e}")
println("f = ${f}")
println("g = ${g}")
println("h = ${h}")
println("i = ${i}")
return 0
}
编译运行输出:
a = 42
b = 1000000
c = 24
d = 175
e = 24
f = 4095
g = 24
h = 255
i = 3405691582
字符字节字面量
仓颉编程语言支持字符字节字面量,以方便使用 ASCII 码表示 UInt8 类型的值。字符字节字面量由字符 b、一对标识首尾的单引号、以及一个 ASCII 字符组成。
写法固定:b'字符' 或 b'\u{xx}'
类型固定:一定是 UInt8
取值范围:0–255(\u 后最多两位 16 进制)
例如:
main(): Int64 {
// 1) 直接写可见字符
let a = b'A' // 65u8
// 2) 直接写转义字符
let b = b'\n' // 10u8 (换行符)
// 3) 16 进制转义,等价于 0x41
let c = b'\u{41}' // 65u8
// 4) 16 进制转义,最大合法值
let d = b'\u{FF}' // 255u8
// 5) 表达式里用字节字面量做运算
let e = b'\u{90}' - b'\u{66}' + b'x' // 144 - 102 + 120 = 162u8
// 6) ❌ 超过 0xFF 会报错
// let bad = b'\u{100}' // error: byte literal out of range
println("a = ${a}")
println("b = ${b}")
println("c = ${c}")
println("d = ${d}")
println("e = ${e}")
return 0
}
编译运行输出:
a = 65
b = 10
c = 65
d = 255
e = 162
整数类型支持的操作
整数类型默认支持的操作符包括:算术操作符、位操作符、关系操作符、自增和自减操作符、复合赋值操作符。各操作符的优先级参见官方文档https://cangjie-lang.cn/docs?url=%2F1.0.0%2Fuser_manual%2Fsource_zh_cn%2FAppendix%2Foperator.html
1.算术操作符包括:一元负号(-)、加法(+)、减法(-)、乘法(*)、除法(/)、取模(%)、幂运算(**)。
o 除了一元负号(-)和幂运算(**),其他操作符要求左右操作数是相同的类型。
o *,/,+ 和 - 的操作数可以是整数类型或浮点类型。
o % 的操作数只支持整数类型。
o ** 的左操作数只能为 Int64 类型或 Float64 类型,并且:
• 当左操作数类型为 Int64 时,右操作数只能为 UInt64 类型,表达式的类型为 Int64。
• 当左操作数类型为 Float64 时,右操作数只能为 Int64 类型或 Float64 类型,表达式的类型为 Float64。
2.位操作符包括:按位求反(!)、左移(<<)、右移(>>)、按位与(&)、按位异或(^)、按位或(|)。注意,按位与、按位异或和按位或操作符要求左右操作数是相同的整数类型。
3.关系操作符包括:小于(<)、大于(>)、小于等于(<=)、大于等于(>=)、相等(==)、不等(!=)。要求关系操作符的左右操作数是相同的整数类型。
4.自增和自减操作符包括:自增(++)和自减(--)。注意,仓颉中的自增和自减操作符只能作为一元后缀操作符使用。
5.复合赋值操作符包括:+=、-=、*=、/=、%=、**=、<<=、>>=、&=、^=、|=。
算术操作符用于执行基本的数学运算,操作数通常为数值类型(整数、浮点数等)。如 a + b 表示 a 与 b 的和。
位操作符直接对数据的二进制位进行操作,操作数通常为整数类型(按二进制位逐位运算)。如:按位与(对应位都为 1 时,结果位为 1,否则为 0;如 3 & 5 即 011 & 101 = 001,结果为 1)。
关系操作符用于比较两个值的关系,结果为布尔值(true 或 false)。如判断两个值是否相等,如 a == b。
自增和自减操作符用于对变量的值进行加 1 或减 1 操作,仓颉中的自增和自减操作符只能作为一元后缀操作符使用:如a++(先使用 a 的值,再将 a 加 1),b--(先使用 b 的值,再将 b 减 1)。
复合赋值操作符将 “操作” 与 “赋值” 结合的简写形式,等价于 “变量 = 变量 操作 右值”。如a += b 等价于 a = a + b。
示例:
// int_ops.cj
main() {
/* 1. 算术操作符 ------------------------------------------------------- */
let a: Int32 = 17
let b: Int32 = 5
let c: Float64 = 2.5
let d: Float64 = 4.0
// 一元负号
let neg = -a // -17
// 同类型整数
let add = a + b // 22
let sub = a - b // 12
let mul = a * b // 85
let div = a / b // 3 (整除)
let rem = a % b // 2 (取模)
// 浮点混合
let fmul = c * d // Float64, 10.0
let fdiv = c / d // Float64, 0.625
// 幂运算
let powInt = 2i64 ** 3u64 // Int64, 8
let powFlt = 2.0f64 ** 4i64 // Float64, 16.0
let powFlt2 = 2.0f64 ** 2.5f64 // Float64, ~5.656
println("算术:")
println("neg=${neg}, add=${add}, sub=${sub}, mul=${mul}")
println("div=${div}, rem=${rem}, fmul=${fmul}, fdiv=${fdiv}")
println("powInt=${powInt}, powFlt=${powFlt}, powFlt2=${powFlt2}")
/* 2. 位操作符 --------------------------------------------------------- */
let x: UInt8 = 0b1100_1010u8
let y: UInt8 = 0b0011_1100u8
let notX = !x // 0b0011_0101
let shl = x << 2u8 // 0b0010_1000
let shr = x >> 3u8 // 0b0001_1001
let and = x & y // 0b0000_1000
let xor = x ^ y // 0b1111_0110
let or = x | y // 0b1111_1110
println("\n位运算:")
println("notX=${notX}, shl=${shl}, shr=${shr}")
println("and=${and}, xor=${xor}, or=${or}")
/* 3. 关系操作符 ------------------------------------------------------- */
let p: Int16 = 10
let q: Int16 = 20
let smaller = p < q // true
let greater = p > q // false
let equal = p == q // false
let notEqual = p != q // true
println("\n关系:")
println("smaller=${smaller}, greater=${greater}")
println("equal=${equal}, notEqual=${notEqual}")
/* 4. 自增 / 自减(只能后缀) ----------------------------------------- */
var cnt: Int32 = 10
cnt++ // 11
cnt-- // 10
println("\ncnt after ++/-- = ${cnt}")
/* 5. 复合赋值 --------------------------------------------------------- */
var v: Int64 = 6
v += 4 // 10
v -= 3 // 7
v *= 2 // 14
v /= 4 // 3
v %= 3 // 0
v = 2i64
v **= 3u64 // 8
v <<= 1 // 16
v >>= 2 // 4
v |= 0b11 // 7
v &= 0b101 // 5
v ^= 0b111 // 2
println("复合赋值后 v = ${v}")
}
运行输出:
算术:
neg=-17, add=22, sub=12, mul=85
div=3, rem=2, fmul=10.000000, fdiv=0.625000
powInt=8, powFlt=16.000000, powFlt2=5.656854
位运算:
notX=53, shl=40, shr=25
and=8, xor=246, or=254
关系:
smaller=true, greater=false
equal=false, notEqual=true
cnt after ++/-- = 10
复合赋值后 v = 2
整数类型之间、整数类型和浮点类型之间可以互相转换,整数类型可以转换为字符类型。
仓颉不支持不同类型之间的隐式转换(子类型天然是父类型,所以子类型到父类型的转换不是隐式类型转换),类型转换必须显式地进行。示例在后面的浮点类型部分介绍。
浮点类型
浮点类型包括 Float16、 Float32 和 Float64,分别用于表示编码长度为 16-bit、 32-bit 和 64-bit 的浮点数(带小数部分的数字,如 3.14159、8.24 和 0.1 等)的类型。Float16、 Float32 和 Float64 分别对应 IEEE 754 中的半精度格式(即 binary16)、单精度格式(即 binary32)和双精度格式(即 binary64)。
Float64 的精度约为小数点后 15 位,Float32 的精度约为小数点后 6 位,Float16 的精度约为小数点后 3 位。
浮点类型字面量有两种进制表示形式:十进制、十六进制。在十进制表示中,一个浮点字面量至少要包含一个整数部分或一个小数部分,没有小数部分时必须包含指数部分(以 e 或 E 为前缀,底数为 10)。在十六进制表示中,一个浮点字面量除了至少要包含一个整数部分或小数部分(以 0x 或 0X 为前缀),同时必须包含指数部分(以 p 或 P 为前缀,底数为 2)。
在使用十进制浮点数字面量时,可以通过加入后缀来明确浮点数字面量的类型,后缀与类型的对应为:
后缀 类型
f16 Float16
f32 Float32
f64 Float64
示例
// 十进制 & 十六进制浮点字面量示例(符合仓颉 1.0.0 语法)
main() {
/* 1. 十进制浮点:小数点 + 可选指数 + 后缀 */
let d1 = 3.14f16
let d2 = 42e0f32
let d3 = .5e-1f64
/* 2. 十六进制浮点:必须带 p/P 指数,后缀放在指数之前 */
let h1 = 0x1a.3f64P4 // 1a.3f × 2⁴
let h2 = 0x.7af16P3 // 0x0.7a × 2³
let h3 = 0x123f64p5 // 0x123 × 2⁵
println("十进制:")
println("d1 = ${d1}") // Float16,d1 = 3.140625
println("d2 = ${d2}") // Float32,d2 = 42.000000
println("d3 = ${d3}") // Float64,d3 = 0.050000
println("十六进制:")
println("h1 = ${h1}") // Float64,h1 = 419.961914
println("h2 = ${h2}") // Float16,h2 = 3.841965
println("h3 = ${h3}") // Float64,38268032.000000
}
编译运行:
浮点类型默认支持的操作符包括:算术操作符、关系操作符、复合赋值操作符。
浮点类型不支持自增和自减操作符。
示例
// float_ops.cj
main() {
let a: Float64 = 3.5
let b: Float64 = 2.0
let c: Float32 = 1.5f32
let d: Float32 = 4.0f32
/* 1. 算术操作符 ------------------------------------------------------- */
let neg = -a // 一元负号
let add = a + b // 5.5
let sub = a - b // 1.5
let mul = a * b // 7.0
let div = a / b // 1.75
let pow = 2.0f64 ** 3 // Float64 的幂运算,8.0
let pow2 = 3.0f64 ** 2.5f64 // Float64 的幂运算,~15.588
println("算术:")
println("neg=${neg}, add=${add}, sub=${sub}")
println("mul=${mul}, div=${div}, pow=${pow}, pow2=${pow2}")
/* 2. 关系操作符 ------------------------------------------------------- */
let lt = a < b // false
let gt = a > b // true
let le = c <= d // true
let ge = c >= d // false
let eq = a == b // false
let ne = a != b // true
println("\n关系:")
println("lt=${lt}, gt=${gt}, le=${le}, ge=${ge}")
println("eq=${eq}, ne=${ne}")
/* 3. 复合赋值操作符 --------------------------------------------------- */
var x: Float64 = 10.0
x += 2.5 // 12.5
x -= 1.0 // 11.5
x *= 3.0 // 34.5
x /= 5.0 // 6.9
x **= 2.0 // 47.61
println("\n复合赋值后 x = ${x}")
/* 4. 自增/自减(不支持,以下两行会编译错误) */
// x++ // ❌ 浮点类型不支持 ++
// x-- // ❌ 浮点类型不支持 --
}
运行输出:
运行输出:
算术:
neg=-3.500000, add=5.500000, sub=1.500000
mul=7.000000, div=1.750000, pow=8.000000, pow2=15.588457
关系:
lt=false, gt=true, le=true, ge=false
eq=false, ne=true
复合赋值后 x = 47.610000
如何比较两个数值是否相等
类型必须完全一致
如果类型不同,要先显式转换到同一类型,否则编译器直接报错。
整数直接 ==
浮点要近似(容忍误差)判断 |a-b| < ε
示例
import std.math.abs //绝对值
main() {
// 1.同类型:直接用 ==
let a: Int32 = 100
let b: Int32 = 100
println(a == b) // true
//2。不同类型:先转换再 ==
let i8: Int8 = 127
let i64: Int64 = 127
// 必须显式转换
println(Int64(i8) == i64) // true
let f32: Float32 = 3.1415926
let f64: Float64 = 3.1415926
// 转成同一精度
println(Float64(f32) == f64) // false(浮点数, 精度不同)
//浮点数可用近似(容忍误差)判断 |a-b| < ε
println(abs(Float64(f32) - f64) < 1e-9) // false
println(abs(Float64(f32) - f64) < 1e-5) //true
}
浮点类型之间、浮点类型和整数类型之间可以互相转换。
数值类型转换规则
仓颉不支持不同类型之间的隐式转换(子类型天然是父类型,所以子类型到父类型的转换不是隐式类型转换),类型转换必须显式地进行。
所有数值类型之间必须显式转换,语法统一为 TargetType(expr),不支持任何隐式转换。
例如
源类型 |
目标类型 |
提示 |
Int8 → Int64 |
Int64(表达式) |
小转大,永不过载 |
Float64 → Float32 |
Float32(表达式) |
精度降低 |
Float32 → Int32 |
Int32(表达式) |
截断小数 |
Rune → UInt32 |
UInt32(表达式) |
取 Unicode 码位 |
UInt32 → Rune |
Rune(表达式) |
需合法码位 |
注意:溢出时报错。
精度损失:浮点转整数直接截断小数;高位转低位浮点会舍入。
转换方向 |
说明 |
整数 → 整数 |
小→大:值不变;大→小:溢出时抛异常 |
整数 → 浮点 |
按最近舍入得到浮点值,可能损失精度 |
浮点 → 浮点 |
按 IEEE-754 最近舍入,精度降低 |
浮点 → 整数 |
丢弃小数部分,溢出抛异常 |
字符 ↔ 整数 |
仅对合法的 Unicode scalar value |
示例:
main() {
// 整数互转
let a: Int8 = 10
let b: Int16 = Int16(a) // Int8 → Int16
// 浮点互转
let c: Float32 = 3.1415926
let d: Float64 = Float64(c) // Float32 → Float64
// 整数 ↔ 浮点
let e: Int64 = 1024
let f: Float64 = Float64(e) // Int64 → Float64
let g: Int64 = Int64(123.7) // Float64 → Int64,g = 123
//a = 10, b = 10, d = 3.141592502593994, f = 1024.000000, g = 123
println("a = ${a}, b = ${b}, d = ${d}, f = ${f}, g = ${g}")
let big: Int32 = 83647
println("big = ${big}") //big = 83647
//let small = Int8(big) // 超出 Int8 范围
}
【类型转换语法及规则官方文档
https://cangjie-lang.cn/docs?url=%2F0.53.18%2FSpec%2Fsource_zh_cn%2FChapter_02_Types%28zh%29.html 】
一个现象的特别说明
Float16、 Float32 和 Float64 分别对应 IEEE 754 中的半精度格式(即 binary16)、单精度格式(即 binary32)和双精度格式(即 binary64)。Float64 的精度约为小数点后 15 位,Float32 的精度约为小数点后 6 位,Float16 的精度约为小数点后 3 位。
先看示例代码:
main() {
let a: Float16 = 3.14159265356979323846
let b: Float32 = 3.14159265356979323846
let c: Float64 = 3.14159265356979323846
println("a = ${a}") // 默认输出 6 位 → a = 3.140625,明显可见Float16 精度不足
println("b = ${b}") // 默认输出 6 位 → b = 3.141593,?
println("c = ${c}") // 默认输出 6 位 → c = 3.141593,?
// 放大小数部分差距,取 12 位小数
let af16 = Int64((Float64(a) - 3.0) * 1e12)
let bf32 = Int64((Float64(b) - 3.0) * 1e12)
let cf64 = Int64((c - 3.0) * 1e12)
println("a 小数部分 ×1e12 = ${af16}") // a 小数部分 ×1e12 = 140625000000
println("b 小数部分 ×1e12 = ${bf32}") // b 小数部分 ×1e12 = 141592741012
println("c 小数部分 ×1e12 = ${cf64}") // c 小数部分 ×1e12 = 141592653569
}
和初始值对比,a = 3.140625明显可见Float16 精度,后两者b = 3.141593和c = 3.141593输出都是3.141593,为什么?
println 在打印浮点数时,默认只保留 6 位有效数字,所以它把 Float32 和 Float64 都四舍五入到 3.141593 后就不再往下显示了;真正的二进制差异被“隐藏”了。明显可见Float16 精度不足。
放大小数部分差距,如取 12 位小数,可以直观地暴露了 Floa16、Float32 与 Float64 的精度差异,示例代码如下:
main() {
let a: Float16 = 3.14159265356979323846
let b: Float32 = 3.14159265356979323846
let c: Float64 = 3.14159265356979323846
println("a = ${a}") // 默认输出 6 位 → a = 3.140625,明显可见Float16 精度不足
println("b = ${b}") // 默认输出 6 位 → b = 3.141593,?
println("c = ${c}") // 默认输出 6 位 → c = 3.141593,?
// 放大小数部分差距,取 12 位小数
let af16 = Int64((Float64(a) - 3.0) * 1e12)
let bf32 = Int64((Float64(b) - 3.0) * 1e12)
let cf64 = Int64((c - 3.0) * 1e12)
println("a 小数部分 ×1e12 = ${af16}") // a 小数部分 ×1e12 = 140625000000
println("b 小数部分 ×1e12 = ${bf32}") // b 小数部分 ×1e12 = 141592741012
println("c 小数部分 ×1e12 = ${cf64}") // c 小数部分 ×1e12 = 141592653569
}
对此更多情况参见https://blog.csdn.net/cnds123/article/details/149668610 一文。