정수 랩 어라운드 "실행 취소"


20

몇 년 전에 흥미로운 이론적 문제가 발생했습니다. 나는 해결책을 찾지 못했고, 잠을 자도 계속 나를 괴롭 힙니다.

x라는 정수로 숫자를 보유하는 (C #) 응용 프로그램이 있다고 가정합니다. (x 값은 고정되어 있지 않습니다). 프로그램이 실행되면 x에 33을 곱한 다음 파일에 씁니다.

기본 소스 코드는 다음과 같습니다.

int x = getSomeInt();
x = x * 33;
file.WriteLine(x); // Writes x to the file in decimal format

몇 년 후, X의 원래 값이 필요하다는 것을 알게되었습니다. 일부 계산은 간단합니다. 파일의 숫자를 33으로 나누기 만하면됩니다. 그러나 다른 경우에는 X가 곱하기 때문에 정수 오버플로가 발생했습니다. 문서 에 따르면 C #은 숫자가보다 작을 때까지 상위 비트를 자릅니다 int.MaxValue. 이 경우 다음 중 하나가 가능합니까?

  1. X 자체를 복구하거나
  2. X에 가능한 값 목록을 복구 하시겠습니까?

단순한 추가 작업 경우 (필수적으로 X에 10을 더하고 랩하면 10을 빼고 X로 다시 감을 수 있음) 중 하나 또는 둘 다 가능해야합니다 (내 논리에 결함이있을 수는 있지만) )와 곱셈은 단순히 반복 더하기입니다. 또한 X가 모든 경우에 동일한 값을 곱한다는 사실을 돕습니다 (믿습니다).

이것은 몇 년 동안 이상한 순간에 내 두개골 주위에서 춤을 추었습니다. 그것은 나에게 일어날 것입니다, 나는 그것을 생각하기 위해 약간의 시간을 할애하고, 그리고 나는 그것을 몇 달 동안 잊어 버릴 것입니다. 이 문제를 쫓는 데 지쳤습니다! 누구나 통찰력을 제공 할 수 있습니까?

(측면 참고 :이 태그를 지정하는 방법을 잘 모르겠습니다. 제안을 환영합니다.)

편집 : X에 가능한 값 목록을 얻을 수 있다면 원래 값으로 좁히는 데 도움이 될 수있는 다른 테스트가 있음을 분명히하겠습니다.



1
@ rwong : 귀하의 의견은 유일한 정답입니다.
케빈 클라인

예, 그리고 Euler의 방법은 인수 분해 m가 단지 2 ^ 32 또는 2 ^ 64이므로 a모듈로 의 지수화 m는 간단하기 때문에 특히 효과적 입니다 (여기서 오버 플로우는 무시)
MSalters

1
특정 문제는 실제로 Rational Reconstruction
MSalters

1
@MSalters : 아니, 그럼 어디 있는지 r*s^-1 mod m당신이 모두를 찾을 필요 r하고 s. 여기, 우리는 r*s mod m모든 것을 알고 r있습니다.
user2357112는 Monica

답변:


50

1041204193을 곱하십시오.

곱셈의 결과가 int에 맞지 않으면 정확한 결과를 얻지 못하지만 정확한 결과 modulo 2 ** 32 와 동등한 숫자를 얻습니다 . 그것은 당신이 곱한 숫자 가 2 ** 32 에 코 프라임 (홀수 여야 함)이라면, 곱하기 역수 를 곱하여 숫자를 되 찾을 수 있다는 것을 의미합니다 . Wolfram Alpha 또는 확장 된 유클리드 알고리즘 은 33의 곱셈 역 모듈로 2 ** 32가 1041204193임을 알 수 있습니다. 따라서 1041204193을 곱하면 원래 x가 돌아옵니다.

