# 016 — 참·거짓과 정확한 정수: `bool`과 `i64` 작성일: 2026-09-30. 상태: 1단계(`bool`)와 2단계(`i64`) 구현, 검사로 고정. aup의 v0c에 남은 것(aup `docs/design/002-dsl-direction.md`의 `i64 bool`)이고, [015](015-boundary-meaning.md)가 정한 경계마다의 값의 뜻에 두 종류를 더한다. ## 문제 - **참·거짓이 수다.** 비교와 논리 연산은 0이나 1을 내고, 0이 아니면 참이다. 그래서 할 일의 `done`은 `done = 0`인 수 필드다. 보기의 JSON에는 `true` 대신 `1`이, 화면에는 `1`이 나온다. `done + 1`도 타입 오류가 아니다. 조건 자리에 아무 수나 들어간다. - **정수가 2^53에서 멈춘다.** 수는 binary64다. 밖에서 오는 64비트 ID(많은 서비스의 ID가 2^53을 넘는다), 나노초 시각(2^53 나노초는 104일), 큰 누계는 정확히 들지 못한다. 015는 수가 비트까지 그대로라고 정했지만, 그 비트가 정수를 담지 못한다. ## 원칙 aup의 원칙(002)을 따른다. 위치가 정수가 아니듯 참·거짓은 수가 아니고, 길이에 단위를 적듯 정수는 정확하다. 둘 다 Pure 검증기의 스택 종류가 되어, 검증된 모듈은 실행 중에 타입을 검사하지 않는다(008). ## 1단계: `bool` **언어(aup).** - 타입 `bool`, 리터럴 `true`와 `false`. - 비교(`< <= > >= == !=`, text 비교), `has`, 논리(`&& || !`)가 bool을 낸다. `&&`와 `||`는 지금처럼 양쪽을 모두 계산한다. - 조건 자리(`if`, 조건식, `where`, `require`, `count`와 `first`의 조건, 걸러진 표)는 bool만 받는다. 수를 주면 진단이 고치는 법을 말한다(`x != 0`). - bool끼리 `==`와 `!=`는 된다. 크기 비교, 산술, `sum`·`max`·`min`·`distinct`는 안 된다. 키 있는 목록의 키가 될 수 없다. - 선언: `state on = false`, 레코드 필드 `done = false`, 메시지와 출력의 필드 `ok: bool`, 보기와 열은 bool 식이면 bool이다. - 보간(`"{done}"`)은 `true`나 `false`다. **모듈 형식 v6(Pure).** 판을 올린다. 비교의 결과 종류가 바뀌므로 v5 모듈은 다시 빌드한다. - 검증기의 스택 종류에 bool을 더한다. 실행 중의 값은 수 칸의 0과 1이다. - 새 명령은 없다. CONST의 인자 1이 bool 상수(값 +0이나 1)이고, NUM_TEXT의 인자 1이 bool을 `true`·`false`로 쓴다. LOAD·STORE와 지역 칸은 수와 bool을 함께 다룬다(상태의 타입이나 칸의 담는 것이 정한다). - bool을 내는 명령: LT–NE, TEXT_COMPARE, LIST_HAS, AND·OR·NOT. EQ와 NE는 bool 둘도 비교한다. - bool을 받는 명령: JZ, REQUIRE, AND·OR·NOT, 집계의 포함 여부(count와 first는 bool 하나, sum·max·min·distinct는 수와 bool), 걸러진 표의 필터. - 새 타입 번호: 상태 5(bool, 8 B 값은 f64 0이나 1), 레코드 필드 2(bool, 기본값 f64 0이나 1), 메시지 필드 6(bool, 접두부의 1바이트 0이나 1), 보기 3(bool). 지역 칸의 담는 것 2(bool). 상수, 초기값, 기본값은 +0이나 1이어야 한다. - 메시지의 bool 필드 바이트가 0이나 1이 아니면 메시지가 `limit`으로 실패한다(도착할 때, 015의 거절과 같은 자리). **경계(015에 더한다).** - JSON: bool 필드는 `true`·`false`만 받는다(`0`, `1`, `"true"`는 거절). 보기와 표의 bool은 `true`·`false`다. - 쿼리: `true`·`false`. - 저장: 목록의 bool 필드는 수 열의 0과 1이다(저장 형식도 수다). 레코드의 필드 타입이 뜻을 정한다. - 전달: 링크의 FIELDS와 HTTP 전달의 JSON이 bool을 bool로 싣는다. 받는 입력의 필드도 bool이어야 한다. 넓히기는 없다. - 모듈 교체: 수 필드나 상태가 bool이 되면 0(−0도, 015)과 1은 `false`와 `true`로 옮기고, 다른 수는 기본값이 된다. bool이 수가 되면 `false`와 `true`는 0과 1이다. 저장소에서 다시 열 때와, 교체가 출력의 필드를 바꿀 때 기다리던 보냄도 같다. - 화면(Vega): 글자로는 `true`·`false`다(보간과 같다). 앱이 다르게 보이고 싶으면 조건식으로 글을 고른다. **옮기는 비용.** 예제의 수 깃발을 bool로 바꾼다(`done`, `urgent`, `running`, `holding`). 저장된 0과 1은 위의 교체 규칙으로 그대로 이어진다. ### 1단계에서 알게 된 것 - **새 명령은 필요 없었다.** 처음 설계는 BOOL_CONST와 BOOL_TEXT를 더하려 했다. CONST와 NUM_TEXT의 인자 하나(0이던 자리)로 충분했다. 검증기의 종류와 타입 번호만 늘었고, 실행기는 bool을 수의 칸의 0과 1로 그대로 돈다(잎 단위 실행도). - **저장된 보냄의 bool을 거절하던 곳이 있었다.** 출력의 필드 타입을 저장소에서 다시 읽는 검사가 `>= 행`으로 bool(6)까지 막아, bool 필드를 가진 출력의 보냄이 기다리는 노드는 다시 시작하지 못했다. `server_meaning`의 재시작 출구가 잡았고, 고쳤다. test-module이 잡지 못한 까닭은 bool 보냄을 저장하고 다시 여는 경우가 없어서였다. 그 경우를 더했다(고치기 전의 코드로는 실패한다). - **−0은 `false`로 옮긴다.** 수가 bool이 될 때 0과 1만 옮기기로 했는데, 015가 0에는 부호가 없다고 정했으므로 −0도 0이다. 처음 구현은 −0을 기본값으로 보냈다. - **JSON의 `True`는 타입이 아니라 문법의 거절이다**(`json`). 1, 0, `"true"`, `null`, 배열은 `json_type`이다. 검사: Pure test-module에 bool 검증 거절 28가지와 실행·메시지 바이트·보냄·링크·교체·저장(보냄 포함)을 더했고, 레인 대조의 무작위 생성기가 수와 bool을 나눠 만든다(3,600 집계가 비트까지 같다). `server_meaning`은 값 67개마다 bool 필드를 싣고 나가는 길 일곱에서 본다(거절 52가지, bool은 거절의 종류까지). HTTP에서 bool 보기의 글자와 bool 인자도 본다. aup는 bool 진단 20가지, 받아들이는 형식, 출력마다 `true`·`false`인지 보는 검사(파이썬의 `True == 1`을 같게 보지 않는 비교)를 더했다. Vega는 bool 보기가 CLI, TUI, 문장, 웹에서 `true`·`false`로 보이는지 본다. 커밋: Pure `86fd4f7`·`d1c90e2`·`d415a5a`, aup `b8e3faa`, Vega `72ecae6`. ## 2단계: `i64` 1단계와 같은 방식이다. 판은 올리지 않는다. 새 명령, 새 타입 번호, 0이던 자리의 값만 더하므로 v6 모듈은 그대로 유효하다. **언어(aup).** - 타입 `i64`. 상태 `state n: i64 = 0`(값을 생략하면 0), 레코드 필드 `id: i64`(기본값 0, `= 5`도 된다), 메시지·출력·인자의 필드 `id: i64`. `let`은 값의 타입을 따른다. - 정수 리터럴(점과 지수가 없는 것)은 문맥의 타입이 된다. 연산이나 비교의 다른 쪽, 대입이나 필드의 자리가 문맥이다. 문맥이 없으면 수다. 글자 그대로 정확하게 읽는다(`9007199254740993`). i64 범위를 넘는 리터럴이나 i64 자리의 소수는 진단이다. - `+ - *`와 단항 `-`는 넘치면 사건이 `overflow`로 실패한다. `/`와 `%`는 내림한다(나머지는 나누는 수의 부호를 따른다). 0으로 나누면 `divide`다. - 수와 섞지 않는다. `i64(x)`는 x가 정수이고 범위 안일 때, `number(i)`는 |i| ≤ 2^53일 때만 되고, 아니면 사건이 `limit`으로 실패한다. - 비교, `min`·`max`, 집계(`sum`은 넘치면 `overflow`), 키 있는 목록의 키(`by id`), 조각(`xs[lo .. hi]`), 보간(정확한 자릿수)이 된다. **모듈(Pure).** - 실행 중의 값 칸은 수와 i64를 같은 8바이트에 겹쳐 든다. 스택, 상태(`PureApp.values`), 목록의 칸(`PureCell`), 지역 칸, 명령의 상수가 그렇다. 옮기는 명령(LOAD, STORE, ITEM_GET·ITEM_SET, LIST_APPEND, 지역 칸, PICK, SLIDE, FIELD, SEND)은 비트를 그대로 옮기고, 계산하는 명령만 종류를 본다. 검증기가 종류를 보장한다. - 새 명령을 뒤에 붙인다. I64_ADD, I64_SUB, I64_MUL, I64_DIV, I64_MOD, I64_NEG, I64_COMPARE(인자는 관계, TEXT_COMPARE처럼), I64_MIN, I64_MAX, I64_FROM(수 → i64), NUM_FROM(i64 → 수). CONST의 인자 2는 i64 상수(값의 8바이트가 정수)이고, NUM_TEXT의 인자 2는 i64의 글자다. 새 실패 종류 `overflow`. - 타입 번호: 상태 6, 레코드 필드 3, 메시지 필드 7(접두부의 8바이트, 부호 있는 little-endian), 보기 4, 지역 칸의 담는 것 3. - 집계: FOLD의 인자에 PURE_FOLD_INTEGER(0x40)가 있으면 sum·max·min·distinct가 i64 값을 받는다(sum·max·min의 결과는 i64). i64가 든 블록은 잎 단위로 돌지 않고 한 항목씩 돈다(필요해지면 뒤에 더한다). distinct의 집합은 빈 칸 표시를 따로 둔다 (지금의 표시 `UINT64_MAX`는 i64의 −1이다). - 키 있는 목록: i64 키는 정수로 비교한다. 찾기는 안내 키 없이 잎의 열을 이진 탐색한다(안내 키는 binary64라 정수에 맞지 않는다. 정수 안내 키는 뒤의 일이다, [109](../research/109-finding-items.md)의 "다음"). 키 범위의 자동 경계(008의 키 범위)는 i64 키에 아직 쓰지 않는다. 조각은 i64 두 개를 받는다. - 보기: `pure_app_view`는 수와 bool을 주고, 새 함수가 i64를 준다. **경계(015에 더한다).** - JSON과 쿼리: i64 필드는 `-?(0|[1-9][0-9]*)`(부호 `+`, 앞의 0, 점, 지수 없이)이고, −2^63부터 2^63−1까지다. 그 밖은 u32처럼 거절한다(JSON은 `json_type`, 쿼리는 `query`). - 보일 때: 정확한 10진 자릿수다. 보기의 글자, 표의 JSON(JSON의 수로, 자릿수 그대로), HTTP 전달의 JSON, `pure-run`, 화면이 모두 같다. 자바스크립트의 `JSON.parse`처럼 binary64로 읽는 쪽은 2^53 너머에서 값을 잃는다. 그런 쪽에 보일 i64는 앱이 글자로 보인다(보간). - 저장: 상태는 `values` 문서의 종류 6과 8바이트 정수다. 목록은 종류 4(i64)의 열이다. 이미지는 8바이트, 로그는 크기와 상관없이 zigzag varint다. - 전달: u32는 i64로 넓힌다(정확하다). i64와 수, bool 사이는 넓히지 않는다(받지 않고 기다린다). - 모듈 교체와 다시 열기: 수가 i64가 되면 정수이고 범위 안일 때 그 값, 아니면 기본값이다. i64가 수가 되면 |i| ≤ 2^53일 때 그 값, 아니면 기본값이다. bool과 i64는 0과 1로 옮긴다(i64의 다른 값은 bool의 기본값이 된다). **착수 기준**(구현하기 전에 정해 커밋한다). - 바꿀 결정: 위 모양대로 넣을지. 값 칸을 겹치는 것이 수의 경로를 느리게 하면 그 모양을 다시 본다. - 최소 확인: - test-module: 검증기의 거절, 산술의 끝값(±2^63 부근, 내림 나눗셈과 나머지의 부호, MIN/−1), 변환, 상태·필드·보기·보냄·지역 칸, i64 키 목록의 순서와 찾기·조각, 집계와 넘침, 저장·체크포인트·로그의 끝값, 교체. - `server_meaning`에 i64 필드. 끝값(0, −1, 2^53+1, 2^63−1, −2^63)이 모든 길에서 같고, 거절할 글자(`1.0`, `1e3`, `+1`, `01`, `"1"`, 2^63, −2^63−1)는 모든 들어오는 길이 거절한다. - aup: 진단(섞기, 리터럴의 범위와 소수), 리터럴의 타입, 참조 모델. Vega: i64 보기의 자릿수. - 통과 조건: 모두 초록이고, 수의 경로는 그대로다. 은행 질의([091](../research/091-queries.md))와 TPC-C(RAM, W=1)를 전과 후 번갈아 여섯 쌍 재서, 중앙값이 3% 넘게 느려지지 않는다. ### 2단계에서 알게 된 것 - **리터럴의 타입은 다른 쪽이 정한다.** `1 + x`에서 1을 내보낼 때는 x의 타입을 아직 모른다. aup는 다른 쪽을 시험 삼아 컴파일해 타입만 알고 되돌린다. 리터럴만의 식(`60 * 60 * 1000`, 리터럴을 고르는 `if`)도 통째로 다른 쪽을 따른다. 처음 구현은 시험 컴파일이 겹치면 한 겹마다 두 배가 됐다(괄호 22겹에 0.25초). 원하는 타입을 아래로 넘겨 식마다 한 번만 보게 했다(30겹에 1 ms 미만). aup의 검사가 30겹을 지킨다. - **문맥이 없는 리터럴은 수로 남는다.** 그래서 있던 프로그램의 뜻이 바뀌지 않는다. `-7 / 2`는 전처럼 −3.5이고, i64의 내림 나눗셈은 한쪽이 i64일 때다(`i64(-7) / 2`는 −4). i64를 쓰지 않는 프로그램은 모듈이 바이트까지 같다. i64 전과 후의 aup가 예제 18개(aup 10, Vega 8), TPC-C, 은행, 은행의 질의 넷을 같은 바이트로 빌드한다. - **값 칸을 겹치는 것은 따로 했다**(`600f576`). 옮기는 명령이 비트를 복사하고 계산하는 명령만 수로 읽게 바꾼 것만으로는 모든 검사가 그대로였다. i64는 그 위에 더했다. - **옮기는 규칙은 하나다.** 모듈 교체, 저장된 값과 항목을 다른 모듈이 읽을 때, 기다리던 보냄을 옮길 때가 모두 `pure_cell_as` 하나를 쓴다. 수·bool·i64 사이에서 맞으면 옮기고 아니면 기본값이라는 규칙이 한 곳에 있다. 실패의 이름도 세 표에 나뉘어 있던 것을 `pure_fault_name` 하나로 모았다(`overflow`가 새로 생겼다). - **수의 경로.** 기준을 채웠다(TPC-C 처리량의 중앙값 −0.6%, 은행 질의 0.0%에서 +2.2%). 시간으로는 가리지 못하는 1–3%는 명령을 세어 봤다. i64가 수의 경로에 더한 두 곳을 걷어냈다. 잎 단위의 폴드가 항목마다 i64인지 묻던 것과, 인라인되지 않게 된 칸 읽기다. 남은 것은 명령 +0.4%에서 +1.4%다([110](../research/110-counting-instead-of-timing.md)). 검사: Pure test-module에 i64 산술의 끝값 28가지와 검증 거절 14가지, 키 있는 i64 목록의 앱(순서, 넘치는 합, 되돌린 추가, 8바이트 메시지와 보냄, 모든 자릿수), 옮기기와 교체, 메모리와 대조하는 저장 셋(재시작, 이미지 46개)을 더했다. `server_meaning`은 값마다 i64 필드를 싣고 끝값 13개를 나가는 길 일곱에서 본다(거절 84가지, i64는 거절의 종류까지). aup는 i64 진단 18가지, 받아들이는 형식(30겹의 식이 선형인지도), 2^53 너머와 끝값을 사건마다 보는 검사, 생성 프로그램의 i64 값을 더했다. Vega는 i64 보기가 CLI, TUI, 문장, 웹에서 모든 자릿수로 보이는지 본다. 커밋: Pure `600f576`·`8c92f4c`·`fc65d66`·`fcc1f67`, aup `24d95e1`, Vega `49e0df3`. ## 1단계의 검사 - aup: 진단(수를 조건으로, bool 산술, bool 크기 비교, bool 키), 받아들이는 형식, 참조 모델. - Pure: test-module(검증기가 bool을 섞은 모듈을 거절한다, bool 상태·필드·보기·보냄), `server_meaning`에 bool 필드(JSON, 쿼리, 링크, HTTP 전달, 재시작, 교체). - Vega: bool 보기가 표면마다 `true`·`false`로 보인다.