Technical Article

Delphi의 엔지니어링 함수: 진법 변환, 복소수

Excel의 엔지니어링 함수군은 참조 가이드에서 가장 단순한 부분처럼 보입니다. DEC2BIN은 숫자를 이진 문자열로 변환하고, HEX2DEC는 반대 연산을 수행하며, IMSUM은 두 복소수를 더합니다. 단순히 형식을 조작하는 것처럼 보이지만 실제로는 그렇지 않습니다. 이 함수들의 이면에는 컴퓨터 구조 수업 이후 다루지 않았을 법한 10비트 2의 보수 인코딩, 문자열 내부에서 관리되는 복소수 데이터 형식, 검증 전에 시프트 연산을 실행하면 자동으로 오버플로를 일으키는 비트 연산자 등이 숨어 있습니다. Excel과 정확하게 일치하는 호환성을 제공하려는 스프레드시트 엔진은 이러한 예외 사항들을 완벽히 구현해야 합니다.

이 함수들은 크게 세 그룹으로 분류되며, 각 그룹은 고유한 예외 상황을 포함합니다. 진법 변환은 음수 표현과 진법별 임계값 처리가 핵심입니다. 복소수 연산은 문자열의 분석(parsing) 및 포맷팅(formatting)이 중요합니다. 비트 연산은 Int64의 범위 내에 연산을 안착시키는 기술입니다. 이 글에서는 HotXLS가 실제 작성되는 워크시트 호출을 처리하기 위해 각 그룹을 어떻게 구현하는지 살펴봅니다.

진법 변환 및 10비트 2의 보수

양수를 변환하는 기본 방향은 예측 가능합니다. DEC2BIN(9)"1001"을 반환하며, 선택적인 두 번째 인수를 통해 결과의 왼쪽에 지정 너비만큼 패딩을 추가할 수 있습니다. 문제는 음수가 입력될 때 발생합니다. Excel은 음수 기호(-)를 작성하지 않는 대신 대상 진법에서 10자리 2의 보수 문자열로 값을 인코딩합니다. 이것이 바로 DEC2BIN(-5,10)가 부호 기호 대신 "1111111011"을 반환하는 이유입니다. 입력 값이 음수이면 출력 형식이 10자리로 고정되므로 자릿수 인수(places)는 무시됩니다.

10자리는 고정된 한계이며, 이 한계가 각 진법에서 표현할 수 있는 값의 범위를 결정합니다. 이진수에서 음수로 간주되는 경계값은 512이고 모듈러스(modulus) 값은 1024이므로, 이진 문자열은 정확히 10글자이고 해당 수치 값이 512 이상일 때만 음수로 처리됩니다. 이 개념은 다른 진법에도 동일하게 확장됩니다. 8진수는 2^29의 중간 경계값과 2^30의 모듈러스 값을 사용합니다. 16진수는 2^39와 2^40을 사용합니다. HotXLS 리더는 정확하게 이 규칙을 적용합니다: 자릿수를 누적하다가 문자열이 10자이고 누적 값이 중간 경계값 이상일 때만 전체 모듈러스를 차감하여 부호 있는 음수 값을 복원합니다. 9자리 이하의 문자열은 수치 크기에 관계없이 항상 양수로 취급됩니다.

인코더(encoder)는 이와 대칭 관계를 이룹니다. 양수 값은 자리별로 변환되어 지정 너비에 맞게 0으로 패딩을 채우며, 지정 진법의 양수 최댓값을 초과하거나 너비 설정이 너무 작아 값을 표현할 수 없는 경우 오류 처리됩니다. 음수 값은 먼저 전체 모듈러스를 더해 정상 범위로 보정한 다음, 항상 10자리가 되는 값으로 변환하고 너비 자릿수를 채우기 위해 접두사 0과 함께 출력됩니다. 진법별로 정의된 대칭적인 상한 및 하한 범위 검사를 통해 DEC2BIN, DEC2OCT, DEC2HEX 함수가 일관되게 작동합니다.

That leaves the cross-base conversions, the ones such as HEX2BIN and OCT2HEX that change base without passing through decimal in the function name. The implementation does not carry a separate routine for every ordered pair. It parses the input string into a signed decimal value using the source base, then formats that decimal value into the destination base. Decimal is the pivot. One parse routine and one format routine, composed, cover every combination, and because both halves share the same ten-digit signed convention, a negative value survives the trip with its sign intact.

복소수는 문자열이므로 주요 작업은 파싱

