expr에서 오버플로를 피하는 방법 A * B-C * D


161

A*B - C*D유형이 다음과 같은 표현식을 계산해야합니다 signed long long int A, B, C, D; . A*B오버플로가 발생할 수 있지만 동시에 표현 A*B - C*D은 매우 작을 수 있습니다. 올바르게 계산하려면 어떻게해야합니까?

예 : MAX * MAX - (MAX - 1) * (MAX + 1) == 1, where MAX = LLONG_MAX - n및 n-자연수


17
정확도는 얼마나 중요합니까?
Anirudh Ramanathan이

1
@Cthulhu, 좋은 질문입니다. 그는 더 작은 수를 사용하여 모두 10 또는 그로 나눈 다음 결과를 곱하여 동등한 기능을 만들려고 시도 할 수 있습니다.
Chris

4
Vars A, B, C, D가 서명되었습니다. 이는 A - C오버플로가 발생할 수 있음을 의미 합니다. 데이터에서 이런 일이 일어나지 않을 것이라는 점을 고려하거나 아는 것이 문제입니까?
William Morris

2
작업이 범람 할 경우 @MooingDuck하지만 당신은 사전에 확인하실 수 있습니다 stackoverflow.com/a/3224630/158285
bradgonesurfing

1
@Chris : 아니요, 서명 된 오버플로가 발생했는지 확인할 수있는 이식 가능한 방법이 없습니다. (브래드는 그것이 일어날 것이라는 것을 휴대용으로 감지 할 수있는 것이 맞습니다 ). 인라인 어셈블리를 사용하는 것은 이식하기 어려운 많은 방법 중 하나입니다.
Mooing Duck

답변:


120

이것은 너무 사소한 것 같아요. 그러나A*B 넘칠 수있는 것입니다.

정밀도를 잃지 않고 다음을 수행 할 수 있습니다

A*B - C*D = A(D+E) - (A+F)D
          = AD + AE - AD - DF
          = AE - DF
             ^smaller quantities E & F

E = B - D (hence, far smaller than B)
F = C - A (hence, far smaller than C)

이 분해는 추가수행 할 수 있습니다 .
@Gian이 지적했듯이 유형이 오랫동안 부호가 없으면 빼기 연산 중에주의를 기울여야 할 수도 있습니다.


예를 들어, 질문에 해당하는 경우 한 번만 반복하면됩니다.

 MAX * MAX - (MAX - 1) * (MAX + 1)
  A     B       C           D

E = B - D = -1
F = C - A = -1

AE - DF = {MAX * -1} - {(MAX + 1) * -1} = -MAX + MAX + 1 = 1

4
@Caleb, 동일한 알고리즘을 적용C*D
Chris

2
E가 무엇을 나타내는 지 설명해야한다고 생각합니다.
Caleb

7
long long과 double은 모두 64 비트입니다. double은 지수에 대해 일부 비트를 할당해야하므로 정밀도 손실없이 가능한 범위가 더 작은 범위를 갖습니다 .
Jim Garrison

3
@Cthulhu-모든 숫자가 매우 큰 경우에만 작동하는 것처럼 보입니다. 예를 들어 {A, B, C, D} = {MAX, MAX, MAX, 2}로 여전히 오버플로가 발생합니다. 영업 이익은 "각 숫자는 정말 큰 일 수있다"라고 합니다만, 각각의 번호가하는 문제 문에서 명확하지 않다 있어야 정말 큰 일.
Kevin K

4
A,B,C,D그래도 부정적인 것이 있다면 ? 하지 않음 E또는 F다음 더 큰 일?
Supr

68

