Haruskah `Vector <float> .Equals` bersifat refleksif atau haruskah mengikuti semantik IEEE 754?

9

Saat membandingkan nilai floating point untuk kesetaraan, ada dua pendekatan berbeda:

  • NaNtidak sama dengan dirinya sendiri, yang cocok dengan spesifikasi IEEE 754 .
  • NaNmenjadi sama dengan dirinya sendiri, yang menyediakan properti matematika dari Refleksivitas yang penting untuk definisi relasi Kesetaraan

Tipe floating point IEEE bawaan di C # ( floatdan double) mengikuti semantik IEEE untuk ==dan !=(dan operator relasional suka <) tetapi memastikan refleksivitas untuk object.Equals, IEquatable<T>.Equals(dan CompareTo).

Sekarang pertimbangkan perpustakaan yang menyediakan struct vektor di atas float/ double. Jenis vektor seperti itu akan membebani ==/ !=dan menimpa object.Equals/ IEquatable<T>.Equals.

Apa yang semua orang setuju pada adalah bahwa ==/ !=harus mengikuti IEEE semantik. Pertanyaannya adalah, haruskah perpustakaan seperti itu menerapkan Equalsmetode (yang terpisah dari operator kesetaraan) dengan cara yang refleksif atau dengan cara yang cocok dengan semantik IEEE.

Argumen untuk menggunakan semantik IEEE untuk Equals:

  • Ini mengikuti IEEE 754
  • Ini (mungkin jauh lebih cepat) karena dapat memanfaatkan instruksi SIMD

    Saya telah mengajukan pertanyaan terpisah tentang stackoverflow tentang bagaimana Anda akan mengekspresikan kesetaraan refleksif menggunakan instruksi SIMD dan dampak kinerjanya: Instruksi SIMD untuk perbandingan kesetaraan floating point

    Pembaruan: Sepertinya mungkin untuk menerapkan kesetaraan refleksif secara efisien menggunakan tiga instruksi SIMD.

  • Dokumentasi untuk Equalstidak memerlukan refleksivitas ketika melibatkan floating point:

    Pernyataan berikut harus benar untuk semua implementasi metode Persamaan (Objek). Dalam daftar, x, y, dan zmewakili referensi objek yang tidak nol.

    x.Equals(x)kembali true, kecuali dalam kasus yang melibatkan tipe floating-point. Lihat ISO / IEC / IEEE 60559: 2011, Teknologi informasi - Sistem Mikroprosesor - Aritmatika Titik Apung.

  • Jika Anda menggunakan pelampung sebagai kunci kamus, Anda hidup dalam keadaan berdosa dan seharusnya tidak mengharapkan perilaku waras.

Argumen untuk bersikap refleksif:

  • Ini konsisten dengan jenis yang ada, termasuk Single, Double, Tupledan System.Numerics.Complex.

    Saya tidak tahu preseden apa pun di BCL tempat Equalsmengikuti IEEE bukannya refleksif. Contoh kontra termasuk Single, Double, Tupledan System.Numerics.Complex.

  • Equalssebagian besar digunakan oleh wadah dan algoritma pencarian yang bergantung pada refleksivitas. Untuk algoritme ini, peningkatan kinerja tidak relevan jika mencegahnya bekerja. Jangan mengorbankan kebenaran untuk kinerja.
  • Rusak semua set berdasarkan hash dan kamus, Contains, Find, IndexOfpada berbagai koleksi / LINQ, operasi ditetapkan berdasarkan LINQ ( Union, Except, dll) jika data yang berisi NaNnilai-nilai.
  • Kode yang melakukan perhitungan aktual di mana semantik IEEE dapat diterima biasanya bekerja pada tipe dan penggunaan konkret ==/ !=(atau lebih mungkin perbandingan epsilon).

    Saat ini Anda tidak dapat menulis perhitungan kinerja tinggi menggunakan generik karena Anda memerlukan operasi aritmatika untuk itu, tetapi ini tidak tersedia melalui antarmuka / metode virtual.

    Jadi Equalsmetode yang lebih lambat tidak akan memengaruhi kode kinerja paling tinggi.

  • Dimungkinkan untuk memberikan IeeeEqualsmetode atau IeeeEqualityComparer<T>untuk kasus di mana Anda membutuhkan semantik IEEE atau Anda perlu untuk keuntungan kinerja.