Excel에는 복소수 전용 데이터 타입이 없습니다. 복소수 값은 "a+bi" 형식의 문자열로 취급되며, IM 계열의 모든 함수는 이 문자열을 입력받아 문자열로 결과를 반환합니다. COMPLEX는 실수부와 허수부를 받아 문자열을 작성합니다. IMSUM, IMSUB, IMPRODUCT, IMDIV는 인수를 분석하여 실수부와 허수부 연산을 수행한 다음, 연산 결과를 다시 문자열로 가공합니다. 대수적인 연산 자체는 기초 수학에 불과합니다. 어려운 부분은 텍스트 데이터를 두 개의 부동 소수점 수로 신뢰할 수 있게 변환하는 작업이며, 이 부분에서 내부 파서가 중요한 역할을 수행합니다.

파서 구현 시 놓치기 쉬운 두 가지 세부 사항이 있습니다. 첫째, 접미사만 단독으로 나타나는 허수 단위 처리입니다. 문자열 "i"는 오류가 아닌 1곱하기 i를 의미하므로, 접미사 앞의 계수가 비어 있거나 단독 플러스 기호인 경우 파서는 1로 처리해야 하고 단독 마이너스인 경우 -1로 읽어야 합니다. 이 처리가 빠지면 IMSUM("i","i") 연산이 2i가 되지 못합니다. 둘째, 실수부와 허수부를 구분하는 부호 기호와 지수 표기법의 충돌 문제입니다. 파서는 플러스 또는 마이너스를 스캔하여 구분 기호를 찾지만, "1.5E-3"과 같은 수치는 지수부의 부호 마이너스를 포함합니다. 따라서 스캐너는 부호 기호 바로 앞 문자가 e 또는 E인 경우 해당 부호를 구분 기호로 취급해서는 안 됩니다. 이 조건이 없다면 실수부가 지수 부호 지점에서 절단되어 올바른 수치 형식임에도 파싱 실패 오류가 발생하게 됩니다.

허수 단위 접미사는 정규화되지 않고 원본 글자 그대로 보존됩니다. Excel은 ij를 모두 허용하며, HotXLS는 입력에서 사용한 문자를 기억하여 포맷팅 시 동일한 문자를 사용합니다. 포맷팅 과정에는 일반적인 단축 표기법이 적용됩니다: 허수부 계수가 1인 경우 접미사만 출력하고, -1인 경우 -i로 출력하며, 허수부가 0인 경우 실수부만 남기고, 실수부가 0인 경우 앞의 0+ 표기는 생략합니다.

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Engineering');
    // Negative input: a ten-bit two's complement, places argument ignored.
    Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
    // Complex multiply on two "a+bi" strings.
    Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
  finally
    Book.Free;
  end;
end;

IMSQRT, IMEXP, IMLN, IMPOWER 등을 포함한 복소수의 초월함수들은 직교좌표계에서 직접 작동하지 않습니다. 이들은 입력값을 극좌표(polar) 형식으로 변환하여 모듈러스와 편각(argument) 연산을 수행한 후 다시 직교좌표 형식으로 복원합니다. 제곱근은 편각을 반으로 나누고 모듈러스의 제곱근을 계산합니다. 거듭제곱은 편각에 승수를 곱하고 모듈러스를 거듭제곱합니다. 직교좌표 형식으로 각 수식을 직접 계산하면 코드가 늘어날 뿐만 아니라 분기 절단면(branch cut) 부근에서 수치적 안정성이 저하됩니다.

비트 연산자 및 가장 먼저 확인해야 하는 오버플로

Excel 2013에 BITAND, BITOR, BITXOR, BITLSHIFT, BITRSHIFT가 추가되었습니다. 피연산자는 음수가 아닌 정수이면서 2^48 - 1 이하여야 하며, 소수부가 있거나 음수인 인수는 오류 처리됩니다. 이 최댓값은 일반적인 플래그 세트를 다루기에 충분하며 double 정밀도로 표현 가능한 안전 범위 내에 위치합니다. Excel이 모든 숫자 인수를 내부적으로 부동 소수점 값으로 전달하기 때문에 이러한 제약 조건은 타당합니다.

시프트 함수에는 주의해야 할 연산 순서 규칙이 있습니다. 왼쪽 시프트는 입력보다 훨씬 큰 값을 생성할 수 있으므로, shl 연산을 먼저 실행한 후 결과를 검사하면 이미 Int64 범위를 초과하여 오버플로가 발생한 상태이므로 유효성 검사가 무색해집니다. 따라서 검사는 시프트 전에 선행되어야 합니다. HotXLS는 피연산자 값을 시프트 크기만큼 오른쪽으로 이동시킨 한계값과 먼저 비교한 후, 조건에 부합할 때만 실제 왼쪽 시프트를 수행합니다. 53비트를 초과하는 시프트는 거부되며, 음수 시프트는 방향을 역전시키므로 음수 값으로 지정된 BITLSHIFT는 오른쪽 시프트로 동작합니다. 이 원칙은 이 함수뿐만 아니라 오버플로를 방지하기 위한 방어 코드가 결과값이 아닌 항상 입력값 단계에서 사전 실행되어야 함을 보여줍니다.

