VBA:Variant/Double和Double之间的区别



我使用的是Excel 2013。在以下代码片段中,VBA为damage:计算40

Dim attack As Variant, defense As Variant, damage As Long
attack = 152 * 0.784637
defense = 133 * 0.784637
damage = Int(0.5 * attack / defense * 70)

如果数据类型更改为Double,VBA将为damage:计算39

Dim attack As Double, defense As Double, damage As Long
attack = 152 * 0.784637
defense = 133 * 0.784637
damage = Int(0.5 * attack / defense * 70)

在调试器中,Variant/DoubleDouble的值显示相同。然而,Variant/Double似乎具有更高的精度。

有人能解释这种行为吗?

tldr如果您需要比Double更高的精度,请不要使用Double

答案在于何时将结果从Variant强制转换为DoubleDouble是IEEE 754浮点数,根据IEEE规范,可逆性保证为15位有效数字。你的价值接近这个极限:

0.5 * (152 * .784637) / (133 * .784637) * 70 = 39.99999999999997 (16 sig. digits)

VBA在被强制为双精度时,将对超过15个有效数字的任何数字进行四舍五入:

Debug.Print CDbl("39.99999999999997") '<--Prints 40

事实上,您可以在VBE中观察此行为。键入或复制以下代码:

Dim x As Double
x = 39.99999999999997

VBE通过将文字值强制转换为Double来"自动更正"文字值,这将为您提供:

Dim x As Double
x = 40#

好吧,现在你可能会问这与这两个表达式之间的差异有什么关系。VBA使用"最高阶"变量类型来计算数学表达式。

在第二个Sub中,所有变量都在右侧声明为Double,运算以Double的高阶进行求值,然后结果在作为Int()的参数传递之前隐式转换为Variant

在具有Variant声明的第一个Sub中,在传递到Int之前不执行对Variant的隐式转换-数学表达式中的最高阶为Variant,因此在将结果传递到Int()之前不执行任何隐式转换-Variant仍包含原始IEEE 754浮点值。

根据Int:文件

Int和Fix都删除了数字的小数部分,并返回得到的整数值。

不执行舍入。顶部代码调用Int(39.99999999999997)。底部代码调用Int(40)。"答案"取决于你想要舍入的浮点误差水平。如果15有效,那么40就是"正确"答案。如果你想把有效数字降到16位或以上,那么39是"正确"的答案。解决方案是使用Round并明确指定您要查找的精度级别。例如,如果您关心完整的15位数字:

Int(Round((0.5 * attack / defense * 70), 15))

请记住,您在输入中使用的最高精度是6位数字,因此这将是一个逻辑舍入截止值:

Int(Round((0.5 * attack / defense * 70), 6))

如果在计算损伤的两行上都去掉Int()函数,那么两者最终都是相同的。你不应该使用Int,因为这会产生错误行为,你应该在转换为Long变量时使用CLng,或者如果伤害是Int,你应该使用CInt。

Int和CInt的行为不同。Int总是四舍五入到下一个较低的整数,而CInt则会使用Banker的舍入进行上下取整。对于尾数为0.5的数字,您通常会看到这种行为。

至于变体和双重差异,如果您对第一个代码块的MsgBox执行TypeName,您会发现,尽管已声明为变体,但分配值后的攻击和防御都已转换为双重。

最新更新