仓颉编程语言的数值数据的几点注意事项

本文的示例在Windows10,当前仓颉编程语言LTS 1.0.0中通过。

数值类型转换规则

所有数值类型之间必须显式转换,语法统一为 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 范围

}

如何比较两个数值是否相等

类型必须完全一致

    如果类型不同,要先显式转换到同一类型,否则编译器直接报错。

    整数直接 ==

    浮点要近似(容忍误差)判断  |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 
}

一个现象的特别说明

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 位小数,可以直观地暴露了 Floa16Float32 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 一文介绍的“自定义的浮点数保留 n 位小数(四舍五入)函数”显示不同,源码如下:

// 自定义formatFloat函数
// 浮点数保留 n 位小数(四舍五入)函数
// 在7 月推出首个长期支持版本(LTS 1.0.0)通过
// 第一个参数浮点数;第二个参数是小数位数
// 真正的实现只接受 Float64
private func formatFloat(v: Float64, digits: Int64): String {
    if (digits <= 0) {
        return Int64(v).toString()
    }

    // 1. 放大 10^n 倍
    var scale: Int64 = 1
    for (_ in 0..digits) { scale *= 10 }

    // 2. 加 0.5 后截断,实现四舍五入
    let scaled  = v * Float64(scale)
    let rounded = if (scaled >= 0.0) { scaled + 0.5 } else { scaled - 0.5 }
    let n       = Int64(rounded)

    // 3. 拆整数 / 小数
    let intPart  = (n / scale).toString()
    var fracPart = (if (n < 0) { -n } else { n } % scale).toString()

    // 4. 左侧补零
    while (fracPart.size < digits) { fracPart = "0" + fracPart }

    return "${intPart}.${fracPart}"
}

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,?
    
    // 使用formatFloat函数
    let af16 = formatFloat(Float64(a),10)  //Float64(a)
    println("af16 = ${af16}")  // af16=3.1406250000 
    let bf32 = formatFloat(Float64(b),10)  //Float64(b)
    println("bf32 = ${bf32}")  // bf32=3.1415927410 
    let cf64 = formatFloat(c,12)
    println("cf64 = ${cf64}")  // cf64=3.141592653570
}

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