rokdocs
v0.1

1.6. 숫자 리터럴

소스 코드에 직접 적힌 숫자 글자열의 토큰화 규칙과, 각 리터럴의 타입을 정하는 규칙을 다룹니다.

1.6.1. 기본 형태

숫자 리터럴의 문법은 다음과 같이 정의됩니다.

numeric_lit ::= int_lit | float_lit
int_lit     ::= dec_lit | hex_lit | bin_lit | oct_lit
dec_lit     ::= dec_digit ("_"? dec_digit)*
hex_lit     ::= ("0x" | "0X") hex_digit ("_"? hex_digit)*
bin_lit     ::= ("0b" | "0B") bin_digit ("_"? bin_digit)*
oct_lit     ::= ("0o" | "0O") oct_digit ("_"? oct_digit)*
float_lit   ::= dec_lit "." dec_digit_seq? exponent?
              | dec_lit exponent
exponent    ::= ("e" | "E") ("+" | "-")? dec_digit_seq
dec_digit_seq ::= dec_digit ("_"? dec_digit)*
dec_digit   ::= [0-9]
hex_digit   ::= [0-9a-fA-F]
bin_digit   ::= [01]
oct_digit   ::= [0-7]

진법

지원되는 진법과 그 표기는 다음 표와 같습니다.

진법접두허용 자릿수예
10진없음0-942, 1024
16진0x 또는 0X0-9, a-f, A-F0xFF, 0X1a
2진0b 또는 0B0, 10b1010
8진0o 또는 0O0-70o755

10진수 외의 진법은 정수 리터럴에만 사용하며, 부동소수 리터럴은 10진 표기만 허용합니다.

선행 0 이 붙은 형태 (예: 0123) 는 오직 한 가지 의미만 가지는데, 바로 10진수 123 입니다. C 나 Java 에서 흔히 보이는 "선행 0 = 8진수" 규칙은 도입하지 않으며, 8진수를 표기하려면 반드시 0o 접두를 사용해야 합니다.

자릿수 구분자

가독성을 높이기 위해 자릿수 사이에 밑줄(_) 을 넣을 수 있습니다.

1_000_000
0xFFFF_FFFF
0b1010_0101
3.141_592_653

밑줄을 사용할 때 지켜야 할 규칙은 다음과 같습니다.

  • _ 는 숫자와 숫자 사이에만 위치할 수 있으며, 첫 글자나 마지막 글자가 _일 수는 없습니다.
  • _ 는 진법 접두 직후(0x_FF)나 소수점 바로 앞뒤(1_.0, 1._0) 에 올 수 없습니다.
  • 연속된 밑줄(1__000) 은 거부됩니다.
  • _ 는 평가 시 무시되므로, 1_000 과 1000 은 같은 값을 나타냅니다.

부동소수 형태

다음 두 형태 중 하나에 해당하면 부동소수 리터럴이며, 그 외의 경우는 정수 리터럴이거나 별개의 토큰으로 처리됩니다.

digit+  '.'  digit*  exponent?         // 1.5, 1., 1.5e10, 0.5
digit+              exponent           // 2e10  (점 없이 지수만 있어도 부동소수)

부동소수 리터럴은 소수점 앞에 자릿수가 반드시 있어야 합니다. 0.5 로 적으며, .5 는 부동소수가 아닙니다. . 다음에 자릿수가 오는 형태는 언제나 튜플 인덱스 (t.0, t.5) 입니다.

지수부 (exponent) 는 e 또는 E 에 이어 선택적으로 부호 (+ 또는 -) 를 두고, 그 뒤에 한 자리 이상의 10진 자릿수를 붙여 표현합니다. 따라서 1., 0.5, 1.5e10, 2e-3, 6.022e23 은 모두 정상적인 부동소수 리터럴입니다. 다만 점만 있고 양쪽이 비어 있는 경우 (.) 는 부동소수가 아니라 별개의 토큰으로 분리됩니다.

1. 와 1.foo 의 구분

digit+ '.' 형태 (1.) 는 점 뒤에 식별자가 시작되지 않을 때만 부동소수로 토큰화됩니다. 구체적인 분기 양상은 다음과 같습니다.

입력토큰화
1.5부동소수 1.5 한 토큰
1. (뒤가 끝/공백/구두점)부동소수 1. 한 토큰
1.foo정수 1, ., 식별자 foo 세 토큰
1 .foo동일 (공백은 토큰 분리 결과에 영향 없음)
1..2정수 1, .., 정수 2 세 토큰 (.. 가 더 긴 일치)
1.+2부동소수 1., +, 정수 2 세 토큰

규칙을 한 줄로 요약하면 다음과 같습니다. 숫자 도중 . 뒤의 첫 글자가 XID_Start 이거나 또 다른 . 이라면 그 . 은 부동소수의 일부가 아니며, 그 외의 경우 (구두점, 연산자 글자, 입력 끝) 에는 부동소수에 흡수됩니다.

1.6.2. 타입 접미사

리터럴 뒤에 접미사를 붙이면 그 리터럴이 어떤 구체 타입으로 평가될지를 명시할 수 있습니다.

42i32
3.14f64
2i              // 허수 단위 (예: 복소수)
0xFFu8
1_000_000i64

