if (a-b <0)와 if (a <b)의 차이점


252

Java의 ArrayList소스 코드를 읽고 if 문에서 몇 가지 비교를 발견했습니다.

Java 7에서이 방법 grow(int)은

if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

Java 6 grow에는 존재하지 않았습니다. ensureCapacity(int)그러나이 방법 은

if (newCapacity < minCapacity)
    newCapacity = minCapacity;

변화의 원인은 무엇입니까? 성능 문제입니까 아니면 스타일입니까?

나는 0과 비교하는 것이 더 빠르다고 생각할 수 있지만, 음수인지 확인하기 위해 완전한 빼기를 수행하는 것은 나에게 약간 과잉 것 같습니다. 또한 바이트 코드와 관련 하여 하나 ( ) 대신 두 개의 명령어 ( ISUB및 IF_ICMPGE) 가 필요 IFGE합니다.


35
@Tunaki 오버플로 방지 측면에서 if (newCapacity - minCapacity < 0)보다 더 나은 방법은 if (newCapacity < minCapacity)무엇입니까?
— Eran

3
언급 된 부호 오버플로가 실제로 그 이유인지 궁금합니다. 뺄셈은 오버플로의 후보로 보입니다. 구성 요소는 "이것은 오버플로되지 않습니다"라고 말하고, 두 변수는 모두 음이 아닙니다.
— Joop Eggen

12
참고로, 비교를 수행하는 것이 "완전 빼기"를 수행하는 것보다 빠르다고 생각합니다. 필자의 경험에 따르면 머신 코드 수준에서는 일반적으로 빼기를 수행하고 결과를 버리고 결과 플래그를 확인하여 비교를 수행합니다.
— David Dubois

6
@David Dubois : OP는 비교가 뺄셈보다 빠르다고 가정하지 않았지만 0과 의 비교가 임의의 두 값을 비교하는 것보다 빠를 수 있으며 실제 뺄셈을 처음 수행 할 때 이것이 유지되지 않는다고 올바르게 가정합니다 0과 비교할 값을 얻으려면. 그것은 모두 합리적입니다.
— Holger

답변:


285

a < b그리고 a - b < 0다른 두 가지를 의미 할 수있다. 다음 코드를 고려하십시오.

int a = Integer.MAX_VALUE;
int b = Integer.MIN_VALUE;
if (a < b) {
    System.out.println("a < b");
}
if (a - b < 0) {
    System.out.println("a - b < 0");
}

실행하면이 인쇄 만됩니다 a - b < 0. 일어나는 일은 a < b분명히 거짓이지만 a - b넘쳐서되며 -1이는 부정적인 것입니다.

이제 말했듯이 배열의 길이가 실제로 거의 같다고 생각하십시오 Integer.MAX_VALUE. 코드 ArrayList는 다음과 같습니다.

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);

oldCapacity정말 가까운 Integer.MAX_VALUE그래서 newCapacity(인 oldCapacity + 0.5 * oldCapacity) 오버플 될 수있다 Integer.MIN_VALUE(즉, 음수). 그런 다음 minCapacity 언더 플로를 빼면 양수로 다시 돌아갑니다.

이 검사는가 if실행되지 않았는지 확인합니다 . 코드는 다음과 같이 작성한다면 if (newCapacity < minCapacity), 그것은 것 true(이후이 경우 newCapacity이 때문에 음수) newCapacity로 강제 될 수 minCapacity에 관계없이 중 oldCapacity.

이 오버플로 사례는 다음 if에 의해 처리됩니다. 때 newCapacity용량이 초과되었습니다,이 될 것이다 true: MAX_ARRAY_SIZE로 정의 Integer.MAX_VALUE - 8하고 Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0있다 true. 는 newCapacity따라서 바르게 처리됩니다 hugeCapacity방법 반환 MAX_ARRAY_SIZE또는 Integer.MAX_VALUE.

NB : 이것이이 // overflow-conscious code방법 의 의견입니다.


8
수학과 CS의 차이점에 대한 좋은 데모
— piggybox

36
@ piggybox 나는 그렇게 말하지 않을 것입니다. 이것은 수학입니다. Z에서는 수학이 아니라 정수 모듈로 2 ^ 32 버전 (정규 표현이 평소와 다르게 선택됨)입니다. 그것은 "롤 컴퓨터와 그 단점"뿐만 아니라 적절한 수학적 시스템입니다.
— harold

2
오버플로되지 않은 코드를 작성했을 것입니다.
— Aleksandr Dubinsky

IIRC 프로세서 a - b는 최상위 비트가 a인지 확인 하고 수행하여 부호있는 정수에 대해보다 적은 명령을 구현 합니다 1. 오버플로를 어떻게 처리합니까?
— Ben Leggiero

2
@ BenC.R. Leggiero x86은 조건부 명령어와 함께 사용하기 위해 별도의 레지스터에서 상태 플래그를 통해 다양한 조건을 추적합니다. 이 레지스터에는 결과 부호, 결과 0, 마지막 산술 연산에서 오버 / 언더 플로 발생 여부에 대한 별도의 비트가 있습니다.

105

이 설명을 찾았습니다 .

2010 년 3 월 9 일 화요일 03:02에 Kevin L. Stern은 다음과 같이 썼습니다.

빠른 검색을했는데 Java가 실제로 2의 보수 기반 인 것으로 보입니다. 그럼에도 불구하고, 일반적으로 이러한 유형의 코드는 어떤 시점에서 누군가가 Dmytro가 제안한 것을 따라 와서 정확하게 수행 할 것으로 기대하기 때문에 일반적 으로이 유형의 코드가 나를 걱정한다는 것을 지적하십시오. 즉, 누군가가 바뀔 것입니다.

