Gerekirse bir tamsayı bölümünün her zaman yuvarlanmasını sağlamak istiyorum. Bundan daha iyi bir yol var mı? Çok fazla döküm var. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Gerekirse bir tamsayı bölümünün her zaman yuvarlanmasını sağlamak istiyorum. Bundan daha iyi bir yol var mı? Çok fazla döküm var. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Yanıtlar:
GÜNCELLEME: Bu soru Ocak 2013'teki blogumun konusuydu . Bu mükemmel soru için teşekkürler!
Tamsayı aritmetiğini doğru yapmak zordur. Şimdiye kadar çokça gösterildiği gibi, "akıllı" bir numara yapmaya çalıştığınız anda, hata yaptığınız ihtimaller iyidir. Ve bir kusur bulunduğunda, düzeltmenin başka bir şeyi bozup bozmadığını düşünmeden kusuru düzeltmek için kodu değiştirmek iyi bir problem çözme tekniği değildir. Şimdiye kadar, bu tamamen özellikle zor olmayan soruna beş farklı yanlış tamsayı aritmetik çözüm olduğunu düşünüyorum.
Tamsayı aritmetik problemlerine yaklaşmanın doğru yolu - yani, cevabı ilk seferde doğru yapma olasılığını artıran yol - soruna dikkatlice yaklaşmak, her seferinde bir adım çözmek ve iyi mühendislik prensiplerini kullanmaktır yani.
Değiştirmeye çalıştığınız şeyin özelliklerini okuyarak başlayın. Tamsayı bölme spesifikasyonu açıkça belirtmektedir:
Bölme sonucu sıfıra yuvarlar
İki işlenen aynı işarete sahip olduğunda sonuç sıfır veya pozitiftir ve iki işlenen ters işarete sahip olduğunda sıfır veya negatiftir.
Sol işlenen temsil edilebilir en küçük int ise ve sağ işlenen –1 ise taşma oluşur. [...], [bir ArithmeticException] öğesinin atılıp atılmadığı veya taşma işleminin, sonuçta elde edilen değer sol işlenenin değeri ile bildirilmeyip bildirilmeyeceği olarak tanımlanır.
Sağ işlenenin değeri sıfırsa, bir System.DivideByZeroException oluşturulur.
İstediğimiz, bölümü hesaplayan ancak sonucu her zaman sıfıra değil, daima yukarı doğru yuvarlayan bir tamsayı bölme işlevidir .
Bu işlev için bir şartname yazın. İşlevimizin int DivRoundUp(int dividend, int divisor)
olası her girdi için tanımlanmış davranışı olmalıdır. Bu tanımsız davranış derinden endişe verici, bu yüzden onu ortadan kaldıralım. Operasyonumuzun bu spesifikasyona sahip olduğunu söyleyeceğiz:
bölen sıfırsa işlem atar
temettü int. minval ve bölen -1 ise işlem atar
kalan yoksa - bölünme 'çift' ise - dönüş değeri integral katsayısıdır
Aksi takdirde , bölümden daha büyük olan en küçük tamsayıyı döndürür , yani her zaman yukarı yuvarlar.
Şimdi bir spesifikasyonumuz var, bu yüzden test edilebilir bir tasarım geliştirebileceğimizi biliyoruz . "Çifte" çözüm, sorun ifadesinde açıkça reddedildiği için, sorunun bir çift olarak hesaplanması yerine, yalnızca tamsayı aritmetiği ile çözülebileceği ek bir tasarım kriteri eklediğimizi varsayalım.
Peki ne hesaplamalıyız? Açıkçası, sadece tamsayı aritmetiğinde kalırken spesifikasyonumuzu karşılamak için üç gerçeği bilmemiz gerekir. İlk olarak, tamsayı bölümü neydi? İkincisi, bölünmenin geri kalanı yok muydu? Ve üçüncüsü, değilse, tamsayı bölümü yukarı veya aşağı yuvarlanarak hesaplandı mı?
Artık bir şartnamemiz ve bir tasarımımız olduğuna göre, kod yazmaya başlayabiliriz.
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;
}
Bu akıllı mı? Güzel değil? Hayır. Kısa mı? Hayır. Spesifikasyona göre doğru mu? Buna inanıyorum, ama tam olarak test etmedim. Yine de oldukça iyi görünüyor.
Biz burada profesyoneliz; iyi mühendislik uygulamalarını kullanır. Araçlarınızı araştırın, istediğiniz davranışı belirtin, önce hata durumlarını göz önünde bulundurun ve açık doğruluğunu vurgulamak için kodu yazın. Bir hata bulduğunuzda, rastgele karşılaştırmaların yönlerini değiştirmeye ve zaten çalışan şeyleri kırmaya başlamadan önce algoritmanızın başlamak için derinden kusurlu olup olmadığını düşünün.
Şimdiye kadar tüm cevaplar oldukça karmaşık görünüyor.
C # ve Java'da, pozitif temettü ve bölen için yapmanız gerekenler:
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)
sonuç olarak 1 verir. Doğru türdeki bölünmeyi almak, 1+(dividend - 1)/divisor
pozitif temettü ve bölen cevabı ile aynı sonucu verir. Ayrıca, taşma problemi yoktur, ancak yapay olabilirler.
İşaretli tamsayılar için:
int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
div++;
İmzasız tamsayılar için:
int div = a / b;
if (a % b != 0)
div++;
Tamsayı bölümü ' /
' sıfıra (spesifikasyonun 7.7.2'si) yuvarlanması olarak tanımlanır, ancak yuvarlamak istiyoruz. Bu, olumsuz cevapların zaten doğru yuvarlandığı anlamına gelir, ancak olumlu cevapların ayarlanması gerekir.
Sıfır olmayan pozitif cevapları tespit etmek kolaydır, ancak sıfır cevabı biraz daha zordur, çünkü bu negatif bir değerin yuvarlanması veya pozitif bir yuvarlamanın yuvarlanması olabilir.
En güvenli bahis, her iki tamsayı işaretinin aynı olduğunu kontrol ederek cevabın ne zaman pozitif olması gerektiğini tespit etmektir. Tamsayı xveya operatörü ' ^
' iki değerde, bu durumda 0 işaret bitiyle sonuçlanır, bu da negatif olmayan bir sonuç anlamına gelir, bu nedenle kontrol (a ^ b) >= 0
yuvarlamadan önce sonucun pozitif olması gerektiğini belirler. Ayrıca imzasız tamsayılar için her cevabın açıkça olumlu olduğuna dikkat edin, bu nedenle bu kontrol atlanabilir.
Geriye kalan tek kontrol a % b != 0
, işi yapacak yuvarlama olup olmadığıdır .
Aritmetik (tamsayı veya başka türlü) göründüğü kadar basit değildir. Her zaman dikkatli düşünmek gerekir.
Ayrıca, son cevabım belki de kayan nokta cevapları kadar 'basit' veya 'açık' veya belki de 'hızlı' olmasa da, benim için çok güçlü bir kullanım kalitesi var; Şimdi cevabı düşündüm, bu yüzden bunun doğru olduğundan eminim (biri daha akıllıca bir şey söyleyene kadar - Eric'in yönüne gizlice bakış -).
Kayan nokta cevabı hakkında aynı kesinlik hissi elde etmek için, kayan nokta hassasiyetinin yoluna girebileceği herhangi bir koşul olup olmadığını ve Math.Ceiling
belki de 'sadece doğru' girdilerde istenmeyen bir şey.
(Ben ikincisini yerini not değiştirme myInt1
ile myInt2
bu ne anlama geldiğini varsayarak,):
(int)Math.Ceiling((double)myInt1 / myInt2)
ile:
(myInt1 - 1 + myInt2) / myInt2
Tek uyarı, kullandığınız myInt1 - 1 + myInt2
tamsayı türünü aşarsa, beklediğinizi alamayabilirsiniz.
Nedeni yanlış : -1000000 ve 3999 -250 vermeli, bu -249 verir
DÜZENLEME:
Bunun negatif myInt1
değerler için diğer tamsayı çözümü ile aynı hataya sahip olduğu düşünüldüğünde, aşağıdaki gibi bir şey yapmak daha kolay olabilir:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
Bu div
yalnızca tamsayı işlemlerinin kullanımında doğru sonucu vermelidir .
Nedeni yanlış : -1 ve -5 1 vermeli, bu 0 verir
EDIT (bir kez daha, duygu ile):
Bölme operatörü sıfıra yuvarlar; negatif sonuçlar için bu kesinlikle doğrudur, bu nedenle sadece negatif olmayan sonuçların ayarlanması gerekir. Ayrıca DivRem
a /
ve her %
neyse, çağrıyı atlayalım (ve gerekli olmadığında modulo hesaplamasını önlemek için kolay karşılaştırma ile başlayalım):
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
Bunun nedeni yanlış : -1 ve 5 0 vermeli, bu 1 veriyor
(Son girişimimi kendi savunmamda, aklım bana uyku için 2 saat geç kaldığımı söylerken asla mantıklı bir cevap vermeye kalkışmamalıydım)
Bir uzatma yöntemi kullanmak için mükemmel şans:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
Bu, kod uber'inizi de okunabilir hale getirir:
int result = myInt.DivideByAndRoundUp(4);
Bir yardımcı yazabilirsiniz.
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
Aşağıdaki gibi bir şey kullanabilirsiniz.
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
Yukarıdaki cevaplardan bazıları float kullanıyor, bu verimsiz ve gerçekten gerekli değil. İmzasız ints için bu int1 / int2 için etkili bir cevaptır:
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
İmzalı girişler için bu doğru olmayacak
Buradaki tüm çözümlerle ilgili sorun, ya bir oyuncuya ihtiyaç duymaları ya da sayısal bir problemleri olması. Şamandıra veya iki katına çıkarmak her zaman bir seçenektir, ancak daha iyisini yapabiliriz.
@Jerryjvl tarafından verilen yanıtın kodunu kullandığınızda
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
bir yuvarlama hatası var. 1/5 yuvarlanır, çünkü% 1 5! = 0. Ancak bu yanlıştır, çünkü yuvarlama yalnızca 1'i 3 ile değiştirirseniz gerçekleşir, bu nedenle sonuç 0,6 olur. Hesaplama bize 0,5 veya daha büyük bir değer verdiğinde yuvarlanmanın bir yolunu bulmalıyız. Üst örnekteki modulo operatörünün sonucu 0 ila myInt2-1 arasındadır. Yuvarlama, yalnızca geri kalan bölenin% 50'sinden fazla olduğunda gerçekleşir. Ayarlanan kod şöyle görünür:
int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
div++;
Tabii ki myInt2 / 2'de de yuvarlama problemimiz var, ancak bu sonuç size bu sitedeki diğerlerinden daha iyi bir yuvarlama çözümü verecektir.