Menurut pendapat saya argumen ini sangat mendukung implementasi refleksif.

Tim CoreFX Microsoft berencana untuk memperkenalkan tipe vektor seperti itu dalam .NET. Tidak seperti saya, mereka lebih suka solusi IEEE , terutama karena keunggulan kinerja. Karena keputusan seperti itu tentu tidak akan berubah setelah rilis final, saya ingin mendapatkan umpan balik dari masyarakat, tentang apa yang saya yakini sebagai kesalahan besar.

CodesInChaos
sumber
1
Pertanyaan yang memprovokasi dan luar biasa. Bagi saya (setidaknya), itu tidak masuk akal ==dan Equalsakan memberikan hasil yang berbeda. Banyak programmer berasumsi, dan melakukan hal yang sama . Selanjutnya - secara umum, implementasi dari operator kesetaraan memohon Equalsmetode. Anda berpendapat bahwa seseorang dapat memasukkan a IeeeEquals, tetapi orang mungkin juga melakukannya sebaliknya dan memasukkan ReflexiveEquals-metode. The Vector<float>-jenis dapat digunakan dalam banyak aplikasi kinerja-kritis, dan harus dioptimalkan sesuai.
die maus
@diemaus Beberapa alasan mengapa saya tidak menemukan yang meyakinkan: 1) untuk float/ doubledan beberapa jenis lainnya, ==dan Equalssudah berbeda. Saya pikir ketidakkonsistenan dengan tipe yang ada akan lebih membingungkan daripada ketidakkonsistenan di antara keduanya ==dan EqualsAnda masih harus berurusan dengan tipe yang lain. 2) Hampir semua algoritma / koleksi generik digunakan Equalsdan bergantung pada refleksifitasnya untuk berfungsi (LINQ dan kamus), sedangkan algoritma floating-point konkret biasanya digunakan di ==mana mereka mendapatkan semantik IEEE mereka.
CodesInChaos
Saya akan mempertimbangkan Vector<float>"binatang" yang berbeda dari yang sederhana floatatau double. Dengan ukuran itu, saya tidak bisa melihat alasan Equalsatau ==operator untuk mematuhi standar mereka. Anda berkata sendiri: "Jika Anda menggunakan pelampung sebagai kunci kamus, Anda hidup dalam keadaan berdosa dan seharusnya tidak mengharapkan perilaku waras". Jika seseorang menyimpan NaNdalam kamus, maka itu adalah kesalahan mereka sendiri karena menggunakan latihan yang mengerikan. Saya hampir tidak berpikir bahwa tim CoreFX tidak memikirkan hal ini. Saya akan pergi dengan ReflexiveEqualsatau serupa, hanya demi kinerja.
die maus

Jawaban:

5

Saya berpendapat bahwa perilaku IEEE benar. NaNs tidak setara satu sama lain dengan cara apa pun; mereka sesuai dengan kondisi yang tidak jelas di mana jawaban numerik tidak sesuai.

Di luar manfaat kinerja yang berasal dari penggunaan aritmatika IEEE yang didukung sebagian besar prosesor secara bawaan, saya pikir ada masalah semantik dengan mengatakan bahwa jika isnan(x) && isnan(y), maka x == y. Contohnya:

// C++
double inf = std::numeric_limits<double>::infinity();
double x = 0.0 / 0.0;
double y = inf - inf;

Saya berpendapat bahwa tidak ada alasan yang berarti mengapa seseorang dianggap xsetara y. Anda hampir tidak dapat menyimpulkan bahwa mereka adalah angka yang setara; mereka sama sekali bukan angka, jadi sepertinya konsep yang sama sekali tidak valid.

