马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PowerBuilder 数据类型与类型转换全解:标准类型、ANY、NULL 三态与精度溢出(PB12.5 实机验证)
阅读说明
1. 适用版本:PB 10 / PB 12.5(示例以 PB10 写法为基准,全部代码在 PB 12.5 实机编译并运行通过;版本差异处单独标注)
2. 支持数据库:本文为纯 PowerScript 语言主题,不依赖任何数据库
3. 操作系统与环境要求:Windows 7 及以上,装有 PB 10 或 PB 12.5 开发环境/运行库;无需第三方组件
4. 难度系数:★★☆☆☆
5. 其它阅读说明:建议先了解变量声明与作用域;文中所有"实测"输出值均取自 PB12.5 真机运行,不是推测
一、为什么类型这件事总在半夜出事
写 PowerBuilder 的人,大概都遇到过这几种场景:金额累加了几万行之后差了几分钱;一个返回 -5536 的数量怎么算都不对;界面上明明没填值,if ls_name = '' then 却死活不进;Integer('12.9') 拿到的不是 13 而是 12,还一声不响。
这些问题没有一个是"语法不会",全都是类型的边界行为没搞清。PowerScript 是一门弱声明、强隐式规则的语言:它不要求你到处写类型转换,但它内部的转换规则又不完全符合直觉。本文把这套规则一次讲透——标准类型有哪些、转换函数怎么选、转换失败会怎样、NULL 到底怎么参与运算、精度与溢出在哪里崩、隐式转换哪些能哪些不能。
下面所有输出值,都是把代码做成 NVO 对象、用 PB12.5 真机编译后在 PBVM 里跑出来的实测结果(共 80 条硬断言,全部命中)。
二、标准数据类型全景
先给一张全景表,后面每一节都是这张表里某一格的展开。
| 类型 | 说明 | 典型用途 | 备注 | | boolean | 真/假 | 开关、标志位 | String(true) 得到小写 true | | integer(int) | 2 字节整数 | 行号、序号、小计数器 | 上限 32767,最容易被撑爆 | | unsignedinteger(uint) | 2 字节无符号 | 端口、编码值 | 实测 65535 正常 | | long | 4 字节整数 | 日常默认整数类型 | 不确定就用 long | | unsignedlong(ulong) | 4 字节无符号 | 句柄、大计数 | 实测 4294967295 正常 | | longlong | 8 字节整数 | 超过 21 亿的计数 | 实测 12345678901 正常 | | real | 4 字节单精度浮点 | 兼容老代码 | 精度低,新代码别用 | | double | 8 字节双精度浮点 | 科学计算、非金额小数 | 有二进制误差 | | decimal(dec) | 十进制精确小数 | 金额、单价、税率 | 0.1+0.2 严格等于 0.3 | | char | 单字符 | 取字符、ASCII 处理 | 与 string 可直接互赋 | | string | 变长字符串 | 文本 | 中文按字符计长度 | | blob | 变长二进制 | 图片、文件、加密结果 | 见 Day26 的 UpdateBlob 篇 | | date | 日期 | 生日、单据日期 | 1000-01-01 ~ 3000-12-31 | | time | 时间 | 时刻 | 精度到微秒 | | datetime | 日期+时间 | 时间戳 | 不能直接赋给 date | | any | 万能容器 | 通用参数、异构数据 | 取用时要自己知道装了什么 |
上表的字节数与取值范围是 PB 帮助文档的常规说明;本文实机验证的是行为(溢出、回绕、转换、比较),见后文各节。凡标注"实测"的,都是真机跑出来的数字。
一句话选型:整数用 long,小数分两种——金额一律 decimal,非金额用 double;字符串和数值之间永远显式转换。
三、声明、默认值:变量声明出来不是 NULL
这是最容易被 C/Java 习惯带偏的一条。PowerScript 里变量声明后自动初始化,数值是 0、字符串是空串,不是 NULL。实测:long ll_zero 声明后 IsNull(ll_zero) = false,值 = 0。
NULL 只有你主动 SetNull() 或从数据库读入空列时才会出现。
前置:新建一个窗口 w_type_demo,放一个按钮 cb_1,把下面代码贴进 cb_1 的 clicked 事件;运行窗口点击按钮即可看到结果。
- // 示例输入:演示用的数值(可自行修改观察变化)
- long ll_zero
- long ll_x
- string ls_s
- string ls_ret
- // 声明后不赋值:PowerScript 自动初始化为 0 / 空串,不是 NULL
- ll_zero = 0
- ls_ret = '数值默认 IsNull=' + String(IsNull(ll_zero))
- ls_ret = ls_ret + '; 默认值=' + String(ll_zero)
- // 字符串同样:声明后是空串,不是 NULL
- ls_s = ''
- ls_ret = ls_ret + '~r~n字符串默认 IsNull=' + String(IsNull(ls_s))
- ls_ret = ls_ret + '; 长度=' + String(Len(ls_s))
- // 只有主动 SetNull 才是 NULL
- SetNull(ll_x)
- ls_ret = ls_ret + '~r~nSetNull 后 IsNull=' + String(IsNull(ll_x))
- MessageBox('默认值与 NULL', ls_ret)
复制代码
四、显式类型转换:函数清单与实测返回值
PowerScript 的转换函数都是"类型名当函数名",非常好记。
| 函数 | 作用 | 实测例子 | 结果 | | Integer(s) | 转 2 字节整数 | Integer('123') | 123 | | Long(s) | 转 4 字节整数 | Long('1234567890') | 1234567890 | | Dec(s) | 转 decimal | Dec('123.456') | 123.456 | | Double(s) | 转双精度 | Double('1.5e3') | 1500(支持科学计数法) | | Real(s) | 转单精度 | Real('3.25') | 3.25 | | String(x [,mask]) | 转字符串,可带格式掩码 | String(1234.5678,'#,##0.00') | 1,234.57 | | Date(s) | 转日期 | Date('2026-09-18') | 2026-09-18 | | Time(s) | 转时间 | Time('14:30:15') | 14:30:15 | | DateTime(d, t) | 日期+时间合成 datetime | DateTime(Date('2026-09-18'), Time('14:30:15')) | 2026-09-18 14:30:15 | | IsNumber(s) | 能不能当数字 | IsNumber(' 12 ') | true(前后空格允许) | | IsDate(s) | 能不能当日期 | IsDate('不是日期') | false | | IsNull(x) / SetNull(x) | 判空 / 置空 | — | — |
注意:转换函数不接受变量作为目标,它是"返回值",必须自己接。
前置:同上,代码贴在 cb_1 的 clicked 事件。
- // 示例输入:待转换的原始字符串(都按"用户手工输入"来设想)
- string ls_num
- string ls_date
- string ls_time
- // 示例输入:用于演示结果的目标变量
- long ll_v
- dec ldec_v
- date ld_v
- datetime ldt_v
- string ls_ret
- ls_num = '1234567890'
- ls_date = '2026-09-18'
- ls_time = '14:30:15'
- // 字符串 -> 数值
- ll_v = Long(ls_num)
- ldec_v = Dec('123.456')
- // 字符串 -> 日期 / 时间 / 时间戳
- ld_v = Date(ls_date)
- ldt_v = DateTime(ld_v, Time(ls_time))
- ls_ret = 'Long=' + String(ll_v)
- ls_ret = ls_ret + '~r~nDec=' + String(ldec_v)
- ls_ret = ls_ret + '~r~nDouble(科学计数法)=' + String(Double('1.5e3'))
- ls_ret = ls_ret + '~r~nDate=' + String(ld_v)
- ls_ret = ls_ret + '~r~nDateTime=' + String(ldt_v)
- MessageBox('显式转换', ls_ret)
复制代码
五、转换失败不报错 —— 这条坑最深
PowerScript 的类型转换失败不抛异常、不返回 NULL,而是返回"零值"。 实测结果:
| 代码 | 返回值 | 说明 | | Integer('12.9') | 12 | 直接截断,不是四舍五入 | | Integer('abc') | 0 | 与"用户真的填了 0"无法区分 | | Integer('') | 0 | 同上 | | Long('abc') | 0 | | | Dec('abc') | 0 | | | Double('abc') | 0 | | | Date('不是日期') | 1900-01-01 | 不是空日期,是 1900 年! |
而且 IsNull(String(Integer('abc'))) 实测 = false —— 结果真的就是 0,不是 NULL,所以你没法用判空来兜底。
正确做法:转换前先用 IsNumber / IsDate 校验。注意 IsNumber(' 12 ') 返回 true(前后空格容忍),IsNumber('12.5.6') 返回 false。
前置:同上。
- // 示例输入:用户从界面填进来的原始串(脏数据,故意给各种形态)
- string ls_in
- string ls_ret
- // 演示一:直接转,看失败的"零值"
- ls_in = 'abc'
- ls_ret = "Integer('abc')=" + String(Integer(ls_in))
- ls_ret = ls_ret + "~r~nInteger('12.9')=" + String(Integer('12.9'))
- ls_ret = ls_ret + "~r~nDate('不是日期')=" + String(Date('不是日期'))
- // 演示二:先校验再转(推荐写法)
- ls_in = '12.5'
- if IsNumber(ls_in) then
- ls_ret = ls_ret + '~r~n校验通过,Dec=' + String(Dec(ls_in))
- else
- ls_ret = ls_ret + '~r~n非法数字:' + ls_in
- end if
- ls_in = '12.5.6'
- if IsNumber(ls_in) then
- ls_ret = ls_ret + '~r~n校验通过'
- else
- ls_ret = ls_ret + '~r~n非法数字(多个小数点):' + ls_in
- end if
- // 演示三:日期同样先 IsDate
- ls_in = '2026-09-18'
- if IsDate(ls_in) then
- ls_ret = ls_ret + '~r~n合法日期=' + String(Date(ls_in))
- else
- ls_ret = ls_ret + '~r~n非法日期:' + ls_in
- end if
- MessageBox('转换失败与校验', ls_ret)
复制代码
六、NULL 三态:既不是真也不是假
NULL 在 PowerScript 里是第三态,参与任何运算/比较的结果还是 NULL,而 if 遇到 NULL 一律按假处理,走 else 分支。实测:
| 表达式 | 实测结果 | | IsNull(已置空的变量) | true | | NULL变量 + 1 | 结果是 NULL(IsNull = true) | | if NULL变量 = 1 then | 走 else(cmp=F) | | if IsNull(NULL变量) then | 走 then(ifnull=T) | | String(NULL数值) | 返回 NULL(不是空串) | | 'a' + NULL字符串 | 整串变 NULL | | NULL字符串 = '' | false(NULL 不等于空串) | | Len(NULL字符串) | 返回 NULL |
最后两条合起来就是最著名的 NULL 传染:把 NULL 拼进返回串,整串变 NULL,写文件就写出 0 字节——排查时表现为"程序跑成功了但输出是空的"。
防御写法:凡是可能为空的值,拼串前过一遍 IsNull。
前置:同上。
- // 示例输入:模拟从数据库读出来的、可能为空的字段
- long ll_qty
- string ls_memo
- string ls_ret
- string ls_part
- // 模拟"数据库里是 NULL"
- SetNull(ll_qty)
- SetNull(ls_memo)
- // 一、判空必须用 IsNull,不能用 = ''
- if IsNull(ll_qty) then
- ls_ret = '数量为空(NULL)'
- else
- ls_ret = '数量=' + String(ll_qty)
- end if
- // 二、NULL 参与运算,结果还是 NULL
- if IsNull(ll_qty + 1) then
- ls_ret = ls_ret + '~r~nNULL+1 仍然是 NULL'
- else
- ls_ret = ls_ret + '~r~nNULL+1=' + String(ll_qty + 1)
- end if
- // 三、拼串前先消毒,否则整串变 NULL(写文件就是 0 字节)
- ls_part = String(ls_memo)
- if IsNull(ls_part) then
- ls_part = '(空)'
- end if
- ls_ret = ls_ret + '~r~n备注=' + ls_part
- // 四、NULL 不等于空串,别混为一谈
- if ls_memo = '' then
- ls_ret = ls_ret + '~r~n备注是空串'
- else
- ls_ret = ls_ret + '~r~n备注不是空串(它是 NULL)'
- end if
- MessageBox('NULL 三态', ls_ret)
复制代码
七、ANY:什么都能装,但你要自己记住装了什么
any 是 PowerScript 的万能容器,可以装数值、字符串、日期、布尔甚至对象。实测 any la 依次装入 3 / 'abc' / 3.14 / Date('2026-09-18') / true / 12345678901,String(la) 全部能正确取出。
两个要点:
- 取用时没有类型检查——你装的是日期却当数值算,编译器不会拦你,结果在运行时才炸。
- 想动态知道装了什么,用 ClassName(any变量)。实测装入字面量 3 后 ClassName(la) 返回 long(注意:整数字面量在 PB 里按 long 处理,不是 integer)。
前置:同上。
- // 示例输入:要依次装进 any 的各种值(模拟异构参数)
- any la_v
- string ls_ret
- // 装数值
- la_v = 3
- ls_ret = '装 3 -> ' + String(la_v) + ',ClassName=' + ClassName(la_v)
- // 装字符串
- la_v = 'abc'
- ls_ret = ls_ret + '~r~n装 abc -> ' + String(la_v)
- // 装小数
- la_v = 3.14
- ls_ret = ls_ret + '~r~n装 3.14 -> ' + String(la_v)
- // 装日期
- la_v = Date('2026-09-18')
- ls_ret = ls_ret + '~r~n装日期 -> ' + String(la_v)
- // 装布尔
- la_v = true
- ls_ret = ls_ret + '~r~n装 true -> ' + String(la_v)
- // 装超过 long 范围的大整数(字面量本身会按 longlong 处理)
- la_v = 12345678901
- ls_ret = ls_ret + '~r~n装大整数 -> ' + String(la_v)
- MessageBox('ANY 变量', ls_ret)
复制代码any 适合做"万能参数"(比如一个通用日志接口),不适合做业务变量。能用确定类型就别用 any,否则把编译期错误推成了运行期错误。
八、精度与溢出:钱要用 decimal,数要用 long
这一节的三条实测结论,每一条都能单独救一次火。
1)double 有二进制误差,decimal 没有
- double: 0.1 + 0.2 = 0.3 但 (0.1+0.2 = 0.3) 判定为 F(假)
- decimal: 0.1 + 0.2 = 0.3 且 (0.1+0.2 = 0.3) 判定为 T(真)
复制代码
String() 显示出来都是 0.3,但比较结果完全不同。所以金额比较绝对不能用 =,要么用 decimal,要么用容差比较。
2)integer 相乘,赋给谁决定会不会溢出(这条最反直觉)
- long ll = li_a * li_b → 60000 (li_a=300, li_b=200)
- integer li = li_a * li_b → -5536 (同一个表达式!)
- integer li = 300 * 200 → -5536 (字面量一样溢出)
- long ll = long(li_a) * li_b → 60000 (先提升就没事)
复制代码
原因:表达式的结果类型受赋值目标影响,目标是 integer 时按 2 字节算,60000 直接回绕成 -5536。写代码时中间结果一律用 long 承接。
3)加减溢出静默回绕
integer li = 32767; li = li + 1 实测得到 -32768,没有任何提示。
4)整除要自己截断
/ 是普通除法:7 / 2 实测 = 3.5。要整除用 Int() 或 Truncate()(实测 Int(7/2) = 3),取余用 Mod(7,2) = 1。
前置:同上。
- // 示例输入:参与计算的原始数据
- dec ldec_a
- dec ldec_b
- double ld_a
- double ld_b
- integer li_a
- integer li_b
- integer li_c
- long ll_p
- string ls_ret
- // 一、金额用 decimal:0.1+0.2 严格等于 0.3
- ldec_a = 0.1
- ldec_b = 0.2
- ls_ret = 'decimal: ' + String(ldec_a + ldec_b)
- if ldec_a + ldec_b = 0.3 then
- ls_ret = ls_ret + ' (等于 0.3:真)'
- else
- ls_ret = ls_ret + ' (等于 0.3:假)'
- end if
- // 二、非金额用 double,但比较要留容差
- ld_a = 0.1
- ld_b = 0.2
- ls_ret = ls_ret + '~r~ndouble : ' + String(ld_a + ld_b)
- if ld_a + ld_b = 0.3 then
- ls_ret = ls_ret + ' (等于 0.3:真)'
- else
- ls_ret = ls_ret + ' (等于 0.3:假 —— 二进制误差)'
- end if
- // 三、integer 乘法:赋给 long 正确、赋给 integer 溢出
- li_a = 300
- li_b = 200
- ll_p = li_a * li_b
- li_c = li_a * li_b
- ls_ret = ls_ret + '~r~n300*200 给 long=' + String(ll_p) + ',给 integer=' + String(li_c)
- ll_p = long(li_a) * li_b
- ls_ret = ls_ret + '~r~n先提升再乘=' + String(ll_p)
- // 四、加减溢出静默回绕
- li_c = 32767
- li_c = li_c + 1
- ls_ret = ls_ret + '~r~n32767+1=' + String(li_c)
- // 五、除法是普通除法,整除要自己截断
- ls_ret = ls_ret + '~r~n7/2=' + String(7 / 2) + ',Int(7/2)=' + String(Int(7 / 2)) + ',Mod(7,2)=' + String(Mod(7, 2))
- // 六、舍入三兄弟
- ls_ret = ls_ret + '~r~nRound(1234.5678,2)=' + String(Round(1234.5678, 2))
- ls_ret = ls_ret + ',Truncate=' + String(Truncate(1234.5678, 2))
- ls_ret = ls_ret + ',Int(1234.987)=' + String(Int(1234.987))
- ls_ret = ls_ret + ',Ceiling(1234.1)=' + String(Ceiling(1234.1))
- MessageBox('精度与溢出', ls_ret)
复制代码
九、隐式转换:哪些自动,哪些必须显式
PowerScript 只在数值族内部和字符/字符串之间做自动转换,其余一律要你显式写。下面这些是实测的编译结果:
| 写法 | 是否通过 | 说明 | | long = integer变量 | ✅ 自动 | 小范围整数提升为大范围 | | string = char变量 | ✅ 自动 | 实测 'A' 转出长度 1 | | date -> datetime(DateTime(ld, lt)) | ✅ 需函数 | 不能直接赋值 | | datetime = date | ❌ C0008 | 类型不匹配,必须 DateTime(ld, Time('00:00:00')) | | 'n=' + 数值变量 | ❌ C0011 | 字符串不能直接拼数值,必须 String() | | if '123' = 123 then | ❌ C0011 | 字符串与数值不能比较 | | if 布尔变量 = 整数变量 then | ❌ C0003 / C0011 | 布尔与数值不能比较 |
第一条(字符串拼数值)是新手最高频的编译错误。记住:只要往字符串里塞数值,就写 String()。
前置:同上。
- // 示例输入:用于演示提升与转换的原始值
- integer li_small
- long ll_big
- char lc_ch
- string ls_s
- date ld_d
- datetime ldt_dt
- string ls_ret
- // 一、integer 自动提升为 long
- li_small = 5
- ll_big = li_small
- ls_ret = 'integer->long: ' + String(ll_big)
- // 二、char 自动转 string
- lc_ch = 'A'
- ls_s = lc_ch
- ls_ret = ls_ret + '~r~nchar->string: ' + ls_s + ',长度=' + String(Len(ls_s))
- // 三、date 合成 datetime(不能直接赋值,必须走 DateTime())
- ld_d = Date('2026-09-18')
- ldt_dt = DateTime(ld_d, Time('00:00:00'))
- ls_ret = ls_ret + '~r~ndate->datetime: ' + String(ldt_dt)
- // 四、字符串拼数值必须显式 String()
- ls_ret = ls_ret + '~r~n拼数值: n=' + String(ll_big)
- // 五、布尔转字符串是小写
- ls_ret = ls_ret + '~r~nString(true)=' + String(true)
- MessageBox('隐式转换', ls_ret)
复制代码
十、String() 格式化掩码
String(值, 掩码) 是 PowerScript 里最实用的输出工具,实测掩码效果:
| 掩码 | 代码 | 实测输出 | | #,##0.00 | String(1234.5678, '#,##0.00') | 1,234.57 | | 0.00% | String(0.256, '0.00%') | 25.60%(自动乘 100) | | 000 | String(7, '000') | 007 | | yyyy-mm-dd | String(日期, 'yyyy-mm-dd') | 2026-09-18 | | yyyy年mm月dd日 | String(日期, 'yyyy年mm月dd日') | 2026年09月18日 | | yyyy-mm-dd hh:mm:ss | String(时间戳, 掩码) | 2026-09-18 14:30:15 | | 0.00E+00 | String(12345.6789, '0.00E+00') | 1.23E+04 | | #,##0.00;(#,##0.00) | String(-1234.5, 掩码) | (1,234.50)(负数用括号) |
前置:同上。
- // 示例输入:待格式化的金额(千分位演示用)
- dec ldec_amt
- // 示例输入:待格式化的时间戳
- datetime ldt_now
- // 示例输入:百分比、补零、负数等演示值
- string ls_ret
- ldec_amt = 1234.5678
- ldt_now = DateTime(Date('2026-09-18'), Time('14:30:15'))
- // 掩码里的 # 表示"有则显示、无则不补",0 表示"没有也补 0"
- ls_ret = '千分位: ' + String(ldec_amt, '#,##0.00')
- ls_ret = ls_ret + '~r~n百分比: ' + String(0.256, '0.00%')
- ls_ret = ls_ret + '~r~n补零 : ' + String(7, '000')
- ls_ret = ls_ret + '~r~n日期 : ' + String(Date('2026-09-18'), 'yyyy年mm月dd日')
- ls_ret = ls_ret + '~r~n时间戳: ' + String(ldt_now, 'yyyy-mm-dd hh:mm:ss')
- ls_ret = ls_ret + '~r~n科学计数: ' + String(12345.6789, '0.00E+00')
- ls_ret = ls_ret + '~r~n负数括号: ' + String(-1234.5, '#,##0.00;(#,##0.00)')
- MessageBox('格式化掩码', ls_ret)
复制代码
顺带一提字符与 ASCII 的互转:Asc('A') 实测 = 65,Char(65) = A;中文同样有效,Asc('中') = 20013,Char(20013) = 中;Asc('') 返回 0(不是 NULL)。
十一、DataWindow / 数据库类型与 PowerScript 类型对照
做数据窗口时经常要"数据库里的这一列,在 PowerScript 里该用什么接":
| 数据库类型(SQL Server) | DataWindow 列类型 | PowerScript 取数类型 | | int | number | long | | bigint | number | longlong | | smallint / tinyint | number | integer / long | | decimal(p,s) / numeric | decimal | decimal(金额必选) | | float / real | number | double | | char / varchar / text | string | string | | datetime / smalldatetime | datetime | datetime | | date / time(2008+) | date / time | date / time | | image / varbinary(max) | blob | blob(走 UpdateBlob/SelectBlob) | | bit | boolean | boolean |
要点:GetItemDecimal() 取金额列、GetItemNumber() 取普通数值列、GetItemString() 取字符列、GetItemDateTime() 取时间戳列。用错取值函数不是报错,而是悄悄丢精度或丢时间部分。
十二、常见疑问 Q&A
Q1:integer 和 int 有区别吗?
没有,int 是 integer 的简写;同理 uint/ulong、dec/decimal。声明时两种写法等价,建议团队统一一种。
Q2:为什么我写 long ll_x = Long('123') 编译不过?
PowerScript 不允许在声明行的同时用表达式初始化。必须拆两行:先 long ll_x,再 ll_x = Long('123')。这条规则同样适用于 uo_json lj = create uo_json 这类对象声明——必须写成 uo_json lj 然后 lj = create uo_json。
Q3:dec 需要指定小数位数吗?
不像数据库那样需要声明精度。PowerScript 的 decimal 是变长十进制类型,你赋多少位它就保留多少位;真正决定显示位数的是 String() 的掩码或 DataWindow 列的 Format 属性。
Q4:判断两个金额是否相等,最稳妥怎么写?
用 decimal 类型承载,然后直接 =;如果数据源是 double,改用容差:if Abs(ld_a - ld_b) < 0.000001 then。
Q5:把一个 any 传进函数,形参该写什么类型?
形参也写 any 就行,PowerScript 不做类型检查。代价是函数内部必须自己 ClassName() 判断装的是什么,否则运行时才会出问题——这也是不建议在业务代码里滥用 any 的原因。
Q6:String(日期) 不写掩码会怎样?
按系统区域设置的短日期格式输出,同一段程序在不同机器上可能得到 2026-09-18 或 2026/9/18。凡是落库、导出、拼接报文的日期,一律显式写掩码。
十三、坑清单(14 条,都是实测踩出来的)
- 转换失败返回 0 不是 NULL:Integer('abc') = 0,IsNull 判不出来,必须先 IsNumber。
- Integer('12.9') = 12:截断不是四舍五入,要四舍五入先转 Dec 再 Round。
- Date('非法') = 1900-01-01:这个日期会真的写进数据库,比 NULL 更难查。
- IsNumber(' 12 ') = true:前后空格容忍,但 '12.5.6' 是 false。
- NULL 拼进字符串,整串变 NULL:写文件就是 0 字节,表现为"程序成功但输出为空"。
- String(NULL) 返回 NULL,不是空串;Len(NULL串) 也返回 NULL。
- NULL 不等于空串:if ls = '' then 对 NULL 不成立,判空一律用 IsNull。
- NULL 参与比较结果是 NULL,if 当假处理:if NULL变量 = 1 永远走 else。
- double 的 0.1+0.2 ≠ 0.3:显示是 0.3,比较是假;金额用 decimal。
- integer 乘法会不会溢出,看赋值给谁:long 接 60000、integer 接 -5536。
- integer 加减溢出静默回绕:32767+1 = -32768,无提示。
- 字符串不能直接拼数值:'n=' + li_i 编译报 C0011,必须 String()。
- datetime 不能直接赋给 date:报 C0008,用 DateTime(ld, Time('00:00:00'))。
- Blob() 不能接数值类型:实测 Blob(integer变量) 在 PB12.5 里编译不通过(导入阶段直接失败),Blob() 只用于 string/blob。
十四、选型对比与小结
| 场景 | 推荐 | 别用 | 原因 | | 金额、单价、税率 | decimal | double | double 有二进制误差,累计会差分 | | 科学计算、坐标 | double | real | real 精度太低 | | 计数、行号、主键 | long | integer | integer 上限 32767 太容易爆 | | 超过 21 亿 | longlong | long | long 上限约 21 亿 | | 通用参数 | any | — | 但取用务必自己记住类型 | | 判空 | IsNull() | = '' / = 0 | NULL 与空串/0 是两回事 | | 数值转字符串 | String(x, 掩码) | 直接 + | 直接拼编译不过 |
一句话收尾:PowerScript 的类型规则不复杂,难的是它"不报错"。转换失败给 0、溢出给回绕、NULL 给传染——三件事都不会抛异常。把这三处的防御写进习惯(先 IsNumber/IsDate 校验、中间结果用 long/decimal 承接、拼串前过 IsNull),能省掉绝大多数半夜事故。
附:本文实机验证环境
- PB 12.5(pypower + PBORCA 导入,build rebuild --type full 0 错误)
- 验证方式:把全部演示做成一个 nvo_type_demo 对象,在 PBVM 中运行并落盘输出,做 80 条硬断言,全部命中
- 静态检查:check_pb125 0 错误 0 提醒;check_enc 编码/行尾/源码头全部 OK
- 文中所有"实测"数字均来自该次真机运行,未实测的部分已明确标注
|