我使用的是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/Double
和Double
的值显示相同。然而,Variant/Double
似乎具有更高的精度。
有人能解释这种行为吗?
tldr如果您需要比Double
更高的精度,请不要使用Double
。
答案在于何时将结果从Variant
强制转换为Double
。Double
是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,您会发现,尽管已声明为变体,但分配值后的攻击和防御都已转换为双重。