2017-01-30 19 views
5

Tôi đang sử dụng Excel 2013. Trong đoạn mã sau, VBA tính 40 cho damage:VBA: chênh lệch giữa Variant/đôi và Double

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) 

Nếu các kiểu dữ liệu được thay đổi để Double, VBA tính 39 cho 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) 

Trong gỡ rối, Variant/DoubleDouble giá trị xuất hiện giống nhau. Tuy nhiên, Variant/Double dường như có độ chính xác cao hơn.

Có ai có thể giải thích hành vi này không?

+0

thay đổi thứ tự của '0,5' và' 70', và bạn sẽ nhận được 39 một trong hai cách: 'Int (70 * attack/defense * 0.5)'. – ThunderFrame

Trả lời

0

Nếu bạn loại bỏ hàm Int() trên cả hai dòng mà thiệt hại được tính toán cả hai kết thúc đều giống nhau. Bạn không nên sử dụng Int vì điều này là tạo ra hành vi sai, bạn nên sử dụng CLng khi bạn đang chuyển đổi thành một biến Long hoặc nếu thiệt hại là một Int, bạn nên sử dụng CInt.

Int và CInt hoạt động khác nhau. Int luôn luôn làm tròn xuống số nguyên thấp hơn tiếp theo - trong khi CInt sẽ làm tròn lên hoặc xuống bằng cách sử dụng Rounder của Banker. Thông thường, bạn sẽ thấy hành vi này đối với các số có mantissa là 0,5. Đối với biến thể và sự khác biệt gấp đôi, nếu bạn gõ TypeName vào MsgBox cho khối mã 1, bạn sẽ thấy rằng cả tấn công và phòng thủ sau khi đã được gán giá trị đã được chuyển đổi thành gấp đôi mặc dù đã được khai báo là biến thể.

+1

... Bạn nhận được cả hai phiên bản trả về 40 nếu bạn rời khỏi cuộc gọi 'Int' và thay đổi biểu thức RHS thành các hằng số trong các giá trị' tấn công' và 'phòng thủ':' attack = 119.264824' và 'defense = 104.356721' . Bạn không chắc chắn những gì bạn đang nhận được. Nếu hàm * được * trả về giá trị 'Integer', thì' CInt' được sử dụng chính xác cách nó được sử dụng, để làm cho kiểu chuyển đổi rõ ràng. Câu hỏi của OP là * tại sao * kết quả là khác nhau khi 'tấn công' và' bảo vệ' là 'Biến thể' so với' Double' rõ ràng, không phải * cách * để "sửa lỗi". –

+0

Nhưng yeah, với 'damage As Long' chuyển đổi phải là' CLng', không phải 'CInt'. –

+0

Xin lỗi bạn đã đúng, cập nhật câu trả lời tốt hơn những gì đang xảy ra ở đây. – Amorpheuses

5

tldr; Nếu bạn cần độ chính xác cao hơn Double, không sử dụng Double.

Câu trả lời nằm trong thời điểm khi kết quả bị ép buộc thành một Double từ một số Variant. Một số Double là số dấu phẩy động IEEE 754, và tính năng đảo ngược đặc điểm kỹ thuật của IEEE được đảm bảo với 15 chữ số có nghĩa. giá trị của mình tán tỉnh với giới hạn đó:

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

VBA sẽ tròn bất cứ điều gì vượt quá 15 chữ số có nghĩa khi nó được ép buộc thành một đôi:

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

Trong thực tế, bạn có thể xem hành vi này trong VBE. Nhập hoặc sao chép đoạn mã sau:

Dim x As Double 
x = 39.99999999999997 

Các VBE "tự động sửa chữa" các giá trị văn chương bằng cách đúc nó vào một Double, mang đến cho bạn:

Dim x As Double 
x = 40# 

OK, vì vậy bây giờ bạn có lẽ yêu cầu những gì đã làm với sự khác biệt giữa 2 biểu thức. VBA đánh giá các biểu thức toán học bằng cách sử dụng loại biến "thứ tự cao nhất" mà nó có thể.

Trong giây Sub của bạn, nơi bạn có tất cả các biến khai báo là Double ở phía bên tay phải, các hoạt động được đánh giá theo trình tự cao Double thì kết quả được ngầm cast vào một Variant trước khi được thông qua như là thông số cho Int().

Trong Sub đầu tiên của bạn, nơi bạn có Variant tờ khai, các diễn viên tiềm ẩn để Variant không được thực hiện trước khi đi qua để Int - thứ tự cao nhất trong biểu thức toán học là Variant, vì vậy không diễn viên tiềm ẩn được thực hiện trước khi đi qua kết quả là Int() - số Variant vẫn chứa float IEEE 754 thô.

mỗi sự documentation của Int:

Cả Int và Fix loại bỏ các phần phân đoạn của số lượng và trả lại kết quả giá trị số nguyên.

Không làm tròn được thực hiện. Mã trên cùng gọi Int(39.99999999999997). Mã dưới cùng gọi Int(40). Câu trả lời "" phụ thuộc vào mức độ của lỗi dấu chấm động mà bạn muốn làm tròn. Nếu 15 công trình, thì 40 là câu trả lời "đúng". Nếu bạn muốn xếp bất kỳ thứ gì lên tới 16 hoặc nhiều chữ số có nghĩa, thì 39 là câu trả lời "đúng". Giải pháp là sử dụng Round và chỉ định mức độ chính xác mà bạn đang tìm kiếm một cách rõ ràng. Ví dụ, nếu bạn quan tâm đến đủ 15 chữ số:

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

Hãy nhớ rằng độ chính xác cao nhất mà bạn sử dụng bất cứ nơi nào trong các yếu tố đầu là 6 chữ số, vì vậy đó sẽ là một tròn logic cắt-off:

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

Rất đẹp. Đáng chú ý là cả hai phiên bản trả về 40 khi kiểu là 'Currency' thay vì' Double'. –

+2

@ Mat'sMug - [Currency] (http://stackoverflow.com/documentation/vba/3418/data-types-and-limits/11782/currency#t=201701300402070599064) chia tỷ lệ mọi thứ lên 10.000 và chỉ quan tâm đến 4 chữ số ở bên phải của số thập phân. Nó làm tròn ở chữ số 5, do đó sẽ tương đương với 'Int (Round (399999).99999, 4))/10000' – Comintern

+0

@Comintern rất đẹp, cảm ơn thông tin :) –

Các vấn đề liên quan