가장 단순하고 가장 일반적인 해결책은 긴 정수 라이브러리 (예 : http://gmplib.org/ )를 사용하거나 구조체 또는 배열을 사용하여 표현하고 일종의 긴 곱셈을 구현 하여 오버플로 할 수없는 표현을 사용하는 것입니다 ( 즉, 각 숫자를 두 개의 32 비트 반으로 분리하고 다음과 같이 곱셈을 수행합니다.

(R1 + R2 * 2^32 + R3 * 2^64 + R4 * 2^96) = R = A*B = (A1 + A2 * 2^32) * (B1 + B2 * 2^32) 
R1 = (A1*B1) % 2^32
R2 = ((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) % 2^32
R3 = (((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) %2^32
R4 = ((((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) / 2^32) + (A2*B2) / 2^32

최종 결과가 64 비트에 적합하다고 가정하면 실제로 대부분의 R3 비트는 필요하지 않으며 R4는 필요하지 않습니다


8
위의 계산은 실제로 보이는 것처럼 복잡하지 않고, 기초 2 ^ 32에서 단순하게 긴 곱셈이며, C의 코드는 더 단순 해 보일 것입니다. 또한 프로그램에서이 작업을 수행하기위한 일반 함수를 작성하는 것이 좋습니다.
Ofir

46

이는 랩 어라운드 부호있는 오버 플로우에 의존하기 때문에 표준이 아닙니다. (GCC에는이를 가능하게하는 컴파일러 플래그가 있습니다.)

그러나의 모든 계산 long long을 수행하는 경우 수식을 직접 적용한
(A * B - C * D)결과는 올바른 결과가에 맞는 한 정확합니다 long long.


부호없는 정수를 부호있는 정수로 캐스팅하는 구현 정의 동작에만 의존하는 해결 방법이 있습니다. 그러나 이것은 오늘날 거의 모든 시스템에서 작동 할 것으로 예상됩니다.

(long long)((unsigned long long)A * B - (unsigned long long)C * D)

이는 unsigned long long오버 플로우 동작이 표준에 의해 랩 어라운드되도록 보장되는 위치로 입력을 캐스트합니다 . 마지막에 부호있는 정수로 다시 캐스팅하는 것은 구현 정의 부분이지만 오늘날 거의 모든 환경에서 작동합니다.


더 많은 pedantic 솔루션이 필요하다면 "긴 산술"을 사용해야한다고 생각합니다


+1 당신은 이것을 알아 차릴 수있는 유일한 사람입니다. 유일한 까다로운 부분은 컴파일러가 랩 어라운드 오버플로를 수행하도록 설정하고 올바른 결과가 실제로에 맞는지 확인하는 것 long long입니다.
Mysticial

2
트릭이 전혀없는 순진한 버전조차도 대부분의 구현 에서 올바른 작업을 수행합니다. 표준에 의해 보장되지는 않지만 1의 보완 기계 또는 다른 이상한 장치를 찾아야 실패 할 수 있습니다.
호브

1
이것이 중요한 답변이라고 생각합니다. 구현 고유의 동작을 가정하는 것이 올바른 프로그래밍이 아닐 수도 있지만 모든 엔지니어는 모듈로 산술과 성능이 필수적인 경우 일관된 동작을 보장하기 위해 올바른 컴파일러 플래그를 얻는 방법을 이해해야합니다. DSP 엔지니어는 고정 소수점 필터 구현을 위해이 동작에 의존하며, 허용되는 답변은 수용 할 수없는 성능을 갖습니다.
Peter M

18

이것은 작동해야합니다 (제 생각에).

signed long long int a = 0x7ffffffffffffffd;
signed long long int b = 0x7ffffffffffffffd;
signed long long int c = 0x7ffffffffffffffc;
signed long long int d = 0x7ffffffffffffffe;
signed long long int bd = b / d;
signed long long int bdmod = b % d;
signed long long int ca = c / a;
signed long long int camod = c % a;
signed long long int x = (bd - ca) * a * d - (camod * d - bdmod * a);

내 파생물은 다음과 같습니다.

x = a * b - c * d
x / (a * d) = (a * b - c * d) / (a * d)
x / (a * d) = b / d - c / a

now, the integer/mod stuff:
x / (a * d) = (b / d + ( b % d ) / d) - (c / a + ( c % a ) / a )
x / (a * d) = (b / d - c / a) - ( ( c % a ) / a - ( b % d ) / d)
x = (b / d - c / a) * a * d - ( ( c % a ) * d - ( b % d ) * a)

1
감사합니다 @ bradgonesurfing-당신은 그런 입력을 제공 할 수 있습니까? 내 답변을 업데이트하고 실행했으며 작동합니다 (bd 및 ca는 0입니다).
paquetp

1
흠. 이제는 생각하지 않을 것입니다. d = 1이고 a = 1이고 b = maxint 및 c = maxint 인 경우는 여전히 작동합니다. 쿨 :)
bradgonesurfing

1
@paquetp : a = 1, b = 0x7fffffffffffffff, c = -0x7ffffffffffffffffff, d = 1 (c는 음수 임). 영리하지만 코드가 모든 양수를 올바르게 처리한다고 확신합니다.
Mooing Duck

3
@MooingDuck이지만 세트의 최종 답변도 오버플로되어 올바른 설정이 아닙니다. 각 변의 부호가 동일한 경우에만 효과가 있으므로 결과 뺄셈이 범위 내에 있습니다.
bradgonesurfing

1
이 답변이 가장 간단하고 최고가 최고 점수를 얻은 답변과 비교하여 낮은 점수를 얻은 경우 StackOverflow에 이상한 점이 있습니다.
bradgonesurfing

9

모든 값에 대해 가장 큰 공통 요소를 계산 한 다음 산술 연산을 수행 한 다음 다시 곱하기 전에 해당 요소로 나눈 값을 고려할 수 있습니다. 이 같은 요인, 예를 들어 (단, 있다고 가정하는 경우 A, B, CD 프라임은 비교적 될 일이, 그들은 공통의 요소가되지 않습니다).

마찬가지로 로그 스케일 작업을 고려할 수도 있지만 수치 정밀도에 따라 약간 무섭습니다.


1
long double사용 가능한 경우 로그가 좋아 보입니다 . 이 경우, 허용 가능한 수준의 정밀도를 달성 할 수 있습니다 (결과는 반올림 됨).

9

결과가 long int int 인 경우 A * BC * D 표현식은 산술 모드 2 ^ 64를 수행하므로 괜찮으며 올바른 결과를 제공합니다. 문제는 결과가 긴 long int에 맞는지 아는 것입니다. 이것을 감지하려면 double을 사용하여 다음 트릭을 사용할 수 있습니다.

if( abs( (double)A*B - (double)C*D ) > MAX_LLONG ) 
    Overflow
else 
    return A*B-C*D;

이 접근 방식의 문제점은 복식의 가수 정밀도 (54 비트?)에 의해 제한되므로 제품 A * B 및 C * D를 63 + 54 비트 (또는 약간 더 적음)로 제한해야한다는 것입니다.


이것이 가장 실용적인 예입니다. 명확하고 정답을 제공합니다 (또는 입력이 나쁜 경우 예외를 던집니다).
Mark Lakata

1
멋지고 우아합니다! 당신은 다른 사람들이 쓰러 뜨린 함정에 빠지지 않았습니다. 한 가지 더 : 반올림 오류로 인해 이중 계산이 MAX_LLONG 미만인 몇 가지 예가 있습니다. 나의 수학적 본능은 대신 double과 long 결과의 차이를 계산하고 MAX_LLONG / 2 또는 다른 것과 비교해야한다고 말합니다. 이 차이는 이중 계산의 반올림 오차와 오버플로이며 일반적으로 상대적으로 낮아야하지만 언급 한 경우에는 클 것입니다. 하지만 지금은 너무 게으르다. :-)
Hans-Peter Störr 오전

9
E = max(A,B,C,D)
A1 = A -E;
B1 = B -E;
C1 = C -E;
D1 = D -E;

그때

A*B - C*D = (A1+E)*(B1+E)-(C1+E)(D1+E) = (A1+B1-C1-D1)*E + A1*B1 -C1*D1

7

배열에 각 숫자를 쓸 수 있으며 각 요소는 숫자이며 다항식으로 계산을 수행 할 수 있습니다 . 결과 다항식 (배열)을 가져 와서 배열의 각 요소에 10을 곱하여 배열의 위치 거듭 제곱 (첫 번째 위치가 가장 크고 마지막은 0)으로 결과를 계산합니다.

숫자 123는 다음과 같이 표현 될 수 있습니다.

123 = 100 * 1 + 10 * 2 + 3

이것에 대한 배열을 만듭니다 [1 2 3].

모든 숫자 A, B, C 및 D에 대해이 작업을 수행 한 다음 다항식으로 곱합니다. 결과 다항식이 있으면 그 숫자를 재구성하십시오.


2
그것이 무엇인지 모르지만 찾아야 할 것입니다. 넣어 :). 이것은 내 여자 친구와 쇼핑하는 동안 내 머리 꼭대기의 솔루션입니다 :)
Mihai

base10 배열에서 bignum을 구현하고 있습니다. GMP는 기본 4294967296을 사용하는 고품질 bignum 라이브러리입니다. 훨씬 빠릅니다. 답은 정확하고 유용하기 때문에 공감대는 없습니다.
Mooing Duck

감사 :) . 이 방법을 사용하는 것이 좋지만 더 좋은 방법이 있다는 것을 아는 것이 유용합니다. 적어도이 상황에서는 :)
Mihai