if (a - b > 0)

에

if (a > b)

배 전체가 가라 앉습니다 나는 개인적으로 정수 오버플로를 알고리즘의 필수 기반으로 만드는 것과 같은 모호한 것을 피하는 것이 좋습니다. 일반적으로 오버플로를 피하고 오버플로 시나리오를보다 명시 적으로 만드는 것을 선호합니다.

if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) {
   // Do something
} else {
  // Do something else
}

좋은 지적입니다.

에 ArrayList있기 때문에 우리는 (또는 적어도하지 양립)이 작업을 수행 할 수있는 ensureCapacity공개 API이고 효과적으로 이미 충족 할 수없는 양의 용량에 대한 요청으로 음수를 받아들입니다.

현재 API는 다음과 같이 사용됩니다.

int newcount = count + len;
ensureCapacity(newcount);

오버플로를 피하려면 덜 자연스러운 것으로 변경해야합니다.

ensureCapacity(count, len);
int newcount = count + len;

어쨌든, 나는 오버플로에 민감한 코드를 유지하고 있지만 경고 주석을 추가하고 거대한 배열 생성을 "아웃 라이닝"하여 ArrayList코드는 다음과 같습니다.

/**
 * Increases the capacity of this <tt>ArrayList</tt> instance, if
 * necessary, to ensure that it can hold at least the number of elements
 * specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
public void ensureCapacity(int minCapacity) {
    modCount++;

    // Overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
    // Overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

Webrev가 재생성되었습니다.

남자 이름

Java 6에서 API를 다음과 같이 사용하는 경우 :

int newcount = count + len;
ensureCapacity(newcount);

그리고 newCount넘침 (이것은 음수가 됨), if (minCapacity > oldCapacity)거짓을 반환하고 실수로 ArrayList가 증가 했다고 가정 할 수 있습니다 len.


2
좋은 생각이지만 구현에ensureCapacity 의해 모순된다 ; 경우 minCapacity부정적이다, 당신은 결코를 얻을 포인트는 그냥 자동으로 방지하기 위해 복잡한 구현 척 무시합니다. 따라서 공개 API 호환성을 위해“이 작업을 수행 할 수 없습니다”는 이미했던 것처럼 이상한 주장입니다. 이 동작에 의존하는 유일한 발신자는 내부 통화입니다.
— Holger

1
@Holger minCapacity매우 음인 경우 (즉 int, 추가하려는 요소 수에 ArrayList의 현재 크기를 추가 할 때 오버플로가 발생) minCapacity - elementData.length다시 오버플로되고 양수가됩니다. 그렇게 이해합니다.
— Eran

1
@Holger 그러나 Java 8에서 다시로 변경 if (minCapacity > minExpand)했는데 이해할 수 없습니다.
— Eran

예, addAll현재 크기와 새 요소 수의 합이 오버플로 될 수 있기 때문에 두 가지 방법 만 관련이 있습니다. 그럼에도 불구하고 이것들은 내부 호출이며“ ensureCapacity공개 API 이기 때문에 변경할 수 없습니다 ”라는 주장은 사실 이상한 주장이며 ensureCapacity음수 값을 무시합니다. Java 8 API는 해당 동작을 변경하지 않았습니다. ArrayList초기 상태 일 때 (즉, 기본 용량으로 초기화되고 여전히 비어있는 경우) 기본 용량 아래의 용량을 무시하는 것 입니다.
— Holger

다시 말해서, newcount = count + len내부 사용에 관해서는 추론 이 맞지만, 그 public방법 에는 적용되지 않습니다 ensureCapacity()…
— Holger

19

코드를 보면 :

int newCapacity = oldCapacity + (oldCapacity >> 1);

경우 oldCapacity매우 큰,이 오버 플로우되며, newCapacity음의 수있을 것입니다. 과 같은 비교 newCapacity < oldCapacity는 잘못 평가 true하고 ArrayList성장하지 못할 것입니다.

대신, 작성된 코드 ( newCapacity - minCapacity < 0false를 반환 함)는 newCapacity다음 행에서 음수 값을 추가로 평가할 수 있도록하여 ( )를 newCapacity호출하여을 hugeCapacity(를 newCapacity = hugeCapacity(minCapacity);) ArrayList증가시킬 수 있도록 하여 다시 계산 합니다 MAX_ARRAY_SIZE.

이것은 무엇 // overflow-conscious code다소 비스듬하지만 댓글, 의사 소통을 시도하고있다.

결론적으로, 새로운 비교 ArrayList는 사전 정의 된 것보다 더 큰 것을 할당하지 MAX_ARRAY_SIZE않으면 서 필요한 경우 바로 그 한계까지 커질 수 있습니다.


1

두 가지 형식은식이 a - b오버플로 되지 않는 한 정확히 동일하게 동작하며 ,이 경우에는 반대입니다. 경우 a큰 음, 그리고 b큰 긍정적, 다음 (a < b)분명 사실이지만, a - b그렇게 긍정적이 될 오버플로 (a - b < 0)false입니다.

x86 어셈블리 코드에 익숙한 경우 SF = OF 일 때 if 문의 본문을 분기 하는 (a < b)로 구현되는 것을 고려 jge하십시오. 반면에 SF = 0 일 때 분기되는 (a - b < 0)a처럼 jns작동합니다. 따라서 OF = 1 일 때는 다르게 동작합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.