2017-01-30 53 views
5
我使用Excel 2013在下面的代碼片斷

之間的差異,計算VBA 40 damageVBA:變體/雙重和雙

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計算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) 

在調試程序中,Variant/DoubleDouble值顯示相同的。但是,Variant/Double似乎更精確。

任何人都可以解釋這種行爲嗎?

+0

改變'0.5'和'70'的順序,你會得到39:'Int(70 * attack/defense * 0.5)'。 – ThunderFrame

回答

0

如果在計算損傷的兩條線上擺脫了Int()函數,兩者的結果都是相同的。您不應該使用Int,因爲這會產生錯誤的行爲,您應該在使用CLng時轉換爲Long變量,或者如果損壞是Int,則應該使用CInt。

Int和CInt的行爲不同。 Int總是舍入到下一個較低的整數 - 而CInt將使用銀行家舍入舍入或舍入。對於尾數爲0.5的數字,您通常會看到此行爲。

至於變體和雙重差異,如果您爲第一個代碼塊的MsgBox執行TypeName,則會發現在分配值之後攻擊和防禦已被轉換爲double,儘管已聲明爲變種。

+1

...如果你退出'Int'調用,並且在'attack'和'defense'值的分配中將RHS表達式改爲常量,那麼你得到兩個版本都返回40:'attack = 119.264824'和'defense = 104.356721' 。不知道你在做什麼。如果函數* *假設*返回一個'整數'值,那麼應該使用'CInt'來確定類型轉換的明確性。 OP的問題是*爲什麼*當'attack'和'defense'是'Variant'與顯式'Double'時,結果不同,而不是*如何「修復」。 –

+0

但是,對於'損壞太長',轉換應該是'CLng',而不是'CInt'。 –

+0

對不起,沒錯,更新更好地回答了這裏發生的事情。 – Amorpheuses

5

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

答案在於結果從Variant被強制爲Double的時間。 A 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# 

好了,現在你可能會問這與2個表達式之間的區別有什麼關係。 VBA使用它可以的「最高階」變量類型來評估數學表達式。

在你把所有聲明爲Double在右手側的可變的第二個Sub,操作與Double高階評估,然後在結果被隱式轉換爲Variant視爲合格之前參數爲Int()

在你有Variant聲明你的第一個Sub,將隱式轉換爲Variant不流通到Int之前執行 - 在數學表達式中的最高階數Variant,所以傳遞前進行沒有隱式轉換結果爲Int() - Variant仍包含原始IEEE 754浮點數。

Intdocumentation

兩個INT和修復除去數的小數部分,並返回 所得整數值。

不執行舍入。頂端代碼調用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)) 
+0

不錯。值得注意的是,當類型是「Currency」而不是「Double」時,兩個版本都會返回40。 –

+2

@ Mat'sMug - [Currency](http://stackoverflow.com/documentation/vba/3418/data-types-and-limits/11782/currency#t=201701300402070599064)將所有東西都縮放10,000,只關心4位數字在小數點的右邊。它在第5位進行四捨五入,所以相當於Int(Round(399999)。99999,4))/ 10000' – Comintern

+0

@Comintern很好,謝謝你的信息:) –