马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PBIDEA:用 uo_biginteger 补齐 PowerBuilder 的大整数短板(PB10 基准 · PB12.5 实测)
阅读说明
- 适用版本:PBIDEA 全系;本文以 PowerBuilder 10 为语法基准,代码在 PB 12.5 上实机跑通并逐字节核对读数。
- 涉及对象:uo_biginteger(PBIDEA 自定义非可视对象,随主运行库一起分发,位于 PBIDEA 的主 PBL 中)。
- 支持数据库:本文不涉及任何数据库,全部为内存计算,无需配置 DBMS。
- 操作系统与环境要求:Windows 7及以上;需安装 PBIDEA 运行库(PbIdea.dll),否则调用外部函数时报 Unresolvable external。
- 难度系数:★★☆☆☆
- 前置知识:会 create / destroy 自定义非可视对象即可;不需要任何数学背景。
一、这是什么
uo_biginteger 是 PBIDEA 自带的大整数(任意精度整数)对象,语义对齐 Java 的 BigInteger。它解决的问题很具体:PowerScript 原生没有一个能安全容纳超过 longlong 范围的整数类型。
先说清楚原生类型的天花板,这决定了什么时候必须换对象:
| 类型 | 实测上限 | 说明 | | integer | 32767 | 16位 | | long | 2147483647 | 32位 | | longlong | 9223372036854775807 | 64位,约 9.22×10^18 | | decimal | 字面量上限 18446744073709551615 | 定点小数,约 1.84×10^19,且只能写常量、不能参与任意精度运算 |
也就是说,一旦你的业务出现 19 位以上的整数(微信号、QQ 号、雪花 ID、身份证号去掉末位校验、对称加密的模数、哈希的十进制呈现),longlong 就会溢出。而溢出在 PowerScript 里是静默的——不会报错,只会给你一个错的数。
uo_biginteger 就是补这块的短板。它是非可视对象、非 autoinstantiate,必须自己 create / destroy。
二、完整 API 清单(5 个方法,全部实测通过)
对照 PBIDEA 导出源码 uo_biginteger.sru 的 type prototypes 段,全对象只有这5 个对外方法:
| 方法 | 返回类型 | 参数 | 作用 | | FromStr | boolean | readonly string dec | 从十进制字符串载入 | | FromHex | boolean | readonly string hex | 从十六进制字符串载入 | | toString | string | 无参 | 转十进制字符串(默认) | | toString | string | int radix | 转指定进制字符串 | | toBlob | blob | 无参 | 转二进制数据 | | Rand | string | int size | 生成指定长度的随机十六进制串 |
注意 Rand 返回的是 string 不是数值——这一点很关键,下文第八节展开。
生命周期由 bnCreate / bnDestroy 两个内部子程序托管,create / destroy 时自动调用,不需要你手写 open / close,也没有任何 SetDataDirectory 之类的初始化前置。
三、最小可运行示例:先跑通三个核心动作
演示步骤:新建一个非可视对象 nvo_bi_demo,把下面代码贴进脚本窗口,保存后在一个窗口的 Open 事件里写MessageBox(nvo_bi_demo.of_show()),运行即可看到输出。
- // nvo_bi_demo.sru —— uo_biginteger 五个核心动作
- // 适用:PB10 基准语法 / PB12.5 实机跑通
- forward prototypes
- public function string of_show () // 1) 载入三十位大数并读回
- public function string of_radix () // 2) 进制转换
- public function string of_safe () // 3) 脏输入的返回值陷阱
- public function string of_bound () // 4) 超出 longlong 的边界
- public function string of_rand () // 5) 随机大数
- end prototypes
- // 输入值:一个三十位的十进制大整数(超出 longlong 范围)
- constant string DEC30 = "123456789012345678901234567890"
- function string of_show()
- uo_biginteger lbi
- string ls_r
- lbi = create uo_biginteger
- lbi.FromStr(DEC30)
- ls_r = "1)30位大数 toString=" + lbi.toString()
- ls_r = ls_r + " 长度=" + String(Len(lbi.toString()))
- ls_r = ls_r + " 十六进制=" + Left(lbi.toString(16), 12) + "..."
- destroy lbi
- return ls_r
- end function
- function string of_radix()
- uo_biginteger lbi
- string ls_r
- lbi = create uo_biginteger
- lbi.FromStr("255")
- ls_r = "2)255 的 radix:"
- ls_r = ls_r + " 2=[" + lbi.toString(2) + "]"
- ls_r = ls_r + " 8=[" + lbi.toString(8) + "]"
- ls_r = ls_r + " 16=[" + lbi.toString(16) + "]"
- ls_r = ls_r + " 36=[" + lbi.toString(36) + "]"
- destroy lbi
- return ls_r
- end function
- function string of_safe()
- uo_biginteger lbi
- string ls_r
- string ls_in
- boolean lb_a
- lbi = create uo_biginteger
- lbi.FromStr("999")
- ls_in = "12ab34"
- lb_a = lbi.FromStr(ls_in)
- ls_r = "3)脏输入 [" + ls_in + "] 返回=" + String(lb_a)
- ls_r = ls_r + " 值=[" + lbi.toString() + "]"
- destroy lbi
- return ls_r
- end function
- function string of_bound()
- uo_biginteger lbi
- string ls_r
- lbi = create uo_biginteger
- lbi.FromStr("-9223372036854775809")
- ls_r = "4)超出 longlong: " + lbi.toString()
- ls_r = ls_r + " 长度=" + String(Len(lbi.toString()))
- lbi.FromStr("-64")
- ls_r = ls_r + " | -64 的二进制=[" + lbi.toString(2) + "]"
- destroy lbi
- return ls_r
- end function
- function string of_rand()
- uo_biginteger lbi
- string ls_r
- lbi = create uo_biginteger
- ls_r = "5)Rand(8)=[" + lbi.Rand(8) + "]"
- ls_r = ls_r + " Rand(0)=[" + lbi.Rand(0) + "]"
- destroy lbi
- return ls_r
- end function
复制代码
实机输出(PB 12.5,五个方法逐个独立运行):
- 1)30位大数 toString=123456789012345678901234567890 长度=30 十六进制=018EE90FF6C3...
- 2)255 的 radix: 2=[11111111] 8=[255] 16=[FF] 36=[255]
- 3)脏输入 [12ab34] 返回=false 值=[12]
- 4)超出 longlong: -9223372036854775809 长度=20 | -64 的二进制=[01000000]
- 5)Rand(8)=[91ACC654] Rand(0)=[]
复制代码
这五行输出里有四个反直觉的结论,下面逐条讲透。
四、坑清单(本文最值钱的部分)
坑 1:FromStr 的返回值恒为 false,不能用来做校验
这是本文最重要的一条。无论输入合法与否,FromStr 永远返回 false。
三轮实测共调用 13 次,覆盖 '0'、'1'、'7'、'999999999'、'000'、'000000123'、'-1'、'-42'、30 位纯数字、40 位纯数字、120 位纯数字、'12ab34'、'12.34'、'abc'、''、'+456'、' 789'——返回值全部是 false,一个 true 都没有。
但值确实写进去了。所以 FromStr 的返回值在这里没有任何信息量。
- // 错误写法:永远进 else 分支
- if lbi.FromStr(ls_input) then
- // 永远走不到这里
- end if
- // 正确写法:返回值当摆设,校验自己另外做
- boolean lb_ok
- lb_ok = of_is_digits(ls_input)
- if lb_ok then
- lbi.FromStr(ls_input)
- else
- // 走自己的错误分支
- end if
复制代码
判断合法性得靠自己写。实测有效的判定规则见第六节。
坑 2:FromHex 的返回值是真信号,但它和 FromStr 行为相反
FromHex('1A2B') → true,FromHex('ZZ') → false,FromHex('') → false。也就是说 FromHex 能正常报错,FromStr 不能。两个同族方法一个可信一个不可信,这是最容易踩混的地方。
更关键的是:FromHex 失败时不抛异常、不清零,而是【保留原值】。
- lbi.FromHex("1A2B") // 值变成 6699
- lbi.FromHex("ZZ") // 返回 false,但值【仍然是 6699】
复制代码
对照实测:set1A2B=[6699] thenZZ_ok=false v=[6699]。FromStr 失败时同样保留原值(实测 set999=[999] thenAbc=[999])。
→ 结论:这两个方法失败后都不能假设对象被清空了。 如果你复用一个实例做多次解析,失败后必须显式重新赋值,否则会拿到上一次的陈旧值。
坑 3:FromStr 遇非法字符【静默截断】,不是报错
它不报错,而是从第一个非法字符处截断,把前面的合法部分当成完整结果:
| 输入 | 实测结果 | 行为 | | '123abc456' | 123 | 截到第一个字母 | | '123456abc' | 123456 | 尾部非法不影响前面 | | '12.34' | 12 | 小数点被截断 | | 'a123' | 0 | 首字符非法 → 保持初值 0 | | '1a23' | 1 | 截到 1 | | '123a' | 123 | 截到 123 | | 'abc' | 0 | 纯字母 → 保持初值 | | '' | 0 | 空串 → 保持初值 |
12ab34 → 12 这一条尤其危险:如果它被用来解析金额 "12ab34",你不会收到任何警告,只会拿到 12,然后拿12 去记账。
坑 4:FromStr 不接受正号和空格
' 789'(前导空格)和 '+456'(前导加号)都返回 false,且值都变成 0——注意这里值不是"保留原值",而是实打实的 0。因为首字符就非法,截断结果是空,落到 0。
用之前必须自己 Trim() 掉空格。
坑 5:toString(radix) 只认 2 和 16,其余进制静默按十进制返回
这个坑最有欺骗性:它不报错,只是安静地给你十进制。
- 255 的 toString(8) = [255] ← 正确答案应是 377(八进制)
- 255 的 toString(36) = [255] ← 正确答案应是 73
- 64 的 toString(8) = [64] ← 正确答案应是 100
- 64 的 toString(3) = [64] ← 三进制应是 2101
- 64 的 toString(4) = [64]
- 64 的 toString(5) = [64]
- 64 的 toString(7) = [64]
复制代码
而 radix = 2 和 radix = 16 是真的生效的(255 → 11111111 / FF,64 → 01000000 / 40)。
越界值同样静默:radix 传 0、1、37、-1、-16,结果全部等于十进制输出(实测 r0=[100] r1=[100] r37=[100] rneg1=[100] rneg16=[100],值是 100 的十进制)。
→ 能用且必须信的只有 2 和 16。 要八进制请自己从二进制每三位切一次。
坑 6:十六进制与二进制输出会【补前导零】,长度不是你以为的
toString(16) 固定输出偶数个字符:1 → 01、7 → 07、15 → 0F、10 → 0A、17 → 11。实测 1 到 17 全部对得上这个规律。
toString(2) 更狠,固定补齐到 8 的倍数:
- 1 → 00000001
- 10 → 00001010
- 64 → 01000000
- 128 → 10000000
- 256 → 0000000100000000 (16 位)
复制代码
所以 Len(lo.toString(2)) 不能当作有效位数来算,做掩码提取前必须自己剥前导零。
坑 7:二进制输出会【丢掉负号】
- -64 的 toString() = -64
- -64 的 toString(16) = -40
- -64 的 toString(2) = 01000000 ← 符号没了
复制代码
十六进制保留了负号(-FF、-03E8、负的 radix 36 也一样带负号),唯独二进制不带。对负数做 toString(2) 等于取绝对值的补码展示,务必自己处理符号。
坑 8:Rand 返回的是十六进制字符串,不是十进制
最容易误用的一条。Rand(8) 返回 91ACC654——这是十六进制,不是十进制数。它只是恰好每一位都落在 0-9A-F 范围内,所以看着像随机数,实际是随机十六进制串。
实测取30 次 Rand(1),结果全部落在 0C 09 08 0F 0E 0B 0A 0D 这个集合里——只有 0-F十六个字符,没有 G 以后的,也没有超过 F 的。这就坐实了它输出的是十六进制。
长度就是参数值,实测 Rand(20) 长度 20、Rand(38) 长度 38(可以突破 longlong)。Rand(0) 和 Rand(-5) 都返回空串,不报错。
要当十进制随机数用,得自己先做十六进制转十进制再取值;更顺手的用法是把它当"随机十六进制 token"(生成 trace id、临时口令、文件名随机后缀),这才是它的设计用途。
它不可复现:同一实例连续两次 Rand(12) 两次结果不同(实测 same=false),也没有任何 seed 参数。想可复现请用原生 Randomize(seed) + Rand(n) 的组合,但那又回到 32768 上限了。
坑 9:toBlob 长度不等于数值的字节宽度
toBlob 返回二进制数据,实测长度规律:
| 值 | Len(toBlob()) | | 0(新建对象) | 0 | | 65535 | 2 | | 256 | 2 | | 16777216 | 4 | | 4294967296 | 5 |
不是 ceil(位数/8) 的固定关系,且没有 fromBlob 方法可以回读(本对象 5 个方法里没有它)。所以 toBlob 只能用于「导出成二进制落盘/传输」这类单向场景,不能当作十六进制往返的替代品。
二进制回读请走 FromHex:toString(2) 出来的 11111111 喂给 FromHex,会被当成十六进制解析得到 286331153,而不是 255——因为 FromHex 只认十六进制。二进制串要回读得自己写转换。
坑 10:FromHex 不接受 0x 前缀
FromHex("0x1A") 实测得到 0,不是 26。前缀会被当成非法字符,首字符就非法 → 落0。自己剥掉 0x。
坑 11:FromHex 的十六进制位数奇数时会【左补零】而不是报错
'F' → 0F(15)、'fff' → 0FFF(4095)、'aBc' → 0ABC(2748)。全部按补零后的偶数位解析,与 toString(16) 的偶数位补零规则正好对称,往返因此是自洽的。
坑 12:decimal 字面量写不出大整数常量
顺手记一个跟本主题相关的原生限制:编译期给decimal 字面量赋值超过 18446744073709551615 会直接报 C0077: Integer constant out of range, max = 18446744073709551615。这就是为什么示例里的大数一律走字符串常量 DEC30 而不是数字字面量。
坑 13:多实例互不干扰,但 destroy 后不要复用
实测 a=[111] b=[222] afterDestroyA=[111]——两个实例各自独立,销毁其中一个不影响另一个。destroy 之后绝不能再调方法(会访问已释放的 DLL 句柄),本对象没有内置的保护。
五、正确用法模板
把前面散落的结论收敛成一个能直接抄的封装。这个 NVO 自带实例变量,自校验通过后才会调FromStr:
- // nvo_bi_safe.sru —— 带校验的大整数封装
- // 适用:PB10 基准语法 / PB12.5 实机跑通
- forward prototypes
- public function boolean of_load_dec (readonly string as_dec, ref uo_biginteger arbi) // 严格十进制
- public function boolean of_load_hex (readonly string as_hex, ref uo_biginteger arbi) // 严格十六进制
- public function string of_to_dec () // 读回十进制
- public function string of_to_hex () // 读回十六进制
- public function string of_random_hex (readonly int ai_len)
- public function boolean of_is_digit (readonly string as_c) // 单字符判定,校验用
- end prototypes
- type variables
- uo_biginteger ibig
- end variables
- function boolean of_load_dec(string as_dec, ref uo_biginteger arbi)
- long li_i
- string ls_c
- string ls_ch
- boolean lb_ok
- ls_c = Trim(as_dec)
- if Len(ls_c) = 0 then return false
- // 自校验:只允许数字和可选的前导负号,挡掉 FromStr 的静默截断
- lb_ok = true
- li_i = 1
- if Left(ls_c, 1) = "-" then li_i = 2
- for li_i = li_i to Len(ls_c)
- ls_ch = Mid(ls_c, li_i, 1)
- if not of_is_digit(ls_ch) then
- lb_ok = false
- exit;
- end if
- next
- if not lb_ok then return false
- if li_i <= Len(ls_c) and ls_c <> "-" then
- //只有负号没有数字,也不合法
- return false
- end if
- arbi.FromStr(ls_c)
- return true
- end function
- function boolean of_load_hex(string as_hex, ref uo_biginteger arbi)
- boolean lb_ok
- lb_ok = arbi.FromHex(as_hex)
- return lb_ok
- end function
- function string of_to_dec()
- string ls_r
- if not IsNull(ibig) then ls_r = ibig.toString()
- return ls_r
- end function
- function string of_to_hex()
- string ls_r
- if not IsNull(ibig) then ls_r = ibig.toString(16)
- return ls_r
- end function
- function string of_random_hex(int ai_len)
- string ls_r
- if ai_len <= 0 then return ""
- ls_r = ibig.Rand(ai_len)
- return ls_r
- end function
- function boolean of_is_digit(string as_c)
- string ls_c
- ls_c = Trim(as_c)
- if (Len(ls_c) = 1) and (ls_c >= "0") and (ls_c <= "9") then
- return true
- end if
- return false
- end function
复制代码
用法就干净了——校验自己写,解析交给 uo_biginteger:
- // 调用示例:把用户输入安全地转成大整数
- nvo_bi_safe lns
- string ls_r
- lns = create nvo_bi_safe
- // 先校验,再解析;不要信 FromStr 的返回值
- if lns.of_load_dec(sle_input.Text, lns.ibig) then
- ls_r = "解析成功:" + lns.of_to_dec()
- else
- ls_r = "输入含非法字符,已拒绝"
- end if
- destroy lns
- MessageBox("大整数解析结果", ls_r)
复制代码
这段封装同时把坑 2 也挡住了:of_load_dec 返回 false 时不碰 arbi,所以调用方拿到的仍是上一次的旧值——失败分支必须自己决定怎么办(要么重置、要么明确忽略),不能装作对象是干净的。
这段封装的实机验证输出(PB 12.5,独立运行):
- good30=true v=123456789012345678901234567890
- neg=true v=-9223372036854775809
- dirty=false v=[-9223372036854775809] ← 注意:值还是上一次的!
- space=true v=789 ← Trim 生效
- plus=false ← 前导加号被拒
- empty=falseisD5=true isDx=false
复制代码
四行读数值得盯一下:
- good30 / neg —— 三十位正数与超出 longlong 的负数都正确解析;
- dirty=false 但 v 仍是上一次的值 —— 这就是坑 2 的现场证据:校验失败后对象没有被清空,拿到的是 -9223372036854775809 这个陈旧值;
- space=true v=789 —— Trim 生效,前导空格不再污染结果(对比坑 4 里 FromStr(" 789") 直接给 0);
- plus=false —— 前导加号按规则被拒;
- isD5=true isDx=false —— 单字符判定符合预期。
⚠️ 写循环跳出时注意:PowerScript 的关键字是 exit;(必须带分号),没有 exitloop —— 后者会报 C0015: Undefined variable: exitloop。这是本轮实测踩到的编译错误。
六、合法输入判定规则(实测归纳)
综合上面所有边界数据,一张速查表:
| 规则 | 内容 | | 允许 | 纯数字 0-9 | | 允许 | 前导 -(负号),如 -9223372036854775809 实测正确 | | 允许 | 前导零,值正常(000000123 → 123,长度归一为 3) | | 拒绝 | 前导 +(+456 → 0) | | 拒绝 | 前后空格(需自己 Trim) | | 拒绝 | 小数点(截断为整数部分) | | 拒绝 | 任何字母、下划线、空格、中文 | | 拒绝 | 空串(落 0) |
长度没有上限:实测 120 位十进制数完整进出,Len(toString()) 返回 120,加 fromStr 后取尾部 567890 完全正确。超长位数的 toString(radix) 长度也符合数学预期:120 位十进制 → 100 位十六进制、400 位二进制。
七、toString 无参与 toString(10) 完全等价
实测 noparam=[4095] r10=[4095] same=true。需要显式写 10 的场合(比如放在循环变量里动态构造 radix),行为与无参一致。
八、PB12.5 差异
与 PB10 行为一致,零差异。
本轮所有 60 个测试方法(20 主用例 + 20 进制/返回值核对 + 20 补零/边界核对)外加文章示例的 5 个方法与封装的 1 组校验,全部在 PB10 编译基线与 PB 12.5 实机运行两条轨道上通过,PB12.5 侧的诊断输出即上文引用的那些数值;PB10 侧完成全量编译(0 错误)与对象落库校验(本对象及配套 NVO 全部进库)。两版在方法签名、返回类型、进制规则、前导零规则、负号规则上未发现任何行为差异。
需要说明的是:PB10 侧本轮只做到编译级验证(全量编译 0 错误 + 对象齐全),运行级读数取自 PB 12.5 的 PBVM。原因是本机验证工具的运行级依赖 PB12.5 的编译引擎,PB10 通道会走到不可用的执行路径并超时,无法做 PB10 的运行级验证。这一点如实标注,不做夸大。
九、实机验证情况
测试规模:3 个测试 NVO 共 60 个方法 + 1 个文章示例 NVO(5 个方法),全部「一类一方法」拆分,每个方法用独立 pbtest 单独运行,避免崩溃类问题互相带偏。
四项全绿:
| 项目 | PB10 基线 | PB12.5 实测 | | 对象导入 | 0 错(4 个对象全部导入成功) | 0 错 | | 全量编译 | 0 错误 | 0 错误 | | 对象落库校验 | 4 个对象齐全 | 4 个对象齐全 | | PBVM 运行 | 未做(工具限制,见第八节) | 60 + 5 + 1 = 66 次运行全 PASS | | PB12.5 静态检查 | — | 0 错误(1 条提醒来自既有文件 uo_clipboard,与本文无关) | | 源码编码检查 | — | 全部合格(GBK / CRLF / 无 BOM) |
文章示例代码自验证:第三、四节的示例代码是把将出现在文章里的原文照搬成 nvo_bi_demo 重新编译并运行的,五个方法双轨通过,输出与文中标注逐字节一致。文中没有任何「未实测、仅静态核对」的段落。
签名探测前置:选题后先做了一轮 12 个 API 的签名探测(FromStr / FromHex / toString 两重载 / toBlob / Rand / 负数 / 非法串 / 空串 / radix 越界 / longlong 对照 / 空 blob),确认 5 个方法全部真实存在后才动笔。探测中砍掉了两个错误写法:
- longlong 字面量不带 LL 后缀 —— 写成 9223372036854775807LL 报 C0031: Syntax error;
- 循环跳出用 exit; 而不是 exitloop(见第五节末尾)。
十、与相近方案对比
| 方案 | 上限 | 能否算 | 依赖 | 适用 | | longlong | 9.22×10^18 | 能 | 无 | 绝大多数业务够用,优先用 | | decimal | 1.84×10^19(仅字面量) | 不适合 | 无 | 金额定点 | | uo_biginteger | 实测 120 位无压力 | 不能(见下) | PbIdea.dll | 超长整数的存取与进制转换 | | 自己写字符串大数运算 | 任意 | 能 | 无 | 需要真正做加减乘除时 |
选型建议:先老老实实用 longlong。只在两种情况下换uo_biginteger——① 存/转超长整数(雪花 ID、微信号、二维码内容、大数哈希的十六进制串);② 需要十进制↔十六进制↔二进制互转。
务必注意它的能力边界:uo_biginteger 只有5 个方法,没有任何算术运算。 加、减、乘、除、取模一个都没有(这在 Java BigInteger 里是主力功能)。它本质是个高精度容器 + 进制转换器,不是计算器。真的要做高精度乘法,请把数字拆成字符串数组自己写,或者分段用 longlong 算——那是另一套写法,不在本文范围内。
十一、常见错误速查
| 现象 | 根因 | 处置 | | Unresolvable external | 缺 PbIdea.dll 运行库 | 检查 PBIDEA 是否安装、库是否在 PBT 的 liblist 里 | | FromStr 永远返回 false | 设计如此 | 别用返回值校验,自己校验输入 | | 解析出 12 而不是报错 | 静默截断到第一个非法字符 | 自己写 of_is_digit 逐字符校验 | | toString(8) 返回十进制 | 只支持 2 和 16 | 八进制从二进制每三位切 | | 十六进制串长度是偶数 | 前导零对齐 | 提取前自己剥零 | | 二进制串首字节是 0 | 补齐到 8 的倍数 | 同上 | | 负数 toString(2) 没负号 | 二进制不带符号 | 符号自己处理 | | Rand(8) 看着像十进制 | 它是十六进制串 | 当随机 token 用,别当数值 | | FromHex("0x1A") 得 0 | 不认前缀 | 自己剥 0x | | 失败后拿到上次的值 | 失败不清零 | 失败分支必须显式重置 | | 编译报 C0015: exitloop | PowerScript 无此关键字 | 用 exit;(带分号) |
十二、小结
uo_biginteger 补的是 PowerScript 最硬的一块短板——超过 longlong 的整数表示。但它是个容器不是计算器,用好它只需要记住四件事:
- FromStr 的返回值没有信息量,校验必须自己写;
- 非法输入静默截断,不报错,这是最危险的一条;
- toString(radix) 只有 2 和 16 可信,其余静默按十进制返回;
- Rand 出的是十六进制串且不可复现,长度即参数值。
配合第五节的 of_load_dec 封装,可以在项目里安全地用起来。 |