仓颉编程语言的数值数据的几点注意事项
《仓颉编程语言数值处理要点》总结了仓颉1.0.0版本中数值处理的注意事项:1) 强制显式类型转换,不支持隐式转换,转换方式为TargetType(expr);2) 不同类型数值比较需先统一类型,浮点数需考虑精度误差;3) 通过放大比较揭示了Float16/32/64的实际精度差异(分别约3/6/15位有效数字);4) 提供了formatFloat函数实现精确的浮点数格式化输出。文中通过具体代码示例
仓颉编程语言的数值数据的几点注意事项
本文的示例在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 位小数,可以直观地暴露了 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 一文介绍的“自定义的浮点数保留 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
}
更多推荐

所有评论(0)