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

ltl*_*lim 5 precision double vba variant

我正在使用Excel 2013.在以下代码片段中,VBA计算40 damage:

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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

在调试器中,Variant/DoubleDouble值显示相同.但是,Variant/Double似乎更精确.

谁能解释这种行为?

Com*_*ern 5

tldr; 如果你需要比a更精确Double,不要使用a Double.

答案在于结果被强制转换成a Double的时间Variant.A Double是IEEE 754浮点数,根据IEEE规范,可逆性保证为15位有效数字.你的价值与这个限制调情:

0.5 * (152 * .784637) / (133 * .784637) * 70 = 39.99999999999997 (16 sig. digits)
Run Code Online (Sandbox Code Playgroud)

当被强制成双倍时,VBA将超过15位有效数字:

Debug.Print CDbl("39.99999999999997") '<--Prints 40
Run Code Online (Sandbox Code Playgroud)

实际上,您可以在VBE中观察此行为.输入或复制以下代码:

Dim x As Double
x = 39.99999999999997
Run Code Online (Sandbox Code Playgroud)

VBE通过将其转换为a来"自动更正"文字值Double,从而为您提供:

Dim x As Double
x = 40#
Run Code Online (Sandbox Code Playgroud)

好的,所以现在你可能会问这与2个表达式之间的区别有什么关系.VBA使用它可以使用的"最高阶"变量类型来评估数学表达式.

在您的第二个中Sub,您将所有变量声明为Double右侧,操作将按照高顺序进行计算Double,然后在作为参数传递之前将结果隐式转换为a .VariantInt()

在你的第一个Sub,你必须Variant声明,对隐式转换Variant不会传递到之前执行Int的最高等级的数学表达式- 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))
Run Code Online (Sandbox Code Playgroud)

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

Int(Round((0.5 * attack / defense * 70), 6))
Run Code Online (Sandbox Code Playgroud)

  • @ Mat'sMug - [货币](http://stackoverflow.com/documentation/vba/3418/data-types-and-limits/11782/currency#t=201701300402070599064)将所有内容扩展到10,000,只关注4位数到小数的权利.它在数字5处舍入,因此相当于`Int(Round(399999.99999,4))/ 10000` (2认同)

Amo*_*ses 0

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

Int 和 CInt 的行为不同。Int 总是向下舍入到下一个较小的整数 - 而 CInt 将使用银行舍入法向上或向下舍入。对于尾数为 0.5 的数字,您通常会看到这种行为。

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