Saya ingin memastikan bahwa pembagian bilangan bulat selalu dikumpulkan jika perlu. Apakah ada cara yang lebih baik dari ini? Ada banyak casting yang terjadi. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Saya ingin memastikan bahwa pembagian bilangan bulat selalu dikumpulkan jika perlu. Apakah ada cara yang lebih baik dari ini? Ada banyak casting yang terjadi. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
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.
public static int DivRoundUp(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;
else
return 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.
Semua jawaban di sini sejauh ini agak rumit.
Di C # dan Java, untuk dividen dan pembagi positif, Anda hanya perlu melakukan:
( dividend + divisor - 1 ) / divisor
((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.
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++;
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.
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'.
Ganti (perhatikan saya ganti dengan yang kedua , myInt1
dengan myInt2
asumsi itulah yang Anda maksud):
(int)Math.Ceiling((double)myInt1 / myInt2)
dengan:
(myInt1 - 1 + myInt2) / myInt2
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:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
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):
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)
Kesempatan sempurna untuk menggunakan metode ekstensi:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
Ini juga membuat kode Anda dapat dibaca:
int result = myInt.DivideByAndRoundUp(4);
Anda bisa menulis pembantu.
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
Anda dapat menggunakan sesuatu seperti berikut ini.
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
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
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.