[C#] 정수 나누기가 항상 반올림되도록하려면 어떻게해야합니까?

필요한 경우 정수 나누기가 항상 반올림되도록하고 싶습니다. 이것보다 더 좋은 방법이 있습니까? 진행중인 캐스팅이 많이 있습니다. 🙂

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



답변

업데이트 :이 질문은 2013 년 1 월 내 블로그의 주제였습니다 . 좋은 질문 감사합니다!


정수 산술을 올바르게 얻는 것은 어렵습니다. 지금까지 충분히 입증 된 것처럼 “영리한”트릭을 시도하는 순간 실수를 저지른 가능성은 높습니다. 결함이 발견되면 수정 사항이 다른 부분을 위반하는지 여부를 고려하지 않고 결함을 수정하도록 코드를 변경 것은 좋은 문제 해결 기술이 아닙니다. 지금까지 우리는이 완전히 문제가 아닌 어려운 문제에 대한 5 가지의 잘못된 정수 산술 솔루션을 게시했다고 생각했습니다.

정수 산술 문제에 접근하는 올바른 방법, 즉 처음에 정답을 얻을 가능성을 높이는 방법은 문제를주의 깊게 접근하고 한 번에 한 단계 씩 해결하며 올바른 엔지니어링 원칙을 사용하는 것입니다. 그래서.

교체하려는 사양을 읽으십시오. 정수 나누기 사양에는 다음과 같은 내용이 명시되어 있습니다.

  1. 나누기는 결과를 0으로 반올림합니다

  2. 두 피연산자가 동일한 부호를 갖는 경우 결과는 0 또는 양수이고 두 피연산자가 반대 부호를 갖는 경우 결과는 0 또는 음수입니다

  3. 왼쪽 피연산자가 표현할 수있는 가장 작은 int이고 오른쪽 피연산자가 –1이면 오버플로가 발생합니다. […] [anarithmeticException]이 발생하는지 아니면 결과 값이 왼쪽 피연산자의 값으로 오버 플로우되지 않는지에 대해 구현 정의됩니다.

  4. 오른쪽 피연산자의 값이 0이면 System.DivideByZeroException이 발생합니다.

우리가 원하는 것은 결과를 몫을 계산하지만 반올림 정수 분할 기능입니다 항상 위쪽으로 하지, 0에 가까워 항상 .

따라서 해당 기능에 대한 사양을 작성하십시오. 우리의 함수 int DivRoundUp(int dividend, int divisor)는 가능한 모든 입력에 대해 동작을 정의해야합니다. 정의되지 않은 동작은 매우 걱정되므로 제거하십시오. 우리는 우리의 작업이 다음 사양을 가지고 있다고 말할 것입니다 :

  1. 제수가 0 인 경우 연산이 발생합니다.

  2. 배당이 int.minval이고 제수가 -1이면 연산이 발생합니다.

  3. 나머지가없는 경우-나눗셈이 ‘짝수’인 경우 반환 값은 정수 몫입니다

  4. 그렇지 않으면 몫 보다 큰 가장 작은 정수 , 즉 항상 반올림합니다.

이제 우리는 사양을 가지고 있으므로 테스트 가능한 디자인을 만들 수 있음을 알고 있습니다. . “double”해답이 문제 설명에서 명시 적으로 거부 되었기 때문에 몫을 double로 계산하지 않고 정수 산술로만 문제를 해결한다는 추가 설계 기준을 추가한다고 가정합니다.

그래서 우리는 무엇을 계산해야합니까? 정수 산술만으로 사양을 충족하려면 세 가지 사실을 알아야합니다. 첫째, 정수 몫은 무엇입니까? 둘째, 부서에 나머지 부분이 없었습니까? 그리고 세 번째가 아닌 경우 정수 몫은 반올림 또는 내림으로 계산 되었습니까?

이제 사양과 디자인을 갖추 었으므로 코드 작성을 시작할 수 있습니다.

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 

출처 : 숫자 변환, 롤랜드 백 하우스, 2001


답변

최종 int 기반 답변

부호있는 정수의 경우 :

int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
    div++;

부호없는 정수의 경우 :

int div = a / b;
if (a % b != 0)
    div++;

이 답변에 대한 추론

정수 나누기 ‘ /‘는 0 (사양의 7.7.2)으로 반올림하도록 정의되어 있지만 반올림하려고합니다. 즉, 부정적인 답변은 이미 올바르게 반올림되지만 긍정적 인 답변은 조정해야합니다.

0이 아닌 양수 대답은 쉽게 검색 할 수 있지만 음수 값의 반올림 또는 양수 값의 반올림 일 수 있으므로 답 0은 조금 까다 롭습니다.

가장 안전한 방법은 두 정수의 부호가 모두 같은지 확인하여 정답이 언제 있어야하는지 감지하는 것입니다. 이 ^경우 두 값의 정수 xor 연산자 ‘ ‘는 음수가 아닌 결과를 의미하는 0 부호 비트가되므로 (a ^ b) >= 0반올림 전에 결과가 양수 여야한다고 확인합니다. 또한 부호없는 정수의 경우 모든 대답은 분명히 긍정적 이므로이 검사는 생략 할 수 있습니다.

남은 유일한 검사는 반올림이 발생했는지 여부뿐입니다 a % b != 0.

교훈

산술 (정수 또는 기타)은 생각만큼 간단하지 않습니다. 항상 신중하게 생각해야합니다.

또한 내 최종 답변이 부동 소수점 답변만큼 ‘단순’또는 ‘분명한’또는 ‘빠른’것도 아니지만, 그것은 나에게 매우 강력한 구속력을 가지고 있습니다. 나는 이제 대답을 통해 추론을 했으므로 실제로는 그것이 정확하다는 것을 확신합니다 ( 에릭의 지시에 따라 다른 사람이 다르게 말해 줄 때까지 ).

부동 소수점 답변에 대해 동일한 확신을 갖기 위해서는 부동 소수점 정밀도가 방해가 될 수있는 조건이 있는지 여부와 Math.Ceiling아마도 가능합니까? ‘올바른’입력에서 바람직하지 않은 것.

여행 한 길

(필자는 두 번째 교체 교체 참고 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을 제공합니다

편집 (한 번 더, 느낌으로) :
나누기 연산자가 0으로 반올림합니다. 부정적인 결과의 경우 이것은 정확하기 때문에 음이 아닌 결과 만 조정하면됩니다. 또한 점을 고려 DivRem만이 작업을 수행 /하고, %어쨌든,이 전화를 건너 (그리고 그것이 필요하지 않을 때 피하기 모듈로 계산에 쉽게 비교로 시작)하자 :

int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
    div++;

이유가 잘못되었습니다 : -1과 5는 0을 주어야합니다.

(마지막 시도에 대한 내 자신의 방어에서, 나는 내 마음이 내가 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;

부호있는 정수의 경우 올바르지 않습니다.