Tidak, ini bukan pertanyaan "Kenapa (1 / 3.0) * 3! = 1" .
Saya telah membaca tentang floating-point akhir-akhir ini; khususnya, bagaimana perhitungan yang sama dapat memberikan hasil yang berbeda pada arsitektur yang berbeda atau pengaturan optimasi.
Ini adalah masalah untuk gim video yang menyimpan replay, atau jaringan peer-to-peer (bukan server-klien), yang mengandalkan semua klien yang menghasilkan hasil yang persis sama setiap kali mereka menjalankan program - perbedaan kecil dalam satu perhitungan floating-point dapat menyebabkan kondisi permainan yang sangat berbeda pada mesin yang berbeda (atau bahkan pada mesin yang sama! )
Ini terjadi bahkan di antara prosesor yang "mengikuti" IEEE-754 , terutama karena beberapa prosesor (yaitu x86) menggunakan presisi ganda yang diperluas . Artinya, mereka menggunakan register 80-bit untuk melakukan semua perhitungan, kemudian memotong ke 64- atau 32-bit, yang mengarah ke hasil pembulatan yang berbeda dari mesin yang menggunakan 64- atau 32-bit untuk perhitungan.
Saya telah melihat beberapa solusi untuk masalah ini secara online, tetapi semua untuk C ++, bukan C #:
- Nonaktifkan mode presisi diperluas ganda (sehingga semua
double
perhitungan menggunakan IEEE-754 64-bit) menggunakan_controlfp_s
(Windows),_FPU_SETCW
(Linux?), Ataufpsetprec
(BSD). - Selalu jalankan kompiler yang sama dengan pengaturan optimisasi yang sama, dan minta semua pengguna memiliki arsitektur CPU yang sama (tidak ada permainan lintas platform). Karena "kompiler" saya sebenarnya adalah JIT, yang dapat mengoptimalkan secara berbeda setiap kali program dijalankan , saya rasa ini tidak mungkin.
- Gunakan aritmatika titik tetap, dan hindari
float
dandouble
sama sekali.decimal
akan bekerja untuk tujuan ini, tetapi akan jauh lebih lambat, dan tidak adaSystem.Math
fungsi perpustakaan yang mendukungnya.
Jadi, apakah ini bahkan masalah di C #? Bagaimana jika saya hanya bermaksud mendukung Windows (bukan Mono)?
Jika ya, apakah ada cara untuk memaksa program saya berjalan pada presisi ganda normal?
Jika tidak, apakah ada perpustakaan yang akan membantu menjaga penghitungan floating-point konsisten?
strictfp
kata kunci, yang memaksa semua perhitungan dilakukan dalam ukuran yang ditentukan (float
ataudouble
) daripada ukuran yang diperluas. Namun, Java masih memiliki banyak masalah dengan dukungan IEE-754. Sangat (sangat, sangat) sedikit bahasa pemrograman mendukung IEE-754 dengan baik.Jawaban:
Saya tahu tidak ada cara untuk membuat floating point yang deterministik normal dalam .net. JITter diizinkan untuk membuat kode yang berperilaku berbeda pada platform yang berbeda (atau antara versi .net yang berbeda). Jadi menggunakan normal
float
s dalam kode .net deterministik tidak mungkin.Solusi yang saya pertimbangkan:
Saya baru saja memulai implementasi perangkat lunak matematika 32 bit floating point. Ini dapat melakukan sekitar 70 juta penambahan / perkalian per detik pada i3 2.66GHz saya. https://github.com/CodesInChaos/SoftFloat . Jelas itu masih sangat tidak lengkap dan bermasalah.
sumber
decimal
dulu, karena jauh lebih mudah untuk dilakukan. Hanya jika terlalu lambat untuk tugas yang dihadapi, pendekatan lain akan layak untuk dipikirkan.Spesifikasi C # (§4.1.6 Jenis titik apung) secara khusus memungkinkan perhitungan titik apung dilakukan dengan menggunakan presisi yang lebih tinggi daripada hasil. Jadi, tidak, saya tidak berpikir Anda bisa membuat perhitungan itu secara langsung di .Net. Yang lain menyarankan berbagai solusi, sehingga Anda bisa mencobanya.
sumber
double
setiap kali setelah operasi menghilangkan bit yang tidak diinginkan, menghasilkan hasil yang konsisten?Halaman berikut mungkin berguna jika Anda membutuhkan portabilitas absolut dari operasi tersebut. Ini membahas perangkat lunak untuk menguji implementasi standar IEEE 754, termasuk perangkat lunak untuk meniru operasi floating point. Namun, sebagian besar informasi mungkin khusus untuk C atau C ++.
http://www.math.utah.edu/~beebe/software/ieee/
Catatan tentang titik tetap
Angka titik tetap biner juga dapat berfungsi dengan baik sebagai pengganti titik mengambang, seperti yang dibuktikan dari empat operasi aritmatika dasar:
Nomor titik tetap biner dapat diimplementasikan pada semua tipe data integer seperti int, long, dan BigInteger, dan tipe yang tidak sesuai dengan CLS uint dan ulong.
Seperti yang disarankan dalam jawaban lain, Anda bisa menggunakan tabel pencarian, di mana setiap elemen dalam tabel adalah angka titik tetap biner, untuk membantu menerapkan fungsi kompleks seperti sinus, cosinus, akar kuadrat, dan sebagainya. Jika tabel pencarian kurang granular daripada nomor titik tetap, disarankan untuk membulatkan input dengan menambahkan setengah dari granularitas tabel pencarian ke input:
sumber
const
sebagai gantistatic
konstanta, sehingga kompiler dapat mengoptimalkannya; lebih suka fungsi anggota daripada fungsi statis (agar kita dapat memanggil, mis.myDouble.LeadingZeros()
alih-alihIntDouble.LeadingZeros(myDouble)
); cobalah untuk menghindari nama variabel satu huruf (MultiplyAnyLength
, misalnya, memiliki 9, membuatnya sangat sulit untuk diikuti)unchecked
dan tipe yang tidak sesuai CLS sepertiulong
,,uint
dll. Untuk tujuan kecepatan - karena mereka sangat jarang digunakan, JIT tidak mengoptimalkannya secara agresif, jadi menggunakannya sebenarnya bisa lebih lambat daripada menggunakan tipe normal sepertilong
danint
. Juga, C # memiliki kelebihan operator , yang mana proyek ini akan mendapat manfaat besar. Akhirnya, apakah ada tes unit terkait? Selain hal-hal kecil, pekerjaan luar biasa Peter, ini sangat mengesankan!strictfp
.Apakah ini masalah untuk C #?
Iya. Arsitektur yang berbeda adalah yang paling tidak Anda khawatirkan, framerate yang berbeda, dll. Dapat menyebabkan penyimpangan karena ketidakakuratan dalam representasi float - bahkan jika itu adalah ketidakakuratan yang sama (misalnya arsitektur yang sama, kecuali GPU yang lebih lambat pada satu mesin).
Bisakah saya menggunakan System.Decimal?
Tidak ada alasan Anda tidak bisa, namun ini anjing lambat.
Apakah ada cara untuk memaksa program saya berjalan dengan presisi ganda?
Iya. Tuan rumah CLR runtime sendiri ; dan kompilasi dalam semua panggilan / bendera nessecary (yang mengubah perilaku aritmatika floating point) ke dalam aplikasi C ++ sebelum memanggil CorBindToRuntimeEx.
Apakah ada perpustakaan yang akan membantu menjaga penghitungan floating point konsisten?
Tidak yang saya tahu.
Apakah ada cara lain untuk menyelesaikan ini?
Saya telah mengatasi masalah ini sebelumnya, idenya adalah menggunakan QNumbers . Mereka adalah bentuk real yang merupakan titik tetap; tetapi bukan titik tetap pada basis-10 (desimal) - melainkan basis-2 (biner); karena ini primitif matematika pada mereka (tambah, sub, mul, div) jauh lebih cepat daripada basis-10 poin tetap naif; terutama jika
n
sama untuk kedua nilai (yang dalam kasus Anda akan menjadi). Selain itu karena mereka tidak terpisahkan mereka memiliki hasil yang jelas pada setiap platform.Ingatlah bahwa framerate masih dapat memengaruhi ini, tetapi tidak seburuk dan mudah diperbaiki menggunakan titik sinkronisasi.
Bisakah saya menggunakan lebih banyak fungsi matematika dengan QNumbers?
Ya, pulang-pergi desimal untuk melakukan ini. Selain itu, Anda harus benar-benar menggunakan tabel pencarian untuk fungsi trig (sin, cos); karena mereka benar - benar dapat memberikan hasil yang berbeda pada platform yang berbeda - dan jika Anda mengkodekannya dengan benar, mereka dapat menggunakan QNumber secara langsung.
sumber
Menurut entri blog MSDN yang agak lama ini , JIT tidak akan menggunakan SSE / SSE2 untuk floating point, semuanya x87. Karena itu, seperti yang Anda sebutkan, Anda harus khawatir tentang mode dan bendera, dan di C # itu tidak mungkin untuk dikendalikan. Jadi menggunakan operasi floating point normal tidak akan menjamin hasil yang sama persis pada setiap mesin untuk program Anda.
Untuk mendapatkan reproduksibilitas presisi ganda yang tepat, Anda harus melakukan emulasi floating point (atau titik tetap) perangkat lunak. Saya tidak tahu perpustakaan C # untuk melakukan ini.
Bergantung pada operasi yang Anda butuhkan, Anda mungkin bisa lolos dengan presisi tunggal. Inilah idenya:
Masalah besar dengan x87 adalah bahwa perhitungan mungkin dilakukan dalam akurasi 53-bit atau 64-bit tergantung pada flag presisi dan apakah register tumpah ke memori. Tetapi untuk banyak operasi, melakukan operasi dengan presisi tinggi dan mengembalikan ke presisi yang lebih rendah akan menjamin jawaban yang benar, yang menyiratkan bahwa jawabannya akan dijamin sama pada semua sistem. Apakah Anda mendapatkan presisi ekstra tidak masalah, karena Anda memiliki cukup presisi untuk menjamin jawaban yang tepat dalam kedua kasus tersebut.
Operasi yang harus bekerja dalam skema ini: penjumlahan, pengurangan, perkalian, pembagian, sqrt. Hal-hal seperti dosa, exp, dll. Tidak akan berfungsi (hasil biasanya akan cocok tetapi tidak ada jaminan). "Kapan pembulatan ganda tidak berbahaya?" Referensi ACM (dibayar reg req.)
Semoga ini membantu!
sumber
Seperti yang sudah dinyatakan oleh jawaban lain: Ya, ini adalah masalah di C # - bahkan ketika tetap menggunakan Windows murni.
Adapun solusinya: Anda dapat mengurangi (dan dengan sedikit usaha / kinerja hit) menghindari masalah sepenuhnya jika Anda menggunakan
BigInteger
kelas bawaan dan menskalakan semua perhitungan ke presisi yang ditetapkan dengan menggunakan penyebut umum untuk setiap perhitungan / penyimpanan angka-angka tersebut.Seperti yang diminta oleh OP - mengenai kinerja:
System.Decimal
mewakili angka dengan 1 bit untuk tanda dan Integer 96 bit dan "skala" (mewakili di mana titik desimal berada). Untuk semua perhitungan, Anda membuatnya harus beroperasi pada struktur data ini dan tidak dapat menggunakan instruksi floating point yang tertanam dalam CPU.The
BigInteger
"solusi" melakukan sesuatu yang serupa - hanya itu Anda dapat menentukan berapa banyak digit yang Anda butuhkan / inginkan ... mungkin Anda ingin hanya 80 bit atau 240 bit presisi.Kelambatan datang selalu karena harus mensimulasikan semua operasi pada angka ini melalui instruksi integer-only tanpa menggunakan instruksi built-in CPU / FPU yang pada gilirannya menyebabkan lebih banyak instruksi per operasi matematika.
Untuk mengurangi hit kinerja ada beberapa strategi - seperti QNumbers (lihat jawaban dari Jonathan Dickinson - Apakah matematika floating-point konsisten dalam C #? Mungkinkah? ) Dan / atau caching (misalnya perhitungan trigonometri ...) dll.
sumber
BigInteger
hanya tersedia di .Net 4.0.BigInteger
melebihi bahkan kinerja terkena Desimal.Decimal
(@Jonathan Dickinson - 'anjing lambat') atauBigInteger
(@CodeInChaos komentar di atas) - dapatkah seseorang memberikan sedikit penjelasan tentang hit kinerja ini dan apakah / mengapa mereka benar-benar pamit untuk memberikan solusi.Nah, ini akan menjadi upaya pertama saya tentang bagaimana melakukan ini :
(Saya yakin Anda hanya dapat mengkompilasi ke .dll 32-bit dan kemudian menggunakannya dengan x86 atau AnyCpu [atau kemungkinan hanya menargetkan x86 pada sistem 64-bit; lihat komentar di bawah].)
Kemudian, dengan asumsi itu berfungsi, apakah Anda ingin menggunakan Mono Saya bayangkan Anda harus dapat mereplikasi perpustakaan pada platform x86 lainnya dengan cara yang serupa (bukan COM tentu saja; walaupun, mungkin, dengan anggur? Sedikit keluar dari daerah saya sekali kita pergi ke sana ...).
Dengan asumsi Anda dapat membuatnya berfungsi, Anda harus dapat mengatur fungsi khusus yang dapat melakukan beberapa operasi sekaligus untuk memperbaiki masalah kinerja, dan Anda akan memiliki matematika titik apung yang memungkinkan Anda untuk mendapatkan hasil yang konsisten di seluruh platform dengan jumlah minimal kode yang ditulis dalam C ++, dan meninggalkan sisa kode Anda dalam C #.
sumber
x86
akan dapat memuat 32 bit dll.Saya bukan pengembang game, meskipun saya memiliki banyak pengalaman dengan masalah yang sulit secara komputasi ... jadi, saya akan melakukan yang terbaik.
Strategi yang saya akan adopsi pada dasarnya adalah ini:
Singkatnya adalah: Anda perlu menemukan keseimbangan. Jika Anda menghabiskan rendering 30 ms (~ 33fps) dan hanya 1ms yang melakukan deteksi tabrakan (atau menyisipkan beberapa operasi yang sangat sensitif) - bahkan jika Anda melipattigakan waktu yang diperlukan untuk melakukan aritmatika kritis, dampaknya pada framerate Anda adalah Anda turun dari 33,3fps ke 30,3fps.
Saya sarankan Anda membuat profil segalanya, memperhitungkan berapa banyak waktu yang dihabiskan untuk melakukan masing-masing perhitungan yang terasa mahal, lalu ulangi pengukuran dengan 1 atau lebih metode untuk menyelesaikan masalah ini dan lihat apa dampaknya.
sumber
Memeriksa tautan di jawaban lain menjelaskan bahwa Anda tidak akan pernah memiliki jaminan apakah floating point "benar" diterapkan atau apakah Anda akan selalu menerima ketepatan tertentu untuk perhitungan tertentu, tetapi mungkin Anda bisa melakukan upaya terbaik dengan (1) memotong semua kalkulasi ke minimum umum (misalnya, jika implementasi yang berbeda akan memberikan Anda 32 hingga 80 bit presisi, selalu memotong setiap operasi hingga 30 atau 31 bit), (2) memiliki tabel beberapa kasus uji pada saat startup (batas kasus menambah, mengurangi, mengalikan, membagi, sqrt, cosinus, dll) dan jika implementasi menghitung nilai yang cocok dengan tabel maka tidak perlu repot melakukan penyesuaian.
sumber
float
tipe data pada mesin x86 - namun ini akan menyebabkan hasil yang sedikit berbeda dari mesin yang melakukan semua perhitungan mereka hanya menggunakan 32-bit, dan perubahan kecil ini akan menyebar seiring waktu. Karena itu, pertanyaannya.Pertanyaan Anda dalam hal-hal yang cukup sulit dan teknis O_o. Namun saya mungkin punya ide.
Anda yakin tahu bahwa CPU melakukan beberapa penyesuaian setelah operasi apung. Dan CPU menawarkan beberapa instruksi berbeda yang membuat operasi pembulatan berbeda.
Jadi untuk ekspresi, kompiler Anda akan memilih serangkaian instruksi yang membawa Anda ke hasil. Tetapi setiap alur kerja instruksi lainnya, bahkan jika mereka bermaksud untuk menghitung ekspresi yang sama, dapat memberikan hasil lain.
'Kesalahan' yang dilakukan oleh penyesuaian pembulatan akan tumbuh pada setiap instruksi lebih lanjut.
Sebagai contoh, kita dapat mengatakan bahwa pada tingkat perakitan: a * b * c tidak setara dengan * c * b.
Saya tidak sepenuhnya yakin akan hal itu, Anda perlu meminta seseorang yang lebih mengenal arsitektur CPU daripada saya: hal
Namun untuk menjawab pertanyaan Anda: di C atau C ++ Anda dapat memecahkan masalah Anda karena Anda memiliki beberapa kontrol pada kode mesin yang dihasilkan oleh kompiler Anda, namun dalam. NET Anda tidak punya. Jadi selama kode mesin Anda bisa berbeda, Anda tidak akan pernah yakin tentang hasil yang tepat.
Saya ingin tahu bagaimana hal ini dapat menjadi masalah karena variasi tampaknya sangat minim, tetapi jika Anda benar-benar membutuhkan operasi, satu-satunya solusi yang dapat saya pikirkan adalah meningkatkan ukuran register mengambang Anda. Gunakan presisi ganda atau bahkan panjang ganda jika Anda bisa (tidak yakin itu mungkin menggunakan CLI).
Saya harap saya sudah cukup jelas, saya tidak sempurna dalam bahasa Inggris (... sama sekali: s)
sumber