어쨌든 ...이 솔루션을 사용하면 기본 유형 (예 : 100 자리 숫자 s)보다 훨씬 큰 컴퓨터를 사용하여 결과를 배열로 유지할 수 있습니다. 이것은 투표를받을 가치가있다 : p
Mihai

이 방법 (효과적이고 비교적 이해하기 쉽지만)이 메모리가 배고프고 느리기 때문에 공감대를 얻는 것은 확실하지 않습니다.
Mooing Duck

6

A는 동안 signed long long int보유하지 않습니다 A*B, 그들 중 두 사람은 것이다. 따라서 A*B다른 지수의 트리 항으로 분해 될 수 있습니다 signed long long int.

A1=A>>32;
A0=A & 0xffffffff;
B1=B>>32;
B0=B & 0xffffffff;

AB_0=A0*B0;
AB_1=A0*B1+A1*B0;
AB_2=A1*B1;

동일합니다 C*D.

직선 방식 Folowing의 subraction는 모든 쌍 수행 할 수 AB_iCD_i각각에 대한 추가 캐리 비트 (정확하게 1 비트 정수)를 사용하여, 마찬가지로. 따라서 E = A * BC * D라고하면 다음과 같은 결과가 나타납니다.

E_00=AB_0-CD_0 
E_01=(AB_0 > CD_0) == (AB_0 - CD_0 < 0) ? 0 : 1  // carry bit if overflow
E_10=AB_1-CD_1 
...