Selain itu, dari perspektif desain API, jika Anda bekerja pada pustaka serba guna yang dimaksudkan untuk digunakan oleh banyak pemrogram, masuk akal untuk menggunakan semantik titik-mengambang yang paling khas industri. Tujuan perpustakaan yang baik adalah untuk menghemat waktu bagi mereka yang menggunakannya, sehingga membangun perilaku yang tidak standar sudah matang untuk kebingungan.

Jason R
sumber
3
Yang NaN == NaNseharusnya mengembalikan false tidak perlu dipersoalkan lagi. Pertanyaannya adalah apa yang .Equalsharus dilakukan metode ini. Misalnya jika saya gunakan NaNsebagai kunci kamus, nilai yang terkait menjadi tidak dapat dikembalikan jika NaN.Equals(NaN)kembali salah.
CodesInChaos
1
Saya pikir Anda harus mengoptimalkan untuk kasus umum. Kasus umum untuk vektor angka adalah perhitungan numerik throughput tinggi (sering dioptimalkan dengan instruksi SIMD). Saya berpendapat bahwa menggunakan vektor sebagai kunci kamus adalah kasus penggunaan yang sangat jarang, dan orang yang sulit mendesain semantik Anda. Argumen yang tampaknya paling masuk akal bagi saya adalah konsistensi, karena ada Single, Double, dll kelas sudah memiliki perilaku refleksif. IMHO, itu hanya keputusan yang salah untuk memulai. Tapi saya tidak akan membiarkan keanggunan menghalangi kegunaan / kecepatan.
Jason R
Tetapi perhitungan numerik biasanya akan menggunakan ==yang selalu mengikuti IEEE, sehingga mereka akan mendapatkan kode cepat tidak peduli bagaimana Equalspenerapannya. IMO seluruh titik memiliki Equalsmetode terpisah digunakan dalam algoritma yang tidak peduli tentang jenis beton, seperti Distinct()fungsi LINQ .
CodesInChaos
1
Saya mengerti. Tetapi saya akan menentang API yang memiliki ==operator dan Equals()fungsi yang memiliki semantik berbeda. Saya pikir Anda membayar biaya kebingungan potensial dari perspektif pengembang, tanpa manfaat nyata (saya tidak menetapkan nilai apa pun untuk dapat menggunakan vektor angka sebagai kunci kamus). Itu hanya pendapat saya; Saya tidak berpikir ada jawaban obyektif untuk pertanyaan yang ada.
Jason R
0

Ada masalah: IEEE754 mendefinisikan operasi relasional dan kesetaraan dengan cara yang cocok untuk aplikasi numerik. Tidak cocok untuk menyortir dan hashing. Jadi jika Anda ingin mengurutkan array berdasarkan nilai numerik, atau jika Anda ingin menambahkan nilai numerik ke set atau menggunakannya sebagai kunci dalam kamus, Anda juga menyatakan bahwa nilai NaN tidak diperbolehkan, atau Anda tidak menggunakan IEEE754 operasi bawaan. Tabel hash Anda harus memastikan bahwa semua NaN dicocokkan dengan nilai yang sama, dan membandingkan sama satu sama lain.

Jika Anda mendefinisikan Vector maka Anda harus membuat keputusan desain apakah Anda ingin menggunakannya hanya untuk tujuan numerik atau apakah itu harus kompatibel dengan pengurutan dan hashing. Saya pribadi berpikir bahwa tujuan numerik harusnya jauh lebih penting. Jika pengurutan / hashing diperlukan maka Anda dapat menulis kelas dengan Vector sebagai anggota dan mendefinisikan hashing dan kesetaraan di kelas itu seperti yang Anda suka.

gnasher729
sumber
1
Saya setuju bahwa tujuan numerik lebih penting. Tapi kami sudah punya ==dan !=operator untuk mereka. Dalam pengalaman saya Equalsmetode ini cukup banyak hanya digunakan oleh algoritma non numerik.
CodesInChaos