팔레트 2개에 상자 7개를 나눠 싣는 계산을 정수 변수로 했더니 팔레트당 3이 아니라 4가 나왔습니다. 다른 언어에서는 7 / 2를 정수로 받으면 소수점 아래를 버려 3이 되는 경우가 많아서, ABAP에서도 그럴 거라고 짐작하기 쉽습니다. 이 글을 읽고 나면 ABAP 산술식의 결과가 어떤 타입 규칙으로 정해지는지 설명하고, 버림이 필요한 자리에 / 대신 무엇을 써야 하는지, 인라인 선언에서 소수 자리가 사라지는 경우를 코드에서 찾아낼 수 있습니다.
예시는 정수 변수 lv_boxes(값 7)와 lv_pallets(값 2), 그리고 금액 100.00을 담은 소수 둘째 자리 변수 lv_amount입니다. 변수 이름, 값, 프로그램은 모두 지어낸 것이고 코드는 실제 시스템에서 실행하지 않았습니다. 글에 적은 결과는 ABAP 키워드 문서의 계산 유형과 계산 규칙, 산술 연산자 페이지(7.58판, 7.50판)의 규칙을 그대로 적용해 따진 값이며, 문서 본문은 영어입니다.
계산을 시작하기 전에 계산 유형부터 정한다
ABAP 산술식에는 계산 유형이 붙습니다. 계산 유형은 i, int8, p, f, decfloat34 가운데 하나이고, 식에 관여하는 데이터 타입 중 우선순위가 가장 높은 것으로 정해집니다. decfloat가 하나라도 있으면 decfloat34, 아니면 f나 거듭제곱 연산자 **가 있으면 f, 그다음 p, int8, i 순서입니다.
다른 언어와 크게 다른 점은 여기서 "관여하는 타입"에 대입받는 결과 필드도 들어간다는 것입니다. lv_per = lv_boxes / lv_pallets.에서 lv_per가 TYPE i이면 세 타입이 모두 i라 계산 유형은 i이고, 정수 계산을 합니다. 같은 식을 TYPE p LENGTH 8 DECIMALS 2인 lv_avg에 대입하면 결과 필드 때문에 계산 유형이 p가 되어 3.50이 나옵니다. 오른쪽 식은 한 글자도 바뀌지 않았는데 받는 변수가 결과를 바꿉니다.
SAP 노트
같은 7 / 2, 받는 쪽에 따라 다른 값
가상 예: lv_boxes = 7, lv_pallets = 2 (둘 다 TYPE i)
결과 필드가 TYPE i
lv_per = 7 / 2 → 4- 관여하는 타입이 모두 i → 계산 유형 i
- 3.5는 정수가 아니므로 반올림
- 버림이 아니라 4
결과 필드가 p DECIMALS 2
lv_avg = 7 / 2 → 3.50- 결과 필드도 계산 유형을 정함 → p
- p는 31자리 정밀도로 계산
- 3.5를 소수 둘째 자리로 → 3.50
인라인 선언 DATA(...)
DATA(lv_x) = 7 / 2 → 4- 오른쪽 피연산자만 보고 타입 결정
- i와 i → 선언되는 변수도 i
- 결과 필드 TYPE i와 같은 4
변수 이름과 값은 가상입니다. 결과는 ABAP 키워드 문서(7.58판, 7.50판)의 계산 유형 규칙으로 따진 값이며 실행 결과가 아닙니다.
정수 계산은 나눌 때마다 반올림한다
계산 유형이 i나 int8이면 나눗셈 뒤에 정수가 아닌 중간 결과가 나올 때마다 곧바로 가장 가까운 정수로 반올림합니다. 문서는 이 반올림을 상업적 반올림이라고 부르는데, 정확히 한가운데인 값은 0에서 먼 쪽으로 보냅니다. 그래서 3.5는 4, -3.5는 -4입니다. 문서도 소수점 아래를 버리는 대부분의 언어와 ABAP가 크게 다른 점이라고 따로 짚고, 특히 계산 유형 i의 나눗셈에서 결과가 달라진다고 적어 둡니다.
반올림이 중간 결과마다 일어난다는 점이 더 큰 함정입니다. 모두 i인 7 / 2 * 2는 왼쪽부터 계산하므로 먼저 7 / 2가 4가 되고, 거기에 2를 곱해 8이 됩니다. 곱하기를 먼저 쓴 7 * 2 / 2는 7입니다. 7.58판 문서의 예제처럼 1 / 3 + 1 / 3 + 1 / 3을 정수 계산하면 나눗셈 세 번이 각각 0이 되어 합도 0입니다. 같은 식을 CONV decfloat34( ... )로 감싸 계산 유형을 decfloat34로 바꾸면 중간값 0.333…이 유지되어 합이 0.999…가 됩니다.
SAP 노트
정수 계산은 나눌 때마다 반올림한다
계산 유형 i: 정수가 아닌 중간 결과는 곧바로 반올림(0.5는 0에서 먼 쪽)
7 / 2 * 2
8- 왼쪽부터: 7 / 2 = 3.5 → 4
- 4 * 2 = 8
- 곱하기를 먼저 쓰면 7 * 2 / 2 = 7
1 / 3 + 1 / 3 + 1 / 3
0- 나눗셈 세 번이 각각 0.333… → 0
- 0 + 0 + 0
- 7.58판 문서의 예와 같은 결과
-7 / 2
-4- -3.5는 한가운데 값
- 0에서 먼 쪽으로 반올림
- -3이 아니라 -4
모두 피연산자와 결과 필드가 TYPE i인 경우입니다. 값은 문서 규칙으로 따진 것이며 실행 결과가 아닙니다.
계산 유형이 p이면 내부적으로 31자리 정밀도로 계산하고, 넘치면 63자리로 다시 계산합니다. 남는 소수 자리는 예외 없이 반올림됩니다. 계산이 끝나면 결과가 결과 필드의 타입으로 변환되는데, 소수 둘째 자리 필드라면 그 자리에서 다시 상업적 반올림이 일어납니다. p 결과를 i 필드로 옮길 때도 버림이 아니라 반올림입니다.
인라인 선언은 받는 쪽이 없다
DATA(lv_x) = lv_boxes / lv_pallets.처럼 인라인으로 선언하면 아직 결과 필드의 타입이 없으므로 오른쪽 식만으로 계산 유형을 정하고, 그 유형이 그대로 새 변수의 타입이 됩니다. 정수끼리의 나눗셈이면 lv_x는 i로 선언되고 값은 4입니다.
계산 유형이 p일 때가 더 위험합니다. 이 경우 선언되는 변수는 항상 길이 8, 소수 자리 0인 p입니다. 소수 둘째 자리 금액 lv_amount(100.00)를 3으로 나눠 DATA(lv_share) = lv_amount / 3.으로 받으면 33.333…이 소수 자리 없는 필드에 들어가 33이 됩니다. 문서는 이것이 예상하지 못한 결과나 예외로 이어질 수 있다고 경고하고, 계산 유형 p에서는 인라인 선언을 피하거나 CONV로 타입을 정하라고 권합니다. DATA(lv_exact) = CONV ty_amount( lv_amount / 3 ).처럼 쓰면 생성 연산자의 타입 ty_amount가 대입의 왼쪽처럼 계산 유형에 들어가 33.33이 됩니다.
위 화면의 번호를 코드 점검 순서로 읽으면 이렇습니다. 1번은 모두 i라 3.5가 4로 반올림되는 자리입니다. 2번은 나눗셈의 중간 결과 4에 다시 2를 곱해 8이 되는 자리입니다. 3번은 결과 필드가 p라 같은 식이 3.50이 되는 자리입니다. 4번은 인라인 선언이 소수 자리를 잃는 자리이고, 바로 아래 CONV ty_amount가 그 대안입니다. 5번은 버림 몫과 나머지를 따로 구하는 DIV와 MOD입니다.
버림이 필요하면 DIV, 나머지는 MOD
정수 몫을 버림으로 구하고 싶다면 /가 아니라 DIV를 씁니다. 7 DIV 2는 3이고 7 MOD 2는 1입니다. 다만 DIV는 "나머지가 0 이상인 몫"으로 정의되어 있어 음수가 끼면 0 쪽으로 버리는 몫과 다를 수 있습니다. 문서의 표에 따르면 -7 DIV 3은 -3, -7 MOD 3은 2이고, 7 DIV -3은 -2, 7 MOD -3은 1, -7 DIV -3은 3, -7 MOD -3은 2입니다. 언제나 DIV 결과에 오른쪽 피연산자를 곱하고 MOD를 더하면 왼쪽 피연산자가 됩니다. ABAP SQL의 DIV와 MOD는 부호를 다르게 다루므로, 데이터베이스에서 계산한 값과 ABAP에서 계산한 값을 그대로 비교하면 안 됩니다.
SAP 노트
/, DIV, MOD는 서로 다른 질문에 답한다
DIV × 오른쪽 + MOD = 왼쪽, MOD는 늘 0 이상
양수끼리: 7과 3
2 · 2 · 1- 7 / 3 = 2.333… → i에서 2
- 7 DIV 3 = 2, 7 MOD 3 = 1
- 7 / 2는 4지만 7 DIV 2는 3
음수가 끼면
-3 × 3 + 2 = -7- -7 DIV 3 = -3, -7 MOD 3 = 2
- 7 DIV -3 = -2, 7 MOD -3 = 1
- -7 DIV -3 = 3, -7 MOD -3 = 2
0으로 나누기
0 / 0 → 0- 처리 가능한 예외가 발생
- 단 0 / 0만 예외 없이 0
- 나누는 값이 0인지 먼저 확인
음수 행은 ABAP 키워드 문서 산술 연산자 페이지의 표와 같습니다. SQL의 DIV·MOD는 부호 처리가 다르므로 이 표를 그대로 옮기면 안 됩니다.
0으로 나누는 경우도 따로 봅니다. 0으로 나누면 처리 가능한 예외가 발생합니다. 단 하나의 예외는 나누어지는 수도 0인 경우로, 이때는 예외 없이 결과가 0이 됩니다. 수량이나 금액이 0일 수 있는 나눗셈은 나누는 값을 먼저 확인하세요. 방향을 정해 반올림해야 한다면 round 함수에 CL_ABAP_MATH의 ROUND_... 상수를 줄 수 있습니다. 예를 들어 round( val = CONV decfloat34( lv_boxes / lv_pallets ) dec = 0 mode = cl_abap_math=>round_down )은 3.5를 3으로 내립니다. 인수의 나눗셈이 정수 계산으로 먼저 반올림되지 않도록 CONV decfloat34로 계산 유형을 정해 두었습니다. 모드를 생략하면 상업적 반올림입니다.
릴리스와 오래된 프로그램에서 다른 점
이 글의 규칙, 곧 계산 유형의 우선순위, 결과 필드가 계산 유형에 들어가는 점, 정수와 p 계산의 반올림, 인라인 선언의 p 길이 8 소수 0, DIV와 MOD의 부호 표는 7.50판과 7.58판 문서에 같은 내용으로 있습니다. 7.58판에는 1 / 3 + 1 / 3 + 1 / 3 예제가 추가되어 있고, 인라인 선언으로 DATA(...)와 함께 FINAL(...)도 다룹니다. 7.50판의 같은 절은 DATA(...)만 언급합니다.
아주 오래된 프로그램이라면 프로그램 속성 "고정 소수점 연산(Fixed point arithmetic)"도 확인합니다. 새 프로그램에는 기본으로 켜져 있고, 끄는 기능은 호환성 때문에만 남아 있는 폐기된 기능입니다. 이 속성이 꺼진 프로그램에서는 p 타입의 소수점 위치가 화면 출력, 문자 필드로의 대입, WRITE 서식에서만 지켜지고 계산에서는 무시됩니다. 같은 식이 다른 프로그램에서 다른 값을 낸다면 이 속성부터 봅니다.
코드에서 확인할 것
나눗셈이 있는 줄에서는 세 가지를 봅니다. 결과 필드까지 포함해 모든 피연산자가 정수라 정수 계산이 되는가. 그 나눗셈의 중간 결과가 뒤의 곱셈이나 덧셈으로 이어지는가. 결과를 인라인 선언으로 받으면서 계산 유형이 p가 되는가. 버림 몫이 필요하면 DIV, 소수 결과가 필요하면 결과 필드나 CONV로 타입을 분명히 정합니다.
하나만 기억한다면 이것입니다. ABAP에서 /는 버리지 않고 반올림하며, 그 반올림의 기준이 되는 타입에는 왼쪽의 결과 필드도 들어갑니다.
이 기록에 대화를 더해 주세요.
궁금한 점, 다른 접근, 직접 해 본 결과를 나눠 주세요.
로그인 상태 확인 중…
댓글을 불러오는 중…