1. ホーム
  2. c#

[解決済み】整数の割り算が常に切り上げられるようにするにはどうしたらいいですか?

2022-04-01 12:27:08

質問

整数の割り算で、必要な場合は必ず切り上げられるようにしたい。これより良い方法はないでしょうか?キャスティングが多いのです :-)

(int)Math.Ceiling((double)myInt1 / myInt2)

解決方法は?

UPDATE: この質問は 2013年1月のブログで取り上げた . 素晴らしい質問をありがとうございました


整数の演算を正しく行うのは難しい。これまで散々実証されてきたように、quot;cleft"なトリックをやろうとした瞬間に、間違いである確率が高いのです。 また、欠陥が見つかった場合、その欠陥を修正するためにコードを変更することもあります。 その修正が他の何かを壊すかどうかを考えずに は、良い問題解決手法とは言えません。 今のところ、この全く特別難しくない問題に対して、5種類の間違った整数演算の解決法が投稿されていると思います。

整数演算の問題に取り組む正しい方法、つまり最初から答えを出せる可能性を高める方法は、問題に慎重に取り組み、一歩ずつ解決し、その際に優れた工学的原則を用いることです。

まず、置き換えようとしているものの仕様書を読むことから始めましょう。 整数の割り算の仕様に明記されています。

  1. 除算は結果を0に丸める

  2. 2つのオペランドが同じ符号であれば結果は0または正、反対の符号であれば0または負となります。

  3. 左オペランドが表現可能な最小の int で、右オペランドが -1 の場合、オーバーフローが発生します。[ArithmeticException をスローするか、オーバーフローを報告せず、左オペランドの値を結果とするかは、実装によって決まります。

  4. 右オペランドの値が 0 の場合、System.DivideByZeroException がスローされます。

私たちが欲しいのは、商を計算し、結果を丸める整数の除算関数です。 常に上向き ではなく 常にゼロに向けて .

だから、その関数の仕様を書けよ。 私たちの関数 int DivRoundUp(int dividend, int divisor) は、可能な限りの入力に対する振る舞いを定義しておく必要があります。その未定義の振る舞いは深く憂慮すべきものなので、それを排除しましょう。私たちの演算はこのような仕様になっていると言うことにしよう。

  1. 演算は、除数がゼロの場合にスローされます。

  2. 演算は、配当がint.minvalで除数が-1である場合に投げられます。

  3. 余りがない場合 -- 除算が「偶数」である場合 -- 戻り値は積分商となります。

  4. それ以外の場合は 最小 である整数が より大きい は商より大きい、つまり常に四捨五入される。

これで仕様が決まったので、次のようなものが出てくることがわかりました。 テスト可能な設計 . さらに、「商をdoubleで計算するのではなく、整数演算のみで問題を解く」という設計基準を追加するとします。

では、何を計算すればよいのでしょうか。 整数演算だけを使いながら仕様を満たすには、明らかに3つの事実を知る必要があります。まず、整数の商が何であったか?2つ目は、余りのない除算であったか?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;
}

これは賢いか?いいえ、美しいですか?いいえ、短いですか?いいえ、仕様に即しているか? そう思っていますが、十分に検証したわけではありません。 でも、かなり良さそうですね。

私たちはプロフェッショナルです。良いエンジニアリングプラクティスを使いましょう。ツールを研究し、望ましい動作を特定し、まずエラーケースを検討し、そして 明らかに正しいことを強調するようにコードを書いてください。 バグを見つけたら、比較の方向をランダムに入れ替えたり、すでに動作しているものを壊したりする前に、そもそもアルゴリズムに深い欠陥がないかどうかを検討しましょう。