예를 들어 33 대신 60을 사용하면 원래 숫자를 복구 할 수 없지만 몇 가지 가능성으로 좁힐 수 있습니다. 60을 4 * 15로, 15 mod 2 ** 32의 역수를 계산하고, 그 수를 곱하면 원래 숫자의 4 배를 복구 할 수 있으며,이 숫자의 상위 2 비트 만 무차별 대입으로 남겨 둘 수 있습니다. Wolfram Alpha 는 int에 맞지 않는 inverse에 4008636143을 제공하지만 괜찮습니다. 우리는 4008636143 mod 2 ** 32에 해당하는 숫자를 찾거나 컴파일러가 우리를 위해 그렇게하도록 int로 강제합니다. 결과는 15 mod 2 ** 32의 역수입니다. ( -286331153이됩니다. )


5
오 소년. 내 컴퓨터가지도를 작성하는 모든 작업은 이미 Euclid가 수행했습니다.
v010dya

21
첫 문장에서 사실을 좋아합니다. "아, 물론 1041204193입니다. 기억하지 않습니까?" :-P
Doorknob

2
x * 33이 넘치지 않는 숫자와 그렇지 않은 숫자와 같은 몇 가지 숫자에 대한이 작업의 예를 보여주는 것이 도움이 될 것입니다.
Rob Watts

2
마음을 날려. 와우.
Michael Gazonda

4
33 모듈로 $ 2 ^ {32} $의 역수를 구하기 위해 Euclid 나 WolframAlpha (확실히!)가 필요하지 않습니다. $ x = 32 = 2 ^ 5 $는 ($ 7 $ 순서의) 모듈로 $ 2 ^ 32 $와 동일하지 않기 때문에, 당신은 기하 계열 항등 $ (1 + x) ^ {-1} = 1-x + x ^를 적용 할 수 있습니다 2-x ^ 3 + \ cdots + x ^ 6 $ (이후 계열이 끊어짐)를 찾아 숫자 $ 33 ^ {-1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $ $ 111110000011111000001111100001_2 = 1041204193_ {10} $
Marc van Leeuwen

6

이것은 아마도 수학 (sic) SE에 대한 질문으로 더 적합 할 것입니다. 가장 왼쪽에있는 비트를 제거하는 것이 동일하기 때문에 기본적으로 모듈 식 산술을 다루고 있습니다.

나는 수학 (sic) SE에있는 사람들만큼 수학에 능숙하지 않지만 대답하려고합니다.

우리가 가진 것은 숫자에 33 (3 * 11)을 곱하고 mod와의 공통 분모가 1이라는 것입니다. 그것은 컴퓨터의 비트가 2의 거듭 제곱이므로 mod는 2의 힘.

모든 이전 값에 대해 다음 값을 계산하는 테이블을 구성 할 수 있습니다. 그리고 질문은 다음 숫자가 하나의 이전 숫자에만 해당됩니다.

그것이 33이 아니라 소수 또는 소수의 거듭 제곱이라면, 나는 대답이 그렇다고 믿지만,이 경우에는… Math.SE에 문의하십시오!

프로그래밍 테스트

이것은 C #을 모르기 때문에 C ++에 있지만 개념은 여전히 ​​유효합니다. 이것은 당신이 할 수 있음을 보여줍니다 :

#include <iostream>
#include <map>

int main(void)
{
    unsigned short count = 0;
    unsigned short x = 0;
    std::map<unsigned short, unsigned short> nextprev;

    nextprev[0] = 0;
    while(++x) nextprev[x] = 0;

    unsigned short nextX;
    while(++x)
    {
            nextX = x*33;
            if(nextprev[nextX])
            {
                    std::cout << nextprev[nextX] << "*33==" << nextX << " && " << x << "*33==" << nextX << std::endl;
                    ++count;
            }
            else
            {
                    nextprev[nextX] = x;
                    //std::cout << x << "*33==" << nextX << std::endl;
            }
    }

    std::cout << count << " collisions found" << std::endl;

    return 0;
}

이러한 맵을 채운 후에는 다음 맵을 알고 있으면 항상 이전 X를 얻을 수 있습니다. 항상 하나의 값만 있습니다.


음이 아닌 데이터 유형으로 작업하는 것이 더 쉬운 이유는 무엇입니까? 서명 및 서명되지 않은 서명은 컴퓨터에서 동일한 방식으로 처리되지 않고 사람의 출력 형식 만 다릅니다?
Xcelled

글쎄,이 숫자들에 대해 생각하기가 더 쉽다.
v010dya

충분한 xD 인적 요소 ~
Xcelled

더 명확하게하기 위해 음이 아닌 것에 대한 진술을 제거했습니다.
v010dya

1
@ Xcelled194 : 서명되지 않은 데이터 유형은 일반적인 모듈 식 산술 규칙을 따릅니다. 부호있는 유형은 그렇지 않습니다. 특히 maxval+1부호없는 유형의 경우에만 0입니다.
MSalters

2

그것을 얻는 한 가지 방법은 무차별적인 힘을 사용하는 것입니다. 죄송하지만 C #을 모르지만 다음은 솔루션을 설명하기 위해 c와 유사한 의사 코드입니다.

for (x=0; x<=INT_MAX; x++) {
    if (x*33 == test_value) {
        printf("%d\n", x);
    }
}

기술적으로 필요한 것은 언어에서 임의의 정밀 정수 (bigint)를 사용하지 않는 한 x*33%(INT_MAX+1) == test_value정수 오버플로가 자동으로 %작업을 수행합니다 .

이것이 당신에게주는 것은 원래의 숫자 일 수있는 일련의 숫자입니다. 인쇄 된 첫 번째 숫자는 한 번의 오버플로를 발생시키는 숫자입니다. 두 번째 숫자는 두 번의 오버플로를 발생시키는 숫자입니다. 등등..

따라서 데이터를 더 잘 알고 있다면 더 나은 추측을 할 수 있습니다. 예를 들어, 일반적인 시계 수학 (12 시마다 오버플로)은 대부분의 사람들이 오늘날 일어난 일에 관심이 있기 때문에 첫 번째 숫자를 더 가능성이 높습니다.


C #은 기본 유형의 C와 같이 작동합니다. 즉 int, 4 바이트 부호있는 정수입니다. 따라서 입력이 많을 경우 무차별 강제가 가장 좋은 방법은 아니지만 대답은 여전히 ​​좋습니다! :)
Xcelled

예, 여기에서 modulo 대수 규칙을 사용하여 종이로 작업 해 보았습니다. math.stackexchange.com/questions/346271/… . 그러나 나는 그것을 알아 내려고 노력하고 결국 무력 솔루션으로 끝났다 :)
slebetman

