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과 비교하는 것이 더 빠르다고 생각할 수 있지만, 음수인지 확인하기 위해 완전한 빼기를 수행하는 것은 나에게 약간 과잉 것 같습니다. 또한 바이트 코드와 관련 하여 하나 ( ) 대신 두 개의 명령어 ( ISUBIF_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.