우리는 상반신 E_10E_20(32만큼 쉬프트하고 추가 한 다음, 상반신을 지우십시오)를 계속해서 전송합니다 E_10.

이제 캐리 비트 E_11를 비 캐리 부분에서 가져온 오른쪽 부호와 함께 추가 하여 캐리 비트 를 제거 할 수 있습니다 E_20. 이것이 오버플로를 유발하면 결과도 맞지 않습니다.

E_10이제 상위 절반을 E_00 (shift, add, erase) 및 carry bit 에서 가져 오기에 충분한 '공간'이 있습니다 E_01.

E_10이제 더 커질 수 있으므로로 전송을 반복합니다 E_20.

이 시점에서 E_200 이 되어야합니다. 그렇지 않으면 결과가 맞지 않습니다. E_10전송 결과로 인해 상단 도 비어 있습니다.

마지막 단계는 아래쪽 절반 E_20E_10다시 옮깁니다.

보류에 E=A*B+C*D적합한 기대가 있다면signed long long int

E_20=0
E_10=0
E_00=E

1
이것은 Ofir의 곱셈 공식을 사용하고 쓸모없는 모든 임시 결과를 제거하면 얻을 수있는 단순화 된 공식입니다.
dronus

3

최종 결과가 정수 유형으로 표현 가능하다는 것을 알고 있다면 아래 코드를 사용하여이 계산을 빠르게 수행 할 수 있습니다. C 표준은 부호없는 산술이 모듈로 산술이고 오버플로되지 않도록 지정하기 때문에 부호없는 유형을 사용하여 계산을 수행 할 수 있습니다.

다음 코드는 동일한 너비의 부호없는 유형이 있고 부호있는 유형은 모든 비트 패턴을 사용하여 값을 표시한다고 가정합니다 (트랩 표현 없음, 부호있는 유형의 최소값은 부호없는 유형의 계수의 절반에 해당함). 이것이 C 구현에서 유지되지 않는 경우,이를 위해 ConvertToSigned 루틴을 간단하게 조정할 수 있습니다.

다음은 코드를 사용 signed char하고 unsigned char보여줍니다. 귀하의 구현을 위해,의 정의 변경 Signedtypedef signed long long int Signed;와의 정의 Unsigned에를 typedef unsigned long long int Unsigned;.

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>