접미사의 형식은 다음 규칙을 따릅니다. 우선 접미사는 리터럴의 마지막 글자 직후에 공백 없이 붙고, 식별자 글자(XID_Continue) 로만 구성됩니다. 어휘적으로는 한 토큰의 일부로 보존되며, 렉서가 숫자 본체와 접미사 문자열을 함께 묶어 토큰 하나로 만들어냅니다.

숫자 타입 이름 접미사 (i8 i16 i32 i64 isize u8 u16 u32 u64 usize f32 f64) 는 언어가 정의하며, 리터럴의 타입을 그 타입으로 정합니다. f32 / f64 접미사는 정수 형태에도 붙을 수 있습니다 (1f64). 그 밖의 접미사 (2i 등) 는 표준 라이브러리나 사용자가 #[literal(...)] 매직 으로 등록한 핸들러가 해석합니다.

1.6.3. 접미사 없는 리터럴

접미사가 없는 리터럴 (42, 3.14) 의 타입은 기대 타입이 정합니다.

  • 정수 리터럴: 기대 타입이 정수 또는 실수 타입이면 그 타입입니다. 기대 타입이 없으면 i32 입니다.
  • 실수 리터럴: 기대 타입이 실수 타입이면 그 타입입니다. 기대 타입이 없으면 f64 입니다. 정수 타입 자리에는 올 수 없습니다.
  • 이항 연산에서 한쪽 피연산자의 타입이 정적으로 정해져 있으면, 그 타입이 리터럴 쪽의 기대 타입이 됩니다.
  • 정수 리터럴을 실수 타입 자리에 둘 때, 그 실수 타입이 값을 정확히 표현하지 못하면 컴파일 에러입니다 (let f: f32 = 16777217;).
  • 기대 타입이 정수 타입 항을 가진 합 타입이면 정수 리터럴은 그 정수 타입이 됩니다 (let v: i64 | f64 = 1; 의 1 은 i64).
  • 접미사 없는 리터럴끼리의 산술 (1 + 2, -1, 2 * (3 + 4)) 은 타입이 정해진 피연산자나 기대 타입을 만날 때까지 타입이 정해지지 않은 채로 남습니다. 만나면 식 전체가 그 타입이 되고, 끝까지 만나지 않으면 위 기본값 (i32 / f64) 입니다.
  • if / else 의 한 분기가 접미사 없는 정수 리터럴이고 다른 분기가 정수 타입이면, 리터럴 분기는 다른 분기의 타입을 따릅니다.

기대 타입은 let 의 타입 주석, 인자 자리의 매개변수 타입, 반환 자리의 반환 타입, 그리고 위 이항 연산 규칙에서 옵니다.

let a = 42;              // i32
let b = 3.14;            // f64
let x: f64 = 1;          // f64
let y: u8 = 200;         // u8
let r = a * 2;           // 2 는 i32 (a 를 따름)
let s: f64 = 2.0;
let t = s * 2;           // 2 는 f64 (s 를 따름)
let e: i32 = 1.5;        // ERROR - 실수 리터럴은 정수 타입 불가
let w: i64 = 7i64;
let k = w + (1 + 2);     // 1 + 2 는 i64 (w 를 만나서)
let m: u8 = 2 * 100;     // 2 * 100 은 u8 (기대 타입)
let n = 1 + 2;           // i32 (끝까지 정해지지 않음 → 기본값)
let v = if flag { 0 } else { w };   // 0 은 i64 → v: i64

1.6.4. 범위 검사

리터럴 값이 타입의 범위를 벗어나면 컴파일 에러입니다. 16진, 8진, 2진 리터럴도 10진과 같은 범위 규칙을 따릅니다.

let a: u8 = 256;         // ERROR - u8 범위 밖
let b = 0xFFi8;          // ERROR - 255 는 i8 범위 밖
let c = 0xFFu8 as i8;    // OK - 비트 패턴이 필요하면 as 로 변환 (-1)

리터럴과 상수만으로 된 산술은 컴파일 타임에 접으며, 그 결과가 타입의 범위를 넘거나 0 으로 나누면 컴파일 에러입니다 (5.2.4).

let m: u8 = 2 * 200;     // ERROR - 400 은 u8 범위 밖

1.6.5. 거부되는 형태

다음 입력은 어휘 단계에서 즉시 에러로 처리됩니다.

입력거부 사유
0x16진 접두 뒤 자릿수 없음
0b2진 접두 뒤 자릿수 없음
0o8진 접두 뒤 자릿수 없음
1__0연속된 _
1__ 로 끝남
0x_FF진법 접두 직후 _
1e지수부에 자릿수 없음

다만 _1 은 숫자 리터럴이 아니라 식별자로 토큰화되므로, 거부되는 입력이 아니라 다른 종류의 토큰으로 분류된다는 점을 유의합니다.

1.6.6. 음수

음수 리터럴이라는 별도의 토큰은 존재하지 않습니다. -42 는 단항 연산자 - 와 리터럴 42라는 두 토큰으로 구성되며, 리터럴 자체는 항상 음이 아닌 형태로 토큰화됩니다.

범위 검사에는 한 가지 예외가 있습니다. 단항 - 바로 뒤의 리터럴은 그 타입의 MAX + 1 까지 허용하므로 -128i8 은 i8 의 최솟값입니다. 이항 연산의 피연산자에는 이 예외가 없습니다.

let m = -128i8;          // OK - i8::MIN
let n = a - 128i8;       // ERROR - 128 은 i8 범위 밖

GitHub에서 이 페이지 고치기