재미있는 기사이지만 클릭하기 위해서는 좀 더 깊이 연구해야 할 것 같습니다.
Xcelled

@slebetman 내 코드를 보라. 33을 곱하면 하나의
답만

2
수정 : C int는 랩을 보장하지 않습니다 (컴파일러 문서 참조). 서명되지 않은 유형의 경우에도 마찬가지입니다.
Thomas Eding

1

SMT 솔버 Z3에서 수식에 대한 만족스러운 할당을 요청할 수 있습니다. x * 33 = valueFromFile . 그것은 당신을 위해 그 방정식을 뒤집고 가능한 모든 값을 줄 것입니다 x. Z3은 곱셈을 포함하여 정확한 비트 벡터 산술을 지원합니다.

    public static void InvertMultiplication()
    {
        int multiplicationResult = new Random().Next();
        int knownFactor = 33;

        using (var context = new Context(new Dictionary<string, string>() { { "MODEL", "true" } }))
        {
            uint bitvectorSize = 32;
            var xExpr = context.MkBVConst("x", bitvectorSize);
            var yExpr = context.MkBVConst("y", bitvectorSize);
            var mulExpr = context.MkBVMul(xExpr, yExpr);
            var eqResultExpr = context.MkEq(mulExpr, context.MkBV(multiplicationResult, bitvectorSize));
            var eqXExpr = context.MkEq(xExpr, context.MkBV(knownFactor, bitvectorSize));

            var solver = context.MkSimpleSolver();
            solver.Assert(eqResultExpr);
            solver.Assert(eqXExpr);

            var status = solver.Check();
            Console.WriteLine(status);
            if (status == Status.SATISFIABLE)
            {
                Console.WriteLine(solver.Model);
                Console.WriteLine("{0} * {1} = {2}", solver.Model.Eval(xExpr), solver.Model.Eval(yExpr), solver.Model.Eval(mulExpr));
            }
        }
    }

