모든 이야기

정수 나눗셈 경계값: 최소 음수를 −1로 나누면 어떤 답을 낼까

−7을 3으로 나누면 몫은 −2일까, −3일까. 계산기에 따라 설정과 표현이 달라 보일 수 있지만, 하드웨어 명령은 어느 쪽인지 정확히 정해야 한다. 정수 나눗셈 경계값을 검증할 때 필요한 것은 나누는 알고리즘만이 아니라 반올림 방향과 표현 범위 밖의 입력에 대한 관측 계약이다.

보통의 양수 입력을 수천 번 검사해도 0으로 나누기와 가장 작은 음수는 거의 나오지 않는다. 그런데 이런 소수의 입력이 결과 선택, 부호 보정과 완료 제어의 별도 경로를 사용할 수 있다.

몫을 어디로 반올림하는가

RISC-V의 signed DIV는 0을 향해 몫을 잘라낸다. −7÷3의 정확한 몫 −2.333…에서 정수 몫은 −2다. 대응하는 나머지는 −1이어서 −7 = 3×(−2)+(−1)이 된다. REM의 0이 아닌 결과 부호는 dividend, 즉 나누어지는 수의 부호와 같다.

바닥 함수처럼 더 작은 정수로 내리면 몫 −3, 나머지 2가 된다. 두 조합 모두 원래 수를 복원하지만 다른 규칙이다. 따라서 dividend = divisor×quotient+remainder만 검사하면 signed 나눗셈의 반올림 계약을 모두 검증한 것이 아니다.

RISC-V ISA 원문의 Division Operations는 0을 향한 몫과 나머지 부호, 특별한 입력의 결과를 명시한다. 소프트웨어 reference model도 이 규칙을 그대로 표현해야 한다. 사용한 언어의 기본 나눗셈·나머지 연산이 같다는 가정은 별도로 확인해야 한다.

0으로 나누는 명령은 어떤 결과를 내나

RISC-V 정수 나눗셈에서 divisor가 0이면 quotient의 모든 비트가 1이고, remainder는 dividend다. RV32의 unsigned 몫은 0xFFFFFFFF, signed로 읽으면 −1이다. 표는 RV32 설명이며 임의의 수학적 나눗셈 결과가 아니다.

입력 사건몫나머지
signed −7÷3−2−1
unsigned 7÷00xFFFFFFFF7
signed −7÷0−1−7
signed 최소값÷−1signed 최소값0

표의 divide-by-zero 결과는 이 ISA가 정의한 값이다. 다른 명령 집합이나 언어 환경에서도 동일하게 동작한다는 뜻은 아니다. 이 명령의 정의와 프로그램이 요구하는 오류 처리는 구분해야 한다. 소프트웨어가 0으로 나누기를 오류로 다루려면 자신의 실행 환경에 맞춰 조건을 검사할 수 있다.

하드웨어 검증에서는 특별한 값을 내는 것뿐 아니라 정상적인 완료 응답을 내는지도 확인한다. 결과 경로가 일찍 선택되는 구조라면 바쁜 상태가 풀리는 시점과 출력 유지 조건도 별도로 실행된다.

가장 작은 음수에는 같은 폭의 양수가 없다

32비트 2의 보수 signed 범위는 −2147483648부터 2147483647까지다. 최소값을 −1로 나누면 수학적 결과는 +2147483648이지만 같은 signed 폭으로 표현할 수 없다. 이 경우 RISC-V는 몫에 원래 최소값을, 나머지에 0을 반환한다.

이 overflow에서는 일반적인 정수 식을 무한 정밀도 숫자로 검사하면 맞지 않는다. −1에 반환 몫 −2147483648을 곱하면 +2147483648이므로 원래 dividend와 다르다. 특별한 결과 계약을 따로 검사해야 하는 이유다.

부호를 먼저 제거하고 unsigned divider로 크기를 계산하는 구현도 주의해야 한다. 최소값의 크기 2147483648은 32비트 unsigned로는 표현할 수 있지만 32비트 signed 양수로는 표현할 수 없다. 중간 크기 값을 어떤 타입으로 보관하는지와 마지막 부호 보정의 폭을 명확히 해야 한다.

unsigned division에는 같은 signed overflow 사건이 없다. 범위를 넘는 수학적 몫을 만들 수 없기 때문이다. signed 여부를 하나의 공통 분기처럼 처리하는 reference model은 이런 차이를 놓칠 수 있다.

경계값은 알고리즘의 제어 경로도 검사한다

Ibex의 공개 divider 설명은 초기 divide-by-zero 검사와 절댓값 계산, long division, 결과 경로를 구분한다. 이 구조가 모든 divider의 정답이라는 뜻은 아니지만, 특별한 입력이 별도의 상태 전이를 거칠 수 있다는 사례다.

시험 입력을 만들 때는 0, ±1, 최대값, 최소값을 단순히 나열하는 데서 끝내지 않는다. divisor가 0인 경우와 아닌 경우, 최소값÷−1과 최소값÷1, 나누어떨어지는 음수와 나머지가 남는 음수를 대조한다. 몫 0이 나오는 작은 절댓값과 divisor의 부호 변화도 부호 보정을 검사하는 데 도움이 된다.

출력이 소비되지 않은 동안 새로운 요청이 들어오거나 reset되는 경우에도 해당 명령의 결과와 상태를 유지·취소하는 계약이 필요하다. 수학 결과가 맞았다는 사실만으로 divider 인터페이스가 맞는 것은 아니다. 조기 완료를 지원하면 일반 입력과 경계 입력의 완료 시간 차이를 받아들이는 제어도 확인한다.

정수 나눗셈 경계값의 중요성은 특별한 숫자가 많아서가 아니다. 소수의 입력이 평소와 다른 의미와 구현 경로를 사용하기 때문이다. 검증 모델은 반올림, 몫·나머지 관계, 0과 overflow의 정의를 각각 보존해야 한다. 그래야 수학적으로 그럴듯한 답과 해당 하드웨어 명령의 올바른 답을 구분할 수 있다.

정수 나눗셈 경계값: 최소 음수를 −1로 나누면 어떤 답을 낼까 · ariser