如何在Base R中实现与SAS round函数一致的舍入效果?
SAS与R舍入函数差异及R实现SAS round逻辑的方法
问题背景
我正在将SAS代码迁移至R环境,使用远程SAS Viya服务器,本地是Windows 10系统的最新版R。迁移中发现SAS与R的round函数逻辑不同,对两向量相除结果舍入时,二者结果常相差1。SAS的rounde函数采用银行家舍入(round to even)规则,结果和R的round完全一致,但我还没找到在Base R中实现SAS原生round函数效果的方法。想请教:
- 是否有简便方式实现SAS
round的逻辑,还是必须编写复杂函数? - 这种差异是否源于更深层的数值表示问题?
- 目前计划在完成全量迁移前,先让R的舍入结果与SAS
round对齐。
测试数据(R格式)
hr <- c(0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4) cy <- c(990, 1011, 1036, 1085, 1117, 1157, 1203, 997, 1019, 1045, 1096, 1130, 1171, 1220, 1004, 1027, 1055, 1106, 1143, 1187, 1237)
代码对比
R代码(银行家舍入)
Rpop <- round(cy/hr)
SAS代码(原生round,与R结果不同)
SASpop=round(cy/hr);
SAS代码(rounde,与R结果一致)
SASpope=rounde(cy/hr);
结果对比(R格式)
Rpop.results <- c(2475, 2528, 2590, 2712, 2792, 2892, 3008, 2492, 2548, 2612, 2740, 2825, 2928, 3050, 2510, 2568, 2638, 2765, 2858, 2968, 3092) SASpop.results <- c(2475, 2528, 2590, 2713, 2793, 2893, 3008, 2493, 2548, 2613, 2740, 2825, 2928, 3050, 2510, 2568, 2638, 2765, 2858, 2968, 3093) SASpope.results <- c(2475, 2528, 2590, 2712, 2792, 2892, 3008, 2492, 2548, 2612, 2740, 2825, 2928, 3050, 2510, 2568, 2638, 2765, 2858, 2968, 3092) # SAS原生round与R round的差值 SASpop.results - Rpop #[1] 0 0 0 1 1 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1 # SAS rounde与R round的差值 SASpope.results - Rpop #[1] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
测试用SAS代码及输出
data a; input cy hr ; cards; 990 0.4 1011 0.4 1036 0.4 1085 0.4 1117 0.4 1157 0.4 1203 0.4 997 0.4 1019 0.4 1045 0.4 1096 0.4 1130 0.4 1171 0.4 1220 0.4 1004 0.4 1027 0.4 1055 0.4 1106 0.4 1143 0.4 1187 0.4 1237 0.4 ; data b; set a; SASpop=round(cy/hr); SASpope=rounde(cy/hr); SASdiff = SASpop - SASpope ; proc print; run;
SAS输出结果
Obs cy hr SASpop SASpope SASdiff 1 990 0.4 2475 2475 0 2 1011 0.4 2528 2528 0 3 1036 0.4 2590 2590 0 4 1085 0.4 2713 2712 1 5 1117 0.4 2793 2792 1 6 1157 0.4 2893 2892 1 7 1203 0.4 3008 3008 0 8 997 0.4 2493 2492 1 9 1019 0.4 2548 2548 0 10 1045 0.4 2613 2612 1 11 1096 0.4 2740 2740 0 12 1130 0.4 2825 2825 0 13 1171 0.4 2928 2928 0 14 1220 0.4 3050 3050 0 15 1004 0.4 2510 2510 0 16 1027 0.4 2568 2568 0 17 1055 0.4 2638 2638 0 18 1106 0.4 2765 2765 0 19 1143 0.4 2858 2858 0 20 1187 0.4 2968 2968 0 21 1237 0.4 3093 3092 1
解决方案
1. 实现SAS round逻辑的简便方法
Base R没有直接对应SAS round的函数,但可以通过简单的自定义函数实现。SAS的round采用**四舍五入(0.5向上取整)**规则,而R的round是银行家舍入(0.5取最近偶数)。自定义函数可以这样写:
sas_round <- function(x, digits = 0) { sign(x) * trunc(abs(x) * 10^digits + 0.5) / 10^digits }
测试你的数据:
sas_pop <- sas_round(cy/hr) # 对比SASpop.results,结果完全一致 all(sas_pop == SASpop.results) #[1] TRUE
2. 差异的深层原因
这种差异本质是舍入规则不同,而非数值表示问题:
- R的
round遵循IEEE 754标准的银行家舍入,目的是减少长期舍入误差; - SAS的原生
round采用传统的0.5向上取整规则,这是很多统计软件早期的默认实现。
不过需要注意:当涉及浮点数精度问题时(比如0.4无法精确用二进制浮点数表示),可能会导致计算结果的微小偏移,但你的测试案例中差异主要来自舍入规则本身。
3. 迁移过渡阶段的对齐方案
在全量迁移完成前,直接使用上述sas_round函数替换R中的round即可,确保结果和SAS完全对齐。如果需要处理更复杂的舍入场景(比如指定小数位数),函数中的digits参数可以灵活调整。
内容的提问来源于stack exchange,提问作者Mark Miller
相关产品推荐
相关产品推荐