// Bitwise calls evaluate the same way through Calculate.
Sheet.Cells[3, 1].Value := Sheet.Calculate('=BITAND(13,11)');    // 9
Sheet.Cells[4, 1].Value := Sheet.Calculate('=BITLSHIFT(5,2)');   // 20
Sheet.Cells[5, 1].Value := Sheet.Calculate('=BITRSHIFT(40,3)');  // 5

Excel 향후 함수 및 _xlfn 이름 접두사

비트 연산자를 비롯해 2007년 이후 추가된 대다수의 함수명은 연산 내용이 아닌 Excel이 이를 파일에 저장하는 네이밍 규약과 밀접하게 연관됩니다. 초기 바이너리 워크시트 포맷은 각 빌트인 함수에 고정된 테이블 인덱스 번호를 할당했습니다. 이 테이블이 확정된 후 새로 설계된 함수들은 인덱스 번호가 없습니다. 따라서 파일에 저장할 때 최신 Excel이 이를 인식할 수 있도록 이름 앞에 _xlfn. 접두사를 붙여 저장하므로, 사용자가 BITAND로 입력하더라도 디스크에는 _xlfn.BITAND로 기록됩니다.

주의할 점은 이 규칙이 완전 일률적이지 않다는 것입니다. 일부 신형 함수는 테이블 인덱스가 부여되어 접두사 없이 기록되며, 일부 구형 내부 함수는 연식에 관계없이 접두사 없이 처리되기도 합니다. HotXLS는 접두사가 필요한 함수명의 명시적 화이트리스트를 관리하여 파일 저장 시에 추가하고 파일을 읽을 때 제거하므로, 개발자가 연동하는 수식 텍스트는 항상 사용자가 보는 깔끔한 이름으로 유지됩니다. 개발자가 =BITLSHIFT(5,2)를 입력하면 파일에는 _xlfn.BITLSHIFT로 저장되지만 연산 결과는 정상적으로 20을 반환합니다. 이 접두사는 저장 시점의 메타 데이터 정보이므로 코드 수식 수준에 노출되지 않는 것이 바람직합니다.

워크시트에 연동하여 사용하기

이를 위한 공개 API 구조는 매우 간단합니다. TXLSXWorkbook을 생성하고 워크시트를 추가한 후, Cells[Row, Col].Formula를 통해 셀에 수식을 작성하고 다시 계산(recalculate)을 지시하거나, 워크시트의 Calculate 메서드를 직접 활용하여 해당 시트 기준으로 수식을 계산하고 Variant 값을 반환받을 수 있습니다. 위 예제들은 다른 시트 상태 정보 없이 단일 호출 결과를 명확히 파악할 수 있도록 Calculate 메서드를 사용했지만, 이들은 통합 문서의 재계산 시 실제 셀 수식 내에서도 동일하게 작동합니다.

호출 방식보다 세부 연산 처리 과정을 이해하는 것이 훨씬 중요합니다. 이진 문자열은 10자리이면서 해당 진법의 임계값을 초과할 때만 음수로 해석됩니다. 복소수는 텍스트이며 계수가 비어 있는 허수 단위는 1로 처리되고 파서는 지수부의 e 표기를 건너뜁니다. 왼쪽 시프트는 연산 수행 전에 조건 검사를 거칩니다. 이 네 가지 원칙을 명확하게 처리하면 엔지니어링 수식 연산 시 부호 오류로 인한 변수가 사라집니다.

스프레드시트 엔진에 자체 맞춤형 비즈니스 수식 로직을 추가하는 방법은 사용자 정의 함수로 수식 엔진 확장하기 가이드를 참조하십시오. 셀 주소가 아닌 명명된 범위를 통해 여러 시트를 가로질러 수식을 참조하는 방법은 정의된 이름과 시트 간 참조 수식 연습 가이드에서 확인할 수 있습니다. 여기에 설명된 엔지니어링 함수들은 파일 로드, 저장, 연산 API와 함께 Delphi 및 C++Builder용 HotXLS 스프레드시트 컴포넌트에 내장되어 제공됩니다.