//  Define the signed and unsigned types we wish to use.
typedef signed char   Signed;
typedef unsigned char Unsigned;

//  uHalfModulus is half the modulus of the unsigned type.
static const Unsigned uHalfModulus = UCHAR_MAX/2+1;

//  sHalfModulus is the negation of half the modulus of the unsigned type.
static const Signed   sHalfModulus = -1 - (Signed) (UCHAR_MAX/2);


/*  Map the unsigned value to the signed value that is the same modulo the
    modulus of the unsigned type.  If the input x maps to a positive value, we
    simply return x.  If it maps to a negative value, we return x minus the
    modulus of the unsigned type.

    In most C implementations, this routine could simply be "return x;".
    However, this version uses several steps to convert x to a negative value
    so that overflow is avoided.
*/
static Signed ConvertToSigned(Unsigned x)
{
    /*  If x is representable in the signed type, return it.  (In some
        implementations, 
    */
    if (x < uHalfModulus)
        return x;

    /*  Otherwise, return x minus the modulus of the unsigned type, taking
        care not to overflow the signed type.
    */
    return (Signed) (x - uHalfModulus) - sHalfModulus;
}


/*  Calculate A*B - C*D given that the result is representable as a Signed
    value.
*/
static signed char Calculate(Signed A, Signed B, Signed C, Signed D)
{
    /*  Map signed values to unsigned values.  Positive values are unaltered.
        Negative values have the modulus of the unsigned type added.  Because
        we do modulo arithmetic below, adding the modulus does not change the
        final result.
    */
    Unsigned a = A;
    Unsigned b = B;
    Unsigned c = C;
    Unsigned d = D;

    //  Calculate with modulo arithmetic.
    Unsigned t = a*b - c*d;

    //  Map the unsigned value to the corresponding signed value.
    return ConvertToSigned(t);
}


int main()
{
    //  Test every combination of inputs for signed char.
    for (int A = SCHAR_MIN; A <= SCHAR_MAX; ++A)
    for (int B = SCHAR_MIN; B <= SCHAR_MAX; ++B)
    for (int C = SCHAR_MIN; C <= SCHAR_MAX; ++C)
    for (int D = SCHAR_MIN; D <= SCHAR_MAX; ++D)
    {
        //  Use int to calculate the expected result.
        int t0 = A*B - C*D;

        //  If the result is not representable in signed char, skip this case.
        if (t0 < SCHAR_MIN || SCHAR_MAX < t0)
            continue;

        //  Calculate the result with the sample code.
        int t1 = Calculate(A, B, C, D);

        //  Test the result for errors.
        if (t0 != t1)
        {
            printf("%d*%d - %d*%d = %d, but %d was returned.\n",
                A, B, C, D, t0, t1);
            exit(EXIT_FAILURE);
        }
    }
    return 0;
}

2

오버플로하지 않는 더 작은 성분으로 방정식을 깰 수 있습니다.

AB - CD
= [ A(B - N) - C( D - M )] + [AN - CM]

= ( AK - CJ ) + ( AN - CM)

    where K = B - N
          J = D - M

구성 요소가 여전히 오버 플로우되면 작은 구성 요소를 재귀 적으로 분할 한 다음 다시 결합 할 수 있습니다.


이것은 정확하거나 정확하지 않을 수 있지만 혼란 스럽습니다. 당신은 정의 K하고 J, 왜 안 NM. 또한 방정식을 더 큰 조각 으로 나누고 있다고 생각합니다 . 3 단계는 OP의 질문과 동일하기 때문에 더 복잡한 점을 제외하고 (AK-CJ)->(AB-CD)
Mooing Duck

N은 아무것도 단순화되지 않습니다. 더 작게 만들기 위해 A에서 빼는 숫자입니다. 실제로 그것은 paquetp와 비슷하지만 열등한 해결책입니다. 여기에서는 정수 나누기 대신 빼기를 사용하여 더 작게 만듭니다.
bradgonesurfing

2

모든 경우를 다루지 않았거나 엄격하게 테스트하지는 않았지만 16 비트 CPU에서 32 비트 정수 수학을 시도 할 때 80 년대에 사용했던 기술을 구현합니다. 기본적으로 32 비트를 2 개의 16 비트 장치로 분할하고 별도로 작업합니다.

