VBA:Variant/Double 和 Double 之间的区别
Posted
技术标签:
【中文标题】VBA:Variant/Double 和 Double 之间的区别【英文标题】:VBA: difference between Variant/Double and Double 【发布时间】:2017-01-30 02:45:53 【问题描述】:我使用的是 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
似乎更精确。
谁能解释这种行为?
【问题讨论】:
改变0.5
和70
的顺序,你会得到39:Int(70 * attack / defense * 0.5)
。
【参考方案1】:
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
。
在您的第一个 Sub
中,您有 Variant
声明,在传递给 Int
之前不会执行到 Variant
的隐式转换 - 数学表达式中的最高阶是 Variant
,所以 在将结果传递给 Int()
之前不执行任何隐式转换 - Variant
仍然包含原始 IEEE 754 浮点数。
根据documentation 的Int
:
Int 和 Fix 都删除了 number 的小数部分并返回 结果整数值。
不进行舍入。顶部代码调用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))
【讨论】:
不错。值得注意的是,当类型为Currency
而不是 Double
时,两个版本都返回 40。
@Mat'sMug - Currency 将所有内容缩放 10,000 并且只关心小数点右侧的 4 位数字。它在数字 5 处四舍五入,因此相当于 Int(Round(399999.99999, 4)) / 10000
请注意,你有它倒退。顶部代码必须调用Int(40)
,底部代码必须调用Int(39.something)
。您的回答暗示 Variant
比标准 IEEE 双精度具有更高的精度,可能存储为长双精度或某种原始 x86 双精度。据我所知,这种行为是无证的。【参考方案2】:
如果您在计算伤害的两条线上都去掉 Int() 函数,那么它们最终会是相同的。您不应该使用 Int,因为这会产生错误的行为,您应该在转换为 Long 变量时使用 CLng,或者如果损坏是 Int,您应该使用 CInt。
Int 和 CInt 的行为不同。 Int 总是向下舍入到下一个较小的整数 - 而 CInt 将使用 Banker's Rounding 向上或向下舍入。对于尾数为 0.5 的数字,您通常会看到这种行为。
至于变体和双重差异,如果您对第一个代码块的 MsgBox 执行 TypeName,您会发现尽管已被声明为变体,但赋值后的攻击和防御都已转换为双重.
【讨论】:
...如果您离开Int
调用并将 RHS 表达式更改为 attack
和 defense
值的赋值中的常量,则两个版本都将返回 40:attack = 119.264824
和defense = 104.356721
。不知道你在做什么。如果该函数应该返回一个Integer
值,那么CInt
将完全按照它应该使用的方式使用,以使类型转换显式。 OP 的问题是为什么当attack
和defense
是Variant
与显式Double
时结果不同,而不是如何“修复它”。
但是,damage As Long
的转换应该是 CLng
,而不是 CInt
。
对不起,你是对的,更新更好地回答了这里发生的事情。
TBH 那是一个相当令人头疼的人;-)
您的假设不正确。我并没有说我希望答案是 39 或 40。就是这样。但是,我不想使用 CLng,因为我需要截断浮点值。我在问为什么当 Variant 的行为应该与 Double 相同时,行为会有所不同。以上是关于VBA:Variant/Double 和 Double 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章
php的递归问题,$arr[$key]=doub($arr[$key])这句看了半天没看懂。下面是代码
public class DistanceUtil private static final double EARTH_RADIUS = 6378137; private static doub