고정소수점 타입
고정소수점 타입 (Fixed-point types)
이 장에서는 고정소수점 타입을 다뤄요. 이 타입은 두 범주로 분류할 수 있어요: 십진 고정소수점 타입(decimal fixed-point types) 과 일반(이진) 고정소수점 타입(ordinary binary fixed-point types). 각 범주를 간략히 살펴본 뒤, 고정소수점과 부동소수점 타입의 몇 가지 차이를 논의할게요.
출처: 고정소수점 타입 문서
본문
십진 고정소수점 타입 (Decimal fixed-point types)
이미 부동소수점 타입을 지정하는 방법을 봤어요. 하지만 어떤 애플리케이션에서는 부동소수점이 적절하지 않을 수 있어요. 예를 들어 이진 산술의 반올림 오차가 받아들일 수 없거나, 하드웨어가 부동소수점 명령어를 지원하지 않을 수 있어요.
Ada는 프로그래머가 필요한 십진 정밀도(자릿수)와 배율 인자(scaling factor, 10의 거듭제곱), 그리고 선택적으로 범위를 지정할 수 있게 하는 십진 고정소수점 타입 범주를 제공해요. 실제로 값은 지정된 10의 거듭제곱으로 암묵적으로 배율 조정된 정수로 표현돼요. 이것은 예를 들어 금융 애플리케이션에 유용해요.
간단한 십진 고정소수점 타입의 구문:
type <type-name> is
delta <delta-value> digits <digits-value>;
이 경우 delta와 digits는 컴파일러가 범위를 유도하는 데 사용돼요.
십진 delta (Decimal delta)
delta는 타입에 필요한 십진 정밀도를 결정해요. 예를 들어 소수점 뒤 2자리를 사용하고 싶다면 delta 10.0 ** (-2)라고 써요. 이는 delta 0.01과 같아요. (두 정의 모두 delta 10.0 ** (-2)와 delta 0.01 모두 올바르므로 아무거나 쓸 수 있어요.)
with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
type Decimal is
delta 10.0 ** (-1) digits 3;
-- Alternatively:
-- type Decimal is
-- delta 0.1 digits 3;
begin
Put_Line
("The decimal precision of Decimal is "
& Decimal'Delta'Image);
end Decimal_Fixed_Point_Types;
이 예시에서는 십진 정밀도가 0.1인 Decimal 타입을 선언해요. 'Delta 애트리뷰트로 타입의 십진 정밀도를 보여줘요.
십진 digits (Decimal digits)
당연하게도, 타입 선언의 digits 부분은 타입이 표현할 수 있는 자릿수를 결정해요. 예를 들어 digits 3이라고 쓰면 -999부터 999까지의 세 자리 값을 표현할 수 있어요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
type Decimal is
delta 10.0 ** (0) digits 3;
begin
Put_Line ("The minimum value of Decimal is "
& Decimal'First'Image);
Put_Line ("The maximum value of Decimal is "
& Decimal'Last'Image);
end Decimal_Fixed_Point_Types;
이 예시에서는 -999부터 999까지의 범위를 가진 Decimal 타입을 선언해요. 'First와 'Last 애트리뷰트로 각각 범위의 첫 값과 마지막 값을 보여줘요.
더 읽기: 위 애플리케이션을 실행하면 첫 값과 마지막 값이 각각 -999.0과 999.0임을 알 수 있어요. 즉 소수점이 있는 값이죠. 엄밀히 말하면 delta가 1.0이므로 실제 첫 값과 마지막 값은 -999와 999예요. 애플리케이션 출력(값 -999.0과 999.0)에서 보이는 소수점(.0)은 이것이 정수 값이 아니라는 것만 나타낼 뿐, 확장된 십진 정밀도를 나타내는 것은 전혀 아니에요.
with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
type Decimal is
delta 10.0 ** (0) digits 3;
D : Decimal := 0.1;
-- ^^^
-- ERROR: value cannot be represented
-- by Decimal type.
begin
Put_Line (D'Image);
end Decimal_Fixed_Point_Types;
D에 값 0.1을 대입하는 것은 Decimal 타입이 이 값을 표현할 수 없기 때문에 잘못됐어요.
더 읽기: 위 Decimal 타입은 다음 부동소수점 타입 선언과 비슷하지만 동등하진 않아요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
type Float_999 is
digits 3
range -999.0 .. 999.0;
begin
Put_Line ("The minimum value of Float_999 is "
& Float_999'First'Image);
Put_Line ("The maximum value of Float_999 is "
& Float_999'Last'Image);
end Decimal_Fixed_Point_Types;
이 예시의 Float_999 타입은 이전 예시에서 선언한 Decimal 타입과 (대략) 같은 범위(-999부터 999)를 가져요. 하지만 고정소수점과 부동소수점 타입 사이에는 상당한 차이가 있어서 이 타입 선언들이 동등하다고 말할 수는 없어요.
십진 delta와 digits 결합 (Decimal delta and digits)
이 세 자리(즉 digits 3)를 소수점 뒤 두 자리의 십진 정밀도(delta 10.0 ** (-2))와 결합하면 -9.99부터 9.99까지의 범위를 얻어요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Fixed_Point_Types is
type Decimal is
delta 10.0 ** (-2) digits 3;
begin
Put_Line ("The minimum value of Decimal is "
& Decimal'First'Image);
Put_Line ("The maximum value of Decimal is "
& Decimal'Last'Image);
end Decimal_Fixed_Point_Types;
이 예시에서는 (예상대로) -9.99부터 9.99까지의 범위를 가진 Decimal 타입을 선언해요.
delta의 요구사항 (Requirements for the delta)
십진 고정소수점 타입의 delta 표현식은 반드시 10의 거듭제곱이어야 해요. 다른 값을 사용하면 컴파일 오류가 나요. 예를 들어:
package Decimal_Fixed_Point_Type_Error is
type Decimal_Error_1 is
delta 2.0 ** (-1) digits 3;
-- ^^^^^^^^^^^
-- ERROR: not power of ten
type Decimal_Error_2 is
delta 0.125 digits 3;
-- ^^^^^
-- ERROR: not power of ten
end Decimal_Fixed_Point_Type_Error;
이 예시에서 (Decimal_Error_1과 Decimal_Error_2의) 타입 선언은 delta 표현식이 10의 거듭제곱이 아니기 때문에 잘못됐어요.
일반 고정소수점 타입 (Ordinary fixed-point types)
일반 고정소수점 타입은 값이 실제로 배율 조정된 정수라는 점에서 십진 고정소수점 타입과 비슷해요. 차이는 배율 인자에 있어요. 십진 고정소수점 타입의 경우 타입의 delta가 명시적으로 주는 배율 인자가 항상 10의 거듭제곱이에요. 반면 일반 고정소수점 타입의 delta는 10의 거듭제곱 값으로 제한되지 않고 임의의 밑(base)을 가질 수 있어요.
더 읽기: 일반 고정소수점 타입을 기계에서 표현할 때 컴파일러는 타입 선언에 지정된 delta 값에서 유도된 배율 인자를 선택해요. 이 컴파일러 선택 배율 인자는 기본적으로 2의 거듭제곱이에요 — delta에 제공된 값이 2의 거듭제곱이 아니어도요. 그래서 일반 고정소수점 타입을 때로 이진 고정소수점 타입이라고도 불러요.
일반 고정소수점 타입의 구문:
type <type-name> is
delta <delta-value>
range <lower-bound> .. <upper-bound>;
예를 들어 역삼각 함수 계산용 일반 고정소수점 타입 T_Inv_Trig를 정의할 수 있어요:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics; use Ada.Numerics;
procedure Custom_Fixed_Point_Range is
type T_Inv_Trig is
delta 0.0005
range -Pi / 2.0 .. Pi / 2.0;
begin
Put_Line ("Delta value of T_Inv_Trig: "
& T_Inv_Trig'Image
(T_Inv_Trig'Delta));
Put_Line ("Minimum value of T_Inv_Trig: "
& T_Inv_Trig'Image
(T_Inv_Trig'First));
Put_Line ("Maximum value of T_Inv_Trig: "
& T_Inv_Trig'Image
(T_Inv_Trig'Last));
end Custom_Fixed_Point_Range;
이 예시에서는 -π/2부터 π/2까지의 범위와 delta 0.0005를 가진 T_Inv_Trig 타입을 정의해요. 이 경우 delta는 10의 거듭제곱도 2의 거듭제곱도 아니라는 점에 주의하세요. (사실 이 값은 2000.0 ** (-1)에 대응돼요.)
고정소수점 vs 부동소수점 타입 (Fixed-point vs. floating-point types)
고정소수점과 부동소수점 타입의 주요 차이는 고정소수점 타입에는 지수(exponent)가 없다는 점이에요. 이는 작은 값을 사용하는 계산에 영향을 미쳐요. 작은 값은 부동소수점 타입으로는 여전히 표현 가능할 수 있지만, 고정소수점 표현에서는 그 작은 값이 단순히 사라질(즉 0이 될) 수 있어요. 십진 타입 Decimal을 부동소수점 타입 Float_32와 비교하는 예시를 볼게요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Decimal_Vs_Floating_Point_Types is
type Decimal is
delta 10.0 ** (-2) digits 9;
type Float_32 is
digits 6
range -9999999.99 .. 9999999.99;
D : Decimal := 0.01;
F : Float_32 := 0.01;
begin
Put_Line ("D = " &
D'Image);
Put_Line ("F = " &
F'Image);
D := D / 2.0;
-- ^^^^^^^
-- Value becomes zero.
F := F / 2.0;
-- ^^^^^^^
-- Exponent is used to
-- represent smaller
-- value.
Put_Line ("D = " &
D'Image);
Put_Line ("F = " &
F'Image);
end Decimal_Vs_Floating_Point_Types;
이 예시의 두 타입은 대략 같은 크기와 범위를 가져요. 하지만 2로 나누는 연산의 결과는 같지 않아요. 지수 때문에 연산 후 F는 예상 값(0.005)을 가지는 반면, D의 값은 0이에요. 그 이유는 결과 값 0.005를 Decimal 타입의 십진 정밀도로 표현할 수 없기 때문이에요. 반면 F의 경우 지수의 단순한 변경으로 값을 표현할 수 있어요.
방금 설명한 정밀도 부족은 고정소수점 타입의 단점처럼 보일 수 있어요. 하지만 알고리즘과 적용 분야에 따라 이것은 정확히 우리가 원하는 동작일 수 있어요. 이 장 서두에서 언급했듯 금융 애플리케이션은 십진 타입의 이점을 얻는 반면, 부동소수점 타입을 사용하면 예측할 수 없거나 바람직하지 않은 동작이 나올 수 있어요.
또 하나의 주요 차이는 고정소수점 연산이 기계 연산으로 변환되는 방식에 관한 것이에요. 대부분의 경우 고정소수점 타입에 대한 연산은 프로세서에서 정수 레지스터와 명령어를 사용해 모델링돼요. 기본적으로 컴파일러는 고정소수점 타입을 정수 타입으로 매핑하되 약간 다른 숫자 규칙을 사용해요. 이 사실은 부동소수점 유닛이 없거나 그 사용이 CPU 사이클이나 전력 소비 측면에서 비용이 더 큰 특정 임베디드 애플리케이션에서 이점이 될 수 있어요. 따라서 이런 특정 애플리케이션에서는 고정소수점 타입을 대안으로 고려할 수 있어요.