public class DoubleMaths {
  private static class SplitLong {
    // High half (or integral part).
    private final long h;
    // Low half.
    private final long l;
    // Split.
    private static final int SPLIT = (Long.SIZE / 2);

    // Make from an existing pair.
    private SplitLong(long h, long l) {
      // Let l overflow into h.
      this.h = h + (l >> SPLIT);
      this.l = l % (1l << SPLIT);
    }

    public SplitLong(long v) {
      h = v >> SPLIT;
      l = v % (1l << SPLIT);
    }

    public long longValue() {
      return (h << SPLIT) + l;
    }

    public SplitLong add ( SplitLong b ) {
      // TODO: Check for overflow.
      return new SplitLong ( longValue() + b.longValue() );
    }

    public SplitLong sub ( SplitLong b ) {
      // TODO: Check for overflow.
      return new SplitLong ( longValue() - b.longValue() );
    }

    public SplitLong mul ( SplitLong b ) {
      /*
       * e.g. 10 * 15 = 150
       * 
       * Divide 10 and 15 by 5
       * 
       * 2 * 3 = 5
       * 
       * Must therefore multiply up by 5 * 5 = 25
       * 
       * 5 * 25 = 150
       */
      long lbl = l * b.l;
      long hbh = h * b.h;
      long lbh = l * b.h;
      long hbl = h * b.l;
      return new SplitLong ( lbh + hbl, lbl + hbh );
    }

    @Override
    public String toString () {
      return Long.toHexString(h)+"|"+Long.toHexString(l);
    }
  }

  // I'll use long and int but this can apply just as easily to long-long and long.
  // The aim is to calculate A*B - C*D without overflow.
  static final long A = Long.MAX_VALUE;
  static final long B = Long.MAX_VALUE - 1;
  static final long C = Long.MAX_VALUE;
  static final long D = Long.MAX_VALUE - 2;

  public static void main(String[] args) throws InterruptedException {
    // First do it with BigIntegers to get what the result should be.
    BigInteger a = BigInteger.valueOf(A);
    BigInteger b = BigInteger.valueOf(B);
    BigInteger c = BigInteger.valueOf(C);
    BigInteger d = BigInteger.valueOf(D);
    BigInteger answer = a.multiply(b).subtract(c.multiply(d));
    System.out.println("A*B - C*D = "+answer+" = "+answer.toString(16));

    // Make one and test its integrity.
    SplitLong sla = new SplitLong(A);
    System.out.println("A="+Long.toHexString(A)+" ("+sla.toString()+") = "+Long.toHexString(sla.longValue()));

    // Start small.
    SplitLong sl10 = new SplitLong(10);
    SplitLong sl15 = new SplitLong(15);
    SplitLong sl150 = sl10.mul(sl15);
    System.out.println("10="+sl10.longValue()+"("+sl10.toString()+") * 15="+sl15.longValue()+"("+sl15.toString()+") = "+sl150.longValue() + " ("+sl150.toString()+")");

    // The real thing.
    SplitLong slb = new SplitLong(B);
    SplitLong slc = new SplitLong(C);
    SplitLong sld = new SplitLong(D);
    System.out.println("B="+Long.toHexString(B)+" ("+slb.toString()+") = "+Long.toHexString(slb.longValue()));
    System.out.println("C="+Long.toHexString(C)+" ("+slc.toString()+") = "+Long.toHexString(slc.longValue()));
    System.out.println("D="+Long.toHexString(D)+" ("+sld.toString()+") = "+Long.toHexString(sld.longValue()));
    SplitLong sanswer = sla.mul(slb).sub(slc.mul(sld));
    System.out.println("A*B - C*D = "+sanswer+" = "+sanswer.longValue());

  }

}

인쇄물:

A*B - C*D = 9223372036854775807 = 7fffffffffffffff
A=7fffffffffffffff (7fffffff|ffffffff) = 7fffffffffffffff
10=10(0|a) * 15=15(0|f) = 150 (0|96)
B=7ffffffffffffffe (7fffffff|fffffffe) = 7ffffffffffffffe
C=7fffffffffffffff (7fffffff|ffffffff) = 7fffffffffffffff
D=7ffffffffffffffd (7fffffff|fffffffd) = 7ffffffffffffffd
A*B - C*D = 7fffffff|ffffffff = 9223372036854775807

