Bagaimana saya bisa memastikan bahwa pembagian bilangan bulat selalu dikumpulkan?
242
Saya ingin memastikan bahwa pembagian bilangan bulat selalu dikumpulkan jika perlu. Apakah ada cara yang lebih baik dari ini? Ada banyak casting yang terjadi. :-)
Bisakah Anda lebih jelas mendefinisikan apa yang Anda anggap "lebih baik"? Lebih cepat? Singkat? Lebih tepat? Lebih kuat? Lebih jelas benar?
Eric Lippert
6
Anda selalu memiliki banyak casting dengan matematika di C # - itu sebabnya itu bukan bahasa yang bagus untuk hal semacam ini. Apakah Anda ingin nilai dibulatkan atau menjauh dari nol - harus -3.1 pergi ke -3 (naik) atau -4 (menjauh dari nol)
Keith
9
Eric: Apa yang Anda maksud dengan "Lebih akurat? Lebih kuat? Lebih jelas benar?" Sebenarnya yang saya maksudkan hanyalah "lebih baik", saya akan membiarkan pembaca memasukkan makna menjadi lebih baik. Jadi, jika seseorang memiliki kode yang lebih pendek, bagus, jika yang lain memiliki kode yang lebih cepat, juga bagus :-) Bagaimana dengan Anda, Anda punya saran?
Karsten
1
Apakah saya satu-satunya yang, setelah membaca judul, meskipun, "Oh, itu semacam pengumpulan C #?"
Matt Ball
6
Sungguh menakjubkan betapa sulitnya pertanyaan ini ternyata, dan seberapa instruktif diskusi itu.
Mendapatkan aritmatika integer benar sulit. Seperti yang telah ditunjukkan sejauh ini, saat Anda mencoba melakukan trik "pintar", kemungkinan besar Anda telah melakukan kesalahan. Dan ketika cacat ditemukan, mengubah kode untuk memperbaiki cacat tanpa mempertimbangkan apakah perbaikan merusak sesuatu yang lain bukanlah teknik pemecahan masalah yang baik. Sejauh ini kami sudah memikirkan lima solusi aritmatika integer salah yang berbeda untuk masalah yang tidak terlalu sulit ini.
Cara yang tepat untuk mendekati masalah bilangan bulat aritmatika - yaitu, cara yang meningkatkan kemungkinan mendapatkan jawaban yang benar pertama kali - adalah dengan mendekati masalah dengan hati-hati, menyelesaikannya satu langkah pada satu waktu, dan menggunakan prinsip-prinsip teknik yang baik dalam melakukan begitu.
Mulailah dengan membaca spesifikasi untuk apa yang ingin Anda ganti. Spesifikasi untuk divisi integer dengan jelas menyatakan:
Divisi ini membulatkan hasil ke nol
Hasilnya nol atau positif ketika kedua operan memiliki tanda yang sama dan nol atau negatif ketika kedua operan memiliki tanda yang berlawanan
Jika operan kiri adalah int representable terkecil dan operan kanan adalah -1, terjadi overflow. [...] itu adalah implementasi yang ditentukan apakah [sebuah ArithmeticException] dilemparkan atau overflow tidak dilaporkan dengan nilai yang dihasilkan adalah dari operan kiri.
Jika nilai operan yang tepat adalah nol, sebuah System.DivideByZeroException dilemparkan.
Apa yang kita inginkan adalah fungsi pembagian bilangan bulat yang menghitung hasil bagi tetapi putaran hasilnya selalu ke atas , tidak selalu menuju nol .
Jadi tulis spesifikasi untuk fungsi itu. Fungsi kami int DivRoundUp(int dividend, int divisor)harus memiliki perilaku yang ditentukan untuk setiap input yang mungkin. Perilaku tak terdefinisi itu sangat mengkhawatirkan, jadi mari kita hilangkan itu. Kami akan mengatakan bahwa operasi kami memiliki spesifikasi ini:
Operasi melempar jika pembagi adalah nol
operasi melempar jika dividen adalah int.minval dan pembagi -1
jika tidak ada sisa - pembagian adalah 'genap' - maka nilai kembali adalah hasil bagi yang tidak terpisahkan
Kalau tidak, ia mengembalikan bilangan bulat terkecil yang lebih besar dari hasil bagi, yaitu selalu bulat .
Sekarang kami memiliki spesifikasi, jadi kami tahu kami dapat membuat desain yang dapat diuji . Misalkan kita menambahkan kriteria desain tambahan bahwa masalah diselesaikan hanya dengan aritmatika bilangan bulat, daripada menghitung hasil bagi sebagai ganda, karena solusi "ganda" telah secara eksplisit ditolak dalam pernyataan masalah.
Jadi apa yang harus kita hitung? Jelas, untuk memenuhi spesifikasi kami sambil tetap hanya dalam hitung bilangan bulat, kita perlu mengetahui tiga fakta. Pertama, apa hasil bagi integer? Kedua, apakah pembagian itu bebas dari sisa? Dan ketiga, jika tidak, apakah hasil bagi bilangan bulat dihitung dengan membulatkan ke atas atau ke bawah?
Sekarang kita memiliki spesifikasi dan desain, kita dapat mulai menulis kode.
publicstaticintDivRoundUp(int dividend,int divisor){if(divisor ==0)throw...if(divisor ==-1&& dividend ==Int32.MinValue)throw...int roundedTowardsZeroQuotient = dividend / divisor;bool dividedEvenly =(dividend % divisor)==0;if(dividedEvenly)return roundedTowardsZeroQuotient;// At this point we know that divisor was not zero // (because we would have thrown) and we know that // dividend was not zero (because there would have been no remainder)// Therefore both are non-zero. Either they are of the same sign, // or opposite signs. If they're of opposite sign then we rounded // UP towards zero so we're done. If they're of the same sign then // we rounded DOWN towards zero, so we need to add one.bool wasRoundedDown =((divisor >0)==(dividend >0));if(wasRoundedDown)return roundedTowardsZeroQuotient +1;elsereturn roundedTowardsZeroQuotient;}
Apakah ini pintar? Tidak, cantik? Tidak, pendek? Tidak. Sesuai dengan spesifikasinya? Saya percaya begitu, tetapi saya belum sepenuhnya mengujinya. Ini terlihat cukup bagus.
Kami profesional di sini; gunakan praktik rekayasa yang baik. Teliti alat Anda, tentukan perilaku yang diinginkan, pertimbangkan kasus kesalahan terlebih dahulu, dan tulis kode untuk menekankan kebenarannya. Dan ketika Anda menemukan bug, pertimbangkan apakah algoritma Anda sangat cacat untuk memulai sebelum Anda secara acak mulai menukar arah perbandingan di sekitar dan memecah hal-hal yang sudah berfungsi.
Yang saya pedulikan bukanlah perilaku; salah satu perilaku tampaknya dapat dibenarkan. Yang saya pedulikan adalah bahwa itu tidak ditentukan , yang berarti tidak mudah diuji. Dalam hal ini, kami mendefinisikan operator kami sendiri, sehingga kami dapat menentukan perilaku apa pun yang kami suka. Saya tidak peduli apakah perilaku itu "melempar" atau "tidak melempar", tetapi saya peduli bahwa itu dinyatakan.
Eric Lippert
68
Sial, gagal gagal :(
Jon Skeet
32
Man - Bisakah Anda menulis buku tentang itu, tolong?
xtofl
76
@finnw: Apakah saya sudah mengujinya atau tidak tidak relevan. Memecahkan masalah aritmatika integer ini bukan masalah bisnis saya ; jika ya, maka saya akan mengujinya. Jika seseorang ingin mengambil kode dari orang asing dari internet untuk menyelesaikan masalah bisnis mereka maka tanggung jawab ada pada mereka untuk mengujinya secara menyeluruh.
Eric Lippert
49
Semua jawaban di sini sejauh ini agak rumit.
Di C # dan Java, untuk dividen dan pembagi positif, Anda hanya perlu melakukan:
Luar biasa. Meskipun, Anda harus menambahkan tanda kurung, untuk menghilangkan ambiguitas. Buktinya agak panjang, tapi Anda bisa merasakannya di usus, bahwa itu benar, hanya dengan melihatnya.
Jörgen Sigvardsson
1
Hmmm ... bagaimana dengan dividend = 4, divisor = (- 2) ??? 4 / (-2) = (-2) = (-2) setelah dibulatkan ke atas. tetapi algoritma yang Anda berikan (4 + (-2) - 1) / (-2) = 1 / (-2) = (-0.5) = 0 setelah dibulatkan.
Scott
1
@ Esc - maaf, saya dihilangkan untuk menyebutkan bahwa solusi ini hanya berlaku untuk dividen dan pembagi positif. Saya telah memperbarui jawaban saya untuk menyebutkan klarifikasi ini.
Ian Nelson
1
Saya suka itu, tentu saja Anda dapat memiliki limpahan buatan dalam pembilang sebagai produk sampingan dari pendekatan ini ...
TCC
2
@ Tagtag: Idenya bagus, tetapi penggunaan modulo salah. Ambil 13 dan 3. Hasil yang diharapkan 5, tetapi ((13-1)%3)+1)berikan 1 sebagai hasilnya. Mengambil pembagian yang tepat, 1+(dividend - 1)/divisormemberikan hasil yang sama dengan jawaban untuk dividen dan pembagi positif. Juga, tidak ada masalah melimpah, betapapun buatan mereka.
Lutz Lehmann
48
Jawaban akhir berbasis int
Untuk bilangan bulat yang ditandatangani:
int div = a / b;if(((a ^ b)>=0)&&(a % b !=0))
div++;
Untuk bilangan bulat yang tidak ditandatangani:
int div = a / b;if(a % b !=0)
div++;
Alasan untuk jawaban ini
Divisi integer ' /' didefinisikan untuk membulatkan ke nol (7.7.2 dari spesifikasi), tetapi kami ingin mengumpulkan. Ini berarti bahwa jawaban negatif sudah dibulatkan dengan benar, tetapi jawaban positif perlu disesuaikan.
Jawaban positif non-nol mudah dideteksi, tetapi jawaban nol sedikit lebih rumit, karena itu bisa berupa pembulatan dari nilai negatif atau pembulatan dari yang positif.
Taruhan paling aman adalah mendeteksi kapan jawabannya harus positif dengan memeriksa bahwa tanda-tanda kedua bilangan bulat itu identik. Integer xor operator ' ^' pada kedua nilai akan menghasilkan 0 sign-bit ketika hal ini terjadi, yang berarti hasil non-negatif, sehingga pemeriksaan (a ^ b) >= 0menentukan bahwa hasilnya seharusnya positif sebelum pembulatan. Perhatikan juga bahwa untuk bilangan bulat tak bertanda, setiap jawaban jelas positif, sehingga pemeriksaan ini dapat dihilangkan.
Cek yang tersisa hanyalah apakah pembulatan telah terjadi, yang a % b != 0akan melakukan pekerjaan.
Pelajaran yang dipetik
Aritmatika (bilangan bulat atau sebaliknya) tidak semudah kelihatannya. Diperlukan pemikiran yang cermat setiap saat.
Juga, walaupun jawaban akhir saya mungkin tidak sesederhana atau sejelas atau bahkan secepat puasa jawaban floating point, ia memiliki satu kualitas penebusan yang sangat kuat bagi saya; Saya sekarang telah beralasan melalui jawaban, jadi saya benar-benar yakin itu benar (sampai seseorang yang lebih cerdas mengatakan sebaliknya - pandangan sekilas ke arah Eric -).
Untuk mendapatkan perasaan kepastian yang sama tentang jawaban floating point, saya harus melakukan lebih banyak (dan mungkin lebih rumit) memikirkan apakah ada kondisi di mana presisi floating-point menghalangi, dan apakah Math.Ceilingmungkin sesuatu yang tidak diinginkan pada input 'tepat'.
Jalan itu dilalui
Ganti (perhatikan saya ganti dengan yang kedua , myInt1dengan myInt2asumsi itulah yang Anda maksud):
(int)Math.Ceiling((double)myInt1 / myInt2)
dengan:
(myInt1 -1+ myInt2)/ myInt2
Satu-satunya peringatan adalah bahwa jika myInt1 - 1 + myInt2meluap tipe integer yang Anda gunakan, Anda mungkin tidak mendapatkan apa yang Anda harapkan.
Alasan ini salah : -1000000 dan 3999 harus memberi -250, ini memberi -249
EDIT:
Mengingat ini memiliki kesalahan yang sama dengan solusi integer lainnya untuk myInt1nilai negatif , mungkin lebih mudah untuk melakukan sesuatu seperti:
int rem;int div =Math.DivRem(myInt1, myInt2,out rem);if(rem >0)
div++;
Itu harus memberikan hasil yang benar dalam divmenggunakan operasi integer saja.
Alasan ini salah : -1 dan -5 harus memberi 1, ini memberi 0
EDIT (sekali lagi, dengan perasaan):
Operator divisi berputar ke nol; untuk hasil negatif ini tepat, sehingga hanya hasil non-negatif yang perlu penyesuaian. Juga mempertimbangkan bahwa DivRemhanya melakukan a /dan a %, mari kita lewati panggilan (dan mulai dengan perbandingan mudah untuk menghindari perhitungan modulo ketika tidak diperlukan):
int div = myInt1 / myInt2;if((div >=0)&&(myInt1 % myInt2 !=0))
div++;
Alasan ini salah : -1 dan 5 harus memberi 0, ini memberi 1
(Dalam pembelaan saya sendiri terhadap upaya terakhir saya seharusnya tidak pernah mencoba jawaban yang beralasan saat pikiran saya memberi tahu saya bahwa saya terlambat 2 jam untuk tidur)
Saya benar-benar terlambat ke pesta, tetapi apakah kode ini dikompilasi? Kelas Matematika saya tidak mengandung metode Plafon yang membutuhkan dua argumen.
@Outlaw, anggap semua yang Anda inginkan. Tetapi bagi saya jika mereka tidak memasukkannya ke dalam pertanyaan, saya biasanya menganggap mereka tidak mempertimbangkannya.
JaredPar
1
Menulis penolong tidak berguna jika hanya itu. Sebagai gantinya, tulislah pembantu dengan test suite yang komprehensif.
dolmen
3
@dolmen Apakah Anda terbiasa dengan konsep penggunaan kembali kode ? oO
Rushyo
4
Anda dapat menggunakan sesuatu seperti berikut ini.
a / b +((Math.Sign(a)*Math.Sign(b)>0)&&(a % b !=0))?1:0)
Kode ini jelas salah dalam dua cara. Pertama, ada kesalahan kecil dalam sintaksis; Anda membutuhkan lebih banyak tanda kurung. Tetapi yang lebih penting, itu tidak menghitung hasil yang diinginkan. Misalnya, coba pengujian dengan = -1000000 dan b = 3999. Hasil pembagian integer reguler adalah -250. Divisi ganda adalah -250,0625 ... Perilaku yang diinginkan adalah untuk mengumpulkan. Jelas pembulatan yang benar dari -250.0625 adalah pembulatan ke -250, tetapi kode Anda membulatkan ke -249.
Eric Lippert
36
Maaf karena harus terus mengatakan ini, tetapi kode Anda MASIH SALAH. 1/2 seharusnya membulatkan HINGGA 1, tetapi kode Anda membaliknya BAWAH ke 0. Setiap kali saya menemukan bug Anda "memperbaikinya" dengan memperkenalkan bug lain. Saran saya: berhentilah melakukan itu. Ketika seseorang menemukan bug dalam kode Anda, jangan hanya menampar perbaikan tanpa memikirkan dengan jelas apa yang menyebabkan bug itu terjadi. Gunakan praktik rekayasa yang baik; temukan kekurangan dalam algoritme dan perbaiki. Kelemahan dalam ketiga versi yang salah dari algoritma Anda adalah bahwa Anda tidak menentukan dengan benar kapan pembulatan "turun".
Eric Lippert
8
Sulit dipercaya berapa banyak bug yang bisa ada dalam kode kecil ini. Saya tidak pernah punya banyak waktu untuk memikirkannya - hasilnya memanifestasikan dalam komentar. (1) a * b> 0 akan benar jika tidak meluap. Ada 9 kombinasi untuk tanda a dan b - [-1, 0, +1] x [-1, 0, +1]. Kita dapat mengabaikan case b == 0 meninggalkan 6 case [-1, 0, +1] x [-1, +1]. a / b putaran menuju nol, yaitu pembulatan untuk hasil negatif dan pembulatan untuk resuls positif. Oleh karena itu penyesuaian harus dilakukan jika a dan b memiliki tanda yang sama dan keduanya bukan nol.
Daniel Brückner
5
Jawaban ini mungkin adalah hal terburuk yang saya tulis di SO ... dan sekarang terhubung oleh blog Eric ... Yah, maksud saya bukan untuk memberikan solusi yang mudah dibaca; Saya benar-benar mengunci hack pendek dan cepat. Dan untuk mempertahankan solusi saya lagi, saya mendapatkan ide yang benar pertama kali, tetapi tidak memikirkan meluap. Jelas kesalahan saya untuk memposting kode tanpa menulis dan mengujinya di VisualStudio. "Perbaikan" bahkan lebih buruk - saya tidak menyadari bahwa itu adalah masalah meluap dan saya pikir saya membuat kesalahan logis. Karena itu "perbaikan" pertama tidak mengubah apa pun; Saya baru saja membalikkan
Daniel Brückner
10
logika dan mendorong bug di sekitar. Di sini saya membuat kesalahan berikutnya; seperti Eric sudah sebutkan saya tidak benar-benar menganalisis bug dan hanya melakukan hal pertama yang kelihatannya benar. Dan saya masih tidak menggunakan VisualStudio. Oke, saya sedang terburu-buru dan tidak menghabiskan lebih dari lima menit untuk "memperbaiki", tetapi ini seharusnya tidak menjadi alasan. Setelah saya Eric berulang kali menunjukkan bug, saya menyalakan VisualStudio dan menemukan masalah sebenarnya. Perbaikan menggunakan Sign () menjadikannya lebih tidak dapat dibaca dan mengubahnya menjadi kode yang tidak ingin Anda pertahankan. Saya belajar pelajaran saya dan tidak akan lagi meremehkan betapa rumitnya
Daniel Brückner
-2
Beberapa jawaban di atas menggunakan pelampung, ini tidak efisien dan benar-benar tidak perlu. Untuk int unsigned, ini adalah jawaban yang efisien untuk int1 / int2:
(int1==0)?0:(int1-1)/int2+1;
Untuk int yang ditandatangani ini tidak akan benar
Bukan apa yang diminta OP sejak awal, tidak benar-benar menambah jawaban lain.
santamanno
-4
Masalah dengan semua solusi di sini adalah apakah mereka membutuhkan pemain atau mereka memiliki masalah numerik. Melakukan casting ke float atau double selalu menjadi pilihan, tetapi kita bisa melakukan yang lebih baik.
Ketika Anda menggunakan kode jawaban dari @jerryjvl
int div = myInt1 / myInt2;if((div >=0)&&(myInt1 % myInt2 !=0))
div++;
ada kesalahan pembulatan. 1/5 akan membulatkan, karena 1% 5! = 0. Tapi ini salah, karena pembulatan hanya akan terjadi jika Anda mengganti 1 dengan 3, sehingga hasilnya adalah 0,6. Kita perlu menemukan cara untuk mengumpulkan ketika perhitungan memberi kita nilai lebih dari atau sama dengan 0,5. Hasil dari operator modulo pada contoh atas memiliki rentang dari 0 hingga myInt2-1. Pembulatan hanya akan terjadi jika sisanya lebih besar dari 50% pembagi. Jadi kode yang disesuaikan terlihat seperti ini:
int div = myInt1 / myInt2;if(myInt1 % myInt2 >= myInt2 /2)
div++;
Tentu saja kami memiliki masalah pembulatan di myInt2 / 2 juga, tetapi hasil ini akan memberi Anda solusi pembulatan yang lebih baik daripada yang lain di situs ini.
"Kita perlu menemukan cara untuk mengumpulkan ketika perhitungan memberi kita nilai lebih dari atau sama dengan 0,5" - Anda telah melewatkan titik pertanyaan ini - atau selalu mengumpulkan yaitu OP ingin mengumpulkan 0,001 ke 1.
Jawaban:
UPDATE: Pertanyaan ini adalah topik blog saya pada Januari 2013 . Terima kasih atas pertanyaannya!
Mendapatkan aritmatika integer benar sulit. Seperti yang telah ditunjukkan sejauh ini, saat Anda mencoba melakukan trik "pintar", kemungkinan besar Anda telah melakukan kesalahan. Dan ketika cacat ditemukan, mengubah kode untuk memperbaiki cacat tanpa mempertimbangkan apakah perbaikan merusak sesuatu yang lain bukanlah teknik pemecahan masalah yang baik. Sejauh ini kami sudah memikirkan lima solusi aritmatika integer salah yang berbeda untuk masalah yang tidak terlalu sulit ini.
Cara yang tepat untuk mendekati masalah bilangan bulat aritmatika - yaitu, cara yang meningkatkan kemungkinan mendapatkan jawaban yang benar pertama kali - adalah dengan mendekati masalah dengan hati-hati, menyelesaikannya satu langkah pada satu waktu, dan menggunakan prinsip-prinsip teknik yang baik dalam melakukan begitu.
Mulailah dengan membaca spesifikasi untuk apa yang ingin Anda ganti. Spesifikasi untuk divisi integer dengan jelas menyatakan:
Divisi ini membulatkan hasil ke nol
Hasilnya nol atau positif ketika kedua operan memiliki tanda yang sama dan nol atau negatif ketika kedua operan memiliki tanda yang berlawanan
Jika operan kiri adalah int representable terkecil dan operan kanan adalah -1, terjadi overflow. [...] itu adalah implementasi yang ditentukan apakah [sebuah ArithmeticException] dilemparkan atau overflow tidak dilaporkan dengan nilai yang dihasilkan adalah dari operan kiri.
Jika nilai operan yang tepat adalah nol, sebuah System.DivideByZeroException dilemparkan.
Apa yang kita inginkan adalah fungsi pembagian bilangan bulat yang menghitung hasil bagi tetapi putaran hasilnya selalu ke atas , tidak selalu menuju nol .
Jadi tulis spesifikasi untuk fungsi itu. Fungsi kami
int DivRoundUp(int dividend, int divisor)
harus memiliki perilaku yang ditentukan untuk setiap input yang mungkin. Perilaku tak terdefinisi itu sangat mengkhawatirkan, jadi mari kita hilangkan itu. Kami akan mengatakan bahwa operasi kami memiliki spesifikasi ini:Operasi melempar jika pembagi adalah nol
operasi melempar jika dividen adalah int.minval dan pembagi -1
jika tidak ada sisa - pembagian adalah 'genap' - maka nilai kembali adalah hasil bagi yang tidak terpisahkan
Kalau tidak, ia mengembalikan bilangan bulat terkecil yang lebih besar dari hasil bagi, yaitu selalu bulat .
Sekarang kami memiliki spesifikasi, jadi kami tahu kami dapat membuat desain yang dapat diuji . Misalkan kita menambahkan kriteria desain tambahan bahwa masalah diselesaikan hanya dengan aritmatika bilangan bulat, daripada menghitung hasil bagi sebagai ganda, karena solusi "ganda" telah secara eksplisit ditolak dalam pernyataan masalah.
Jadi apa yang harus kita hitung? Jelas, untuk memenuhi spesifikasi kami sambil tetap hanya dalam hitung bilangan bulat, kita perlu mengetahui tiga fakta. Pertama, apa hasil bagi integer? Kedua, apakah pembagian itu bebas dari sisa? Dan ketiga, jika tidak, apakah hasil bagi bilangan bulat dihitung dengan membulatkan ke atas atau ke bawah?
Sekarang kita memiliki spesifikasi dan desain, kita dapat mulai menulis kode.
Apakah ini pintar? Tidak, cantik? Tidak, pendek? Tidak. Sesuai dengan spesifikasinya? Saya percaya begitu, tetapi saya belum sepenuhnya mengujinya. Ini terlihat cukup bagus.
Kami profesional di sini; gunakan praktik rekayasa yang baik. Teliti alat Anda, tentukan perilaku yang diinginkan, pertimbangkan kasus kesalahan terlebih dahulu, dan tulis kode untuk menekankan kebenarannya. Dan ketika Anda menemukan bug, pertimbangkan apakah algoritma Anda sangat cacat untuk memulai sebelum Anda secara acak mulai menukar arah perbandingan di sekitar dan memecah hal-hal yang sudah berfungsi.
sumber
Semua jawaban di sini sejauh ini agak rumit.
Di C # dan Java, untuk dividen dan pembagi positif, Anda hanya perlu melakukan:
Sumber: Konversi Angka, Roland Backhouse, 2001
sumber
((13-1)%3)+1)
berikan 1 sebagai hasilnya. Mengambil pembagian yang tepat,1+(dividend - 1)/divisor
memberikan hasil yang sama dengan jawaban untuk dividen dan pembagi positif. Juga, tidak ada masalah melimpah, betapapun buatan mereka.Jawaban akhir berbasis int
Untuk bilangan bulat yang ditandatangani:
Untuk bilangan bulat yang tidak ditandatangani:
Alasan untuk jawaban ini
Divisi integer '
/
' didefinisikan untuk membulatkan ke nol (7.7.2 dari spesifikasi), tetapi kami ingin mengumpulkan. Ini berarti bahwa jawaban negatif sudah dibulatkan dengan benar, tetapi jawaban positif perlu disesuaikan.Jawaban positif non-nol mudah dideteksi, tetapi jawaban nol sedikit lebih rumit, karena itu bisa berupa pembulatan dari nilai negatif atau pembulatan dari yang positif.
Taruhan paling aman adalah mendeteksi kapan jawabannya harus positif dengan memeriksa bahwa tanda-tanda kedua bilangan bulat itu identik. Integer xor operator '
^
' pada kedua nilai akan menghasilkan 0 sign-bit ketika hal ini terjadi, yang berarti hasil non-negatif, sehingga pemeriksaan(a ^ b) >= 0
menentukan bahwa hasilnya seharusnya positif sebelum pembulatan. Perhatikan juga bahwa untuk bilangan bulat tak bertanda, setiap jawaban jelas positif, sehingga pemeriksaan ini dapat dihilangkan.Cek yang tersisa hanyalah apakah pembulatan telah terjadi, yang
a % b != 0
akan melakukan pekerjaan.Pelajaran yang dipetik
Aritmatika (bilangan bulat atau sebaliknya) tidak semudah kelihatannya. Diperlukan pemikiran yang cermat setiap saat.
Juga, walaupun jawaban akhir saya mungkin tidak sesederhana atau sejelas atau bahkan secepat puasa jawaban floating point, ia memiliki satu kualitas penebusan yang sangat kuat bagi saya; Saya sekarang telah beralasan melalui jawaban, jadi saya benar-benar yakin itu benar (sampai seseorang yang lebih cerdas mengatakan sebaliknya - pandangan sekilas ke arah Eric -).
Untuk mendapatkan perasaan kepastian yang sama tentang jawaban floating point, saya harus melakukan lebih banyak (dan mungkin lebih rumit) memikirkan apakah ada kondisi di mana presisi floating-point menghalangi, dan apakah
Math.Ceiling
mungkin sesuatu yang tidak diinginkan pada input 'tepat'.Jalan itu dilalui
Ganti (perhatikan saya ganti dengan yang kedua ,
myInt1
denganmyInt2
asumsi itulah yang Anda maksud):dengan:
Satu-satunya peringatan adalah bahwa jika
myInt1 - 1 + myInt2
meluap tipe integer yang Anda gunakan, Anda mungkin tidak mendapatkan apa yang Anda harapkan.Alasan ini salah : -1000000 dan 3999 harus memberi -250, ini memberi -249
EDIT:
Mengingat ini memiliki kesalahan yang sama dengan solusi integer lainnya untuk
myInt1
nilai negatif , mungkin lebih mudah untuk melakukan sesuatu seperti:Itu harus memberikan hasil yang benar dalam
div
menggunakan operasi integer saja.Alasan ini salah : -1 dan -5 harus memberi 1, ini memberi 0
EDIT (sekali lagi, dengan perasaan):
Operator divisi berputar ke nol; untuk hasil negatif ini tepat, sehingga hanya hasil non-negatif yang perlu penyesuaian. Juga mempertimbangkan bahwa
DivRem
hanya melakukan a/
dan a%
, mari kita lewati panggilan (dan mulai dengan perbandingan mudah untuk menghindari perhitungan modulo ketika tidak diperlukan):Alasan ini salah : -1 dan 5 harus memberi 0, ini memberi 1
(Dalam pembelaan saya sendiri terhadap upaya terakhir saya seharusnya tidak pernah mencoba jawaban yang beralasan saat pikiran saya memberi tahu saya bahwa saya terlambat 2 jam untuk tidur)
sumber
Kesempatan sempurna untuk menggunakan metode ekstensi:
Ini juga membuat kode Anda dapat dibaca:
sumber
Anda bisa menulis pembantu.
sumber
Anda dapat menggunakan sesuatu seperti berikut ini.
sumber
Beberapa jawaban di atas menggunakan pelampung, ini tidak efisien dan benar-benar tidak perlu. Untuk int unsigned, ini adalah jawaban yang efisien untuk int1 / int2:
Untuk int yang ditandatangani ini tidak akan benar
sumber
Masalah dengan semua solusi di sini adalah apakah mereka membutuhkan pemain atau mereka memiliki masalah numerik. Melakukan casting ke float atau double selalu menjadi pilihan, tetapi kita bisa melakukan yang lebih baik.
Ketika Anda menggunakan kode jawaban dari @jerryjvl
ada kesalahan pembulatan. 1/5 akan membulatkan, karena 1% 5! = 0. Tapi ini salah, karena pembulatan hanya akan terjadi jika Anda mengganti 1 dengan 3, sehingga hasilnya adalah 0,6. Kita perlu menemukan cara untuk mengumpulkan ketika perhitungan memberi kita nilai lebih dari atau sama dengan 0,5. Hasil dari operator modulo pada contoh atas memiliki rentang dari 0 hingga myInt2-1. Pembulatan hanya akan terjadi jika sisanya lebih besar dari 50% pembagi. Jadi kode yang disesuaikan terlihat seperti ini:
Tentu saja kami memiliki masalah pembulatan di myInt2 / 2 juga, tetapi hasil ini akan memberi Anda solusi pembulatan yang lebih baik daripada yang lain di situs ini.
sumber