Ini adalah contoh dengan komentar:
class Program
{
// first version of structure
public struct D1
{
public double d;
public int f;
}
// during some changes in code then we got D2 from D1
// Field f type became double while it was int before
public struct D2
{
public double d;
public double f;
}
static void Main(string[] args)
{
// Scenario with the first version
D1 a = new D1();
D1 b = new D1();
a.f = b.f = 1;
a.d = 0.0;
b.d = -0.0;
bool r1 = a.Equals(b); // gives true, all is ok
// The same scenario with the new one
D2 c = new D2();
D2 d = new D2();
c.f = d.f = 1;
c.d = 0.0;
d.d = -0.0;
bool r2 = c.Equals(d); // false! this is not the expected result
}
}
Jadi, apa pendapat Anda tentang ini?
c#
.net
floating-point
Alexander Efimov
sumber
sumber
c.d.Equals(d.d)
dievaluasitrue
seperti halnyac.f.Equals(d.f)
Jawaban:
Bug ada dalam dua baris berikut
System.ValueType
: (Saya masuk ke sumber referensi)(Kedua metode tersebut adalah
[MethodImpl(MethodImplOptions.InternalCall)]
)Ketika semua bidang adalah 8 byte lebar,
CanCompareBits
secara keliru mengembalikan nilai true, menghasilkan perbandingan bitwise dari dua nilai yang berbeda, tetapi identik secara semantik.Ketika setidaknya satu bidang tidak lebar 8 byte,
CanCompareBits
mengembalikan false, dan kode mulai menggunakan refleksi untuk mengulangi bidang dan memanggilEquals
setiap nilai, yang memperlakukan dengan benar-0.0
sama dengan0.0
.Berikut adalah sumber untuk
CanCompareBits
dari SSCLI:sumber
IsNotTightlyPacked
.The bug also happens with floats, but only happens if the fields in the struct add up to a multiple of 8 bytes.
Saya menemukan jawabannya di http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx .
Bagian inti adalah sumber komentar
CanCompareBits
, yangValueType.Equals
digunakan untuk menentukan apakah akan menggunakanmemcmp
perbandingan-gaya:Penulis selanjutnya menyatakan dengan tepat masalah yang dijelaskan oleh OP:
sumber
Equals(Object)
untukdouble
,float
danDecimal
berubah selama draft awal .net; Saya akan berpikir bahwa lebih penting untukX.Equals((Object)Y)
hanya mengembalikan virtualtrue
ketikaX
danY
tidak dapat dibedakan, daripada memiliki metode yang sesuai dengan perilaku kelebihan beban lainnya (terutama mengingat bahwa, karena paksaan tipe implisit,Equals
metode kelebihan beban bahkan tidak mendefinisikan hubungan kesetaraan !, mis.1.0f.Equals(1.0)
menghasilkan false, tetapi1.0.Equals(1.0f)
menghasilkan true!) Masalah sebenarnya IMHO tidak dengan cara struktur dibandingkan ...Equals
berarti sesuatu selain kesetaraan. Misalkan, misalnya, seseorang ingin menulis sebuah metode yang mengambil objek abadi dan, jika belum di-cache, melakukanToString
di atasnya dan cache hasilnya; jika telah di-cache, cukup kembalikan string yang di-cache. Bukan hal yang tidak masuk akal untuk dilakukan, tetapi akan gagal denganDecimal
karena dua nilai mungkin membandingkan sama tetapi menghasilkan string yang berbeda.Dugaan Vilx benar. Apa yang "CanCompareBits" lakukan adalah memeriksa untuk melihat apakah tipe nilai yang dimaksud "penuh sesak" dalam memori. Struct yang padat dibandingkan dengan hanya membandingkan bit biner yang membentuk struktur; struktur yang longgar dibandingkan dengan memanggil Equals pada semua anggota.
Ini menjelaskan pengamatan SLaks bahwa repros dengan struct yang semuanya berlipat ganda; struct seperti itu selalu penuh sesak.
Sayangnya seperti yang telah kita lihat di sini, itu memperkenalkan perbedaan semantik karena perbandingan bitwise dari double dan Equals perbandingan dari double memberikan hasil yang berbeda.
sumber
Setengah jawaban:
Reflector memberi tahu kami bahwa
ValueType.Equals()
melakukan sesuatu seperti ini:Sayangnya keduanya
CanCompareBits()
danFastEquals()
(keduanya metode statis) adalah eksternal ([MethodImpl(MethodImplOptions.InternalCall)]
) dan tidak memiliki sumber yang tersedia.Kembali ke menebak mengapa satu kasus dapat dibandingkan dengan bit, dan yang lainnya tidak dapat (masalah pelurusan mungkin?)
sumber
Itu memang benar bagi saya, dengan Monm gmcs 2.4.2.3.
sumber
Test case yang lebih sederhana:
EDIT : Bug ini juga terjadi dengan float, tetapi hanya terjadi jika bidang di struct menambahkan hingga kelipatan 8 byte.
sumber
double
adalah0
. Anda salah.Itu harus terkait dengan perbandingan sedikit demi sedikit, karena
0.0
harus berbeda-0.0
hanya dari sedikit sinyal.sumber
Selalu menimpa Equals dan GetHashCode pada tipe nilai. Ini akan cepat dan benar.
sumber
Hanya pembaruan untuk bug berusia 10 tahun ini: telah diperbaiki ( Penafian : Saya penulis PR ini) dalam .NET Core yang mungkin akan dirilis dalam .NET Core 2.1.0.
The posting blog menjelaskan bug dan bagaimana saya tetap itu.
sumber
Jika Anda membuat D2 seperti ini
itu benar.
jika kamu membuatnya seperti ini
Itu masih salah.
i t tampaknya seperti itu false jika struct hanya memegang ganda.
sumber
Itu harus nol terkait, karena mengubah garis
untuk:
menghasilkan perbandingan yang benar ...
sumber