작동하는 것처럼 보입니다.

나는 부호 넘침 등을 관찰하는 것과 같은 미묘한 부분을 놓친 것 같지만 본질이 있다고 생각합니다.


1
나는 이것이 @Ofir가 제안한 구현이라고 생각합니다.
OldCurmudgeon

2

완전성을 위해 아무도 언급하지 않았기 때문에 일부 컴파일러 (예 : GCC)는 실제로 오늘날 128 비트 정수를 제공합니다.

따라서 쉬운 해결책은 다음과 같습니다.

(long long)((__int128)A * B - (__int128)C * D)

1

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. 어느 쪽 B/CD/A넘칠 수 있으므로 계산하지 (B/C-D/A)첫째. 최종 결과는 정의에 따라 오버플로되지 않으므로 나머지 곱셈을 안전하게 수행 (B/C-D/A)*A*C하고 필요한 결과를 계산할 수 있습니다.

사용자의 입력이 될 수 있다면 참고, 매우 작은 뿐만 아니라의 B/C또는 D/A오버 플로우 수 있습니다. 가능하면 입력 검사에 따라 더 복잡한 조작이 필요할 수 있습니다.


2
정수 나누기가 정보를 잃어 버렸기 때문에 작동하지 않습니다 (결과의 일부)
Ofir

@Ofir 맞습니다.하지만 케이크를 먹을 수 없으며 그대로 두십시오. 당신은 정밀하게 또는 추가 리소스를 사용하여 지불해야합니다 (답변에서 제안한 것처럼). 내 대답은 수학적인 성격을 지니고 있지만 당신은 컴퓨터 지향적입니다. 상황에 따라 각각 정확할 수 있습니다.
SomeWittyUsername 17시 59 분

2
당신은 정확합니다-나는 그것을 표현해야합니다-수학이 정확하기 때문에 작동하지 않을 것이 아니라 정확한 결과를 제공하지 않습니다. 그러나 질문 제출자에게 관심이있는 경우 (예를 들어 질문의 예에서) 오류는 놀랍게도 실제 응용 프로그램에서 허용 할 수있는 것보다 훨씬 클 수 있습니다. 어쨌든 통찰력있는 대답이었고 그 언어를 사용해서는 안됩니다.
Ofir

@Ofir 나는 당신의 언어가 부적절하다고 생각하지 않습니다. OP는 극도의 자원 제약 조건에서 수행하기 위해 정확도를 잃지 않는 "올바른"계산을 분명히 요청했습니다.
user4815162342

1

선택 K = a big number(예. K = A - sqrt(A))

A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A-C+B-D); // Avoid overflow.

왜?

(A-K)*(B-K) = A*B - K*(A+B) + K^2
(C-K)*(D-K) = C*D - K*(C+D) + K^2

=>
(A-K)*(B-K) - (C-K)*(D-K) = A*B - K*(A+B) + K^2 - {C*D - K*(C+D) + K^2}
(A-K)*(B-K) - (C-K)*(D-K) = A*B - C*D - K*(A+B) + K*(C+D) + K^2 - K^2
(A-K)*(B-K) - (C-K)*(D-K) = A*B - C*D - K*(A+B-C-D)

=>
A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A+B-C-D)

=>
A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A-C+B-D)

참고 A, B, C 및 D, 따라서, 큰 수이기 때문에 그 A-CB-D작은 숫자이다.


실제로 K 를 어떻게 선택 합니까? 게다가 K * (A-C + BD) 가 여전히 오버플로 될 수 있습니다.
ylc

@ylc : K = sqrt (A)를 선택하십시오 A-C+B-D. A, B, C 및 D는 숫자가 크므로 AC는 작습니다.
Amir Saniyan

당신이 선택하면 K = SQRT (A)를 , 다음 (AK) * (BK)가 다시 오버 플로우 수 있습니다.
YLC

@ ylc : OK! 나는 그것을 변경 A - sqrt(A):)
아미르 Saniyan

그러면 K * (A-C + BD) 가 오버플로 될 수 있습니다.
YLC
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.