출력은 다음과 같습니다.

SATISFIABLE
(define-fun y () (_ BitVec 32)
  #xa33fec22)
(define-fun x () (_ BitVec 32)
  #x00000021)
33 * 2738875426 = 188575842

0

이 결과를 취소하면 0이 아닌 유한 한 양의 숫자가 나타납니다 (일반적으로 무한하지만 intℤ의 유한 하위 집합입니다). 이것이 허용되는 경우 숫자를 생성하십시오 (다른 답변 참조).

그렇지 않으면 변수 히스토리의 히스토리 목록 (유한 또는 무한 길이)을 유지해야합니다.


0

언제나 그렇듯이 과학자의 솔루션과 엔지니어의 솔루션이 있습니다.

위에서 당신은 항상 효과가 있지만“곱하기의 역수”를 계산해야하는 과학자로부터 아주 좋은 해결책을 찾을 수 있습니다.

다음은 엔지니어가 제공하는 빠른 솔루션으로 모든 가능한 정수를 시도하지는 않습니다.

val multiplier = 33 //used with 0x23456789
val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL

val overflowBit = 0x100000000L
for(test <- 0 until multiplier) {
  if((problemAsLong + overflowBit * test) % multiplier == 0) {
    val originalLong = (problemAsLong + overflowBit * test) / multiplier
    val original = originalLong.toInt
    println(s"$original (test = $test)")
  }
}

아이디어는 무엇입니까?

  1. 오버플로가 발생했기 때문에 더 큰 유형을 사용하여 복구합니다 ( Int -> Long).
  2. 오버플로로 인해 약간의 비트가 손실되었을 수 있습니다.
  3. 오버 플로우는 Int.MaxValue * multiplier

전체 실행 코드는 http://ideone.com/zVMbGV에 있습니다.

세부:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    여기서 우리는 저장된 숫자를 Long으로 변환하지만 Int와 Long이 서명되었으므로 올바르게해야합니다.
    따라서 비트 단위의 Int 비트 단위 AND를 사용하여 숫자를 제한합니다.
  • val overflowBit = 0x100000000L
    이 비트 또는 곱셈은 초기 곱셈으로 손실 될 수 있습니다.
    Int 범위를 벗어난 첫 번째 비트입니다.
  • for(test <- 0 until multiplier)
    3rd Idea에 따르면 최대 오버플로는 승수에 의해 제한되므로 실제로 필요한 것 이상을 시도하지 마십시오.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    오버플로 가능성을 추가하여 해결책을 찾았는지 확인하십시오.
  • val original = originalLong.toInt
    원래 문제는 Int 범위에 있었으므로 돌아가겠습니다. 그렇지 않으면 음수 인 숫자를 잘못 복구 할 수 있습니다.
  • println(s"$original (test = $test)")
    가능한 다른 솔루션이있을 수 있기 때문에 첫 번째 솔루션을 중단하지 마십시오.

추신 : 3rd Idea는 엄격하게 정확하지는 않지만 이해할 수 있도록 남겨 두었습니다.
Int.MaxValue입니다 만 0x7FFFFFFF, 최대 오버 플로우는 0xFFFFFFFF * multiplier입니다.
따라서 올바른 텍스트는 "오버플로가 넘지 않았습니다 -1 * multiplier"입니다.
이것은 맞지만 모두가 그것을 이해하지는 않습니다.

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