必要に応じて、整数の除算が常に切り上げられるようにしたいと思います。これより良い方法はありますか?多くのキャストが進行中です。:-)
(int)Math.Ceiling((double)myInt1 / myInt2)
必要に応じて、整数の除算が常に切り上げられるようにしたいと思います。これより良い方法はありますか?多くのキャストが進行中です。:-)
(int)Math.Ceiling((double)myInt1 / myInt2)
回答:
更新:この質問は、2013年1月に私のブログの主題でした。すばらしい質問をありがとう!
整数演算を正しく行うのは困難です。これまでに十分に説明されているように、「巧妙な」トリックを実行しようとした瞬間に、間違いを犯した確率は高くなります。そして、欠陥が見つかった場合、修正が他の何かを壊すかどうかを考慮せずに欠陥を修正するようにコードを変更します問題が解決さは、適切な問題解決手法ではありません。これまでのところ、投稿された完全に特に困難ではない問題に対する5つの異なる不正な整数演算ソリューションを考えてきました。
整数演算問題にアプローチする正しい方法、つまり、最初に正しい答えが得られる可能性を高める方法は、問題に注意深く取り組み、一度に1つずつ問題を解決し、優れたエンジニアリング原則を使用して実行することです。そう。
置き換えようとしているものの仕様を読むことから始めます。整数除算の仕様は明確に述べています:
除算は結果をゼロに向かって丸めます
2つのオペランドの符号が同じ場合の結果はゼロまたは正、2つのオペランドの符号が反対の場合の結果はゼロまたは負
左のオペランドが表現可能な最小の整数で、右のオペランドが–1の場合、オーバーフローが発生します。[...]これは、[ArithmeticException]がスローされるか、オーバーフローが報告されずに結果の値が左のオペランドの値になるかについて、実装によって定義されます。
右側のオペランドの値がゼロの場合、System.DivideByZeroExceptionがスローされます。
必要なのは、商を計算するが常にゼロに向かってではなく常に上に丸める整数除算関数です。
その関数の仕様を書いてください。関数にint DivRoundUp(int dividend, int divisor)
は、可能なすべての入力に対して定義された動作が必要です。その未定義の動作は非常に心配なので、それを排除しましょう。私たちの操作には次の仕様があると言います。
除数がゼロの場合、操作がスローされます
被除数がint.minvalで除数が-1の場合、演算がスローされます
余りがない場合-除算は「偶数」です-戻り値は整数の商です
それ以外の場合は、商よりも大きい最小の整数を返します。つまり、常に切り上げます。
これで仕様ができたので、テスト可能な設計を考え出すことができます。。「二重」の解が問題ステートメントで明示的に拒否されているため、商をdoubleとして計算するのではなく、整数演算のみで問題を解決するという追加の設計基準を追加するとします。
では、何を計算する必要があるのでしょうか。明らかに、整数演算のみを行いながら仕様を満たすには、3つの事実を知る必要があります。まず、整数の商は何でしたか?第二に、部門には残りがありませんでしたか?そして、3番目の場合、そうでない場合、整数商は切り上げまたは切り捨てによって計算されましたか?
仕様とデザインができたので、コードの作成を開始できます。
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;
}
これは賢いですか?いいえ。美しいですか?いいえ、短いですか?いいえ、仕様に合わせて修正しますか?私はそう信じていますが、完全にはテストしていません。それはかなりよさそうだ。
私たちはここで専門家です。適切なエンジニアリング手法を使用します。ツールを調査し、必要な動作を指定し、エラーのケースを最初に検討し、コードを記述してその明らかな正確さを強調します。そして、バグを見つけたら、比較の方向をランダムに入れ替えて、既に機能しているものを壊す前に、アルゴリズムに最初から深い欠陥があるかどうかを検討してください。
ここまでのすべての答えは、かなり複雑すぎるようです。
C#とJavaでは、プラスの配当と除数を得るには、次のことを行うだけです。
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)
、結果として1を返します。適切な除算1+(dividend - 1)/divisor
を行うと、正の配当と除数の答えと同じ結果になります。また、オーバーフローの問題はありませんが、人工的なものであってもかまいません。
符号付き整数の場合:
int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
div++;
符号なし整数の場合:
int div = a / b;
if (a % b != 0)
div++;
整数除算 ' /
'はゼロ(仕様の7.7.2)に丸めるように定義されていますが、切り上げます。つまり、否定的な回答は既に正しく丸められていますが、肯定的な回答は調整する必要があります。
ゼロ以外の正の回答は簡単に検出できますが、回答0は少し難しいです。これは、負の値の切り上げまたは正の値の切り捨てのどちらかになる可能性があるためです。
最も安全な方法は、両方の整数の符号が同じであることを確認することで、答えが正である場合を検出することです。整数XOR演算子 '^
2つの値の 'は、負でない結果を意味する場合、0の符号ビットになります。したがって、チェック(a ^ b) >= 0
は、丸める前に結果が正である必要があると判断します。また、符号なし整数の場合、すべての回答は明らかに正なので、このチェックは省略できます。
残っている唯一のチェックは、丸めが行われたかどうかです。 a % b != 0
です。
算術(整数またはそれ以外)は、見かけほど単純ではありません。常に慎重に考えることが必要です。
また、私の最終的な答えは、おそらく浮動小数点の答えほど「単純」でも「明白」でも「高速」でもないかもしれませんが、私にとって非常に強力な償還品質があります。私は今、答えを通して推論したので、私は実際にそれが正しいと確信しています(より賢い誰かが私にそう言わない限り- エリックの方向をさらに一瞥する) -)。
浮動小数点の答えについて同じ確信を得るために、浮動小数点の精度が邪魔になる可能性のある条件があるかどうか、および Math.Ceiling
おそらくそう「正しい」入力では望ましくない何か。
置き換え(2つ目myInt1
をに置き換えたことに注意してくださいmyInt2
。
(int)Math.Ceiling((double)myInt1 / myInt2)
と:
(myInt1 - 1 + myInt2) / myInt2
唯一の注意点myInt1 - 1 + myInt2
は、使用している整数型がオーバーフローすると、期待どおりの結果が得られない可能性があることです。
これが間違っている理由:-1000000と3999は-250を与え、これは-249を与えます
編集:
これが負のmyInt1
値の他の整数解と同じエラーを持っていることを考えると、次のようなことを行う方が簡単かもしれません:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
これにより、div
整数演算のみを使用した場合に正しい結果が得られます。
これが間違っている理由:-1と-5は1を与え、これは0を与えます
編集(1回以上、感覚あり):
除算演算子はゼロに向かって丸めます。否定的な結果の場合、これはまったく正しいので、否定的でない結果のみ調整が必要です。またことを考えるとDivRem
ちょうどない/
と%
とにかく、レッツ・コールをスキップ(そしてそれが必要とされていない場合にモジュロ計算を避けるために、簡単に比較で始まります):
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
これが間違っている理由:-1と5は0を与え、これは1を与えます
(私の最後の試みの弁護において、私は私の心が睡眠のために2時間遅れていると私に告げている間、理にかなった答えを試みるべきではありませんでした)
拡張メソッドを使用する絶好のチャンス:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
これにより、コードも読みやすくなります。
int result = myInt.DivideByAndRoundUp(4);
あなたはヘルパーを書くことができます。
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
次のようなものを使用できます。
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
上記の回答のいくつかは浮動小数点数を使用していますが、これは非効率で実際には必要ありません。符号なし整数の場合、これはint1 / int2の効率的な答えです。
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
署名されたintの場合、これは正しくありません
ここでのすべてのソリューションの問題は、キャストが必要か、数値的な問題があることです。floatまたはdoubleへのキャストは常にオプションですが、もっとうまくやることができます。
@jerryjvlからの回答のコードを使用する場合
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
丸め誤差があります。1/5は、1%5!= 0なので切り上げられます。ただし、1を3に置き換えた場合にのみ丸めが行われるため、結果は0.6になるため、これは誤りです。計算で0.5以上の値が得られた場合は、切り上げる方法を見つける必要があります。上の例のモジュロ演算子の結果の範囲は0〜myInt2-1です。丸めは、余りが除数の50%を超える場合にのみ行われます。したがって、調整後のコードは次のようになります。
int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
div++;
もちろん、myInt2 / 2でも丸めの問題がありますが、この結果により、このサイトの他の丸めよりも優れた丸めソリューションが得られます。