프로그래밍 언어 내부적으로
보통 숫자는 리터럴 상수로 취급합니다.
리터럴 상수(literal constant)란 그 이름에
대해서 값이 이미 정해져 있기 때문에
변수처럼 바뀔 수 없는거죠.
이 개념을 이해하는 것이 쉽지는 않습니다.
사람이 바라보는 시각과
컴퓨터가 처리하는 데이터에는
관점의 차이가 있기 때문입니다.
예를 들어서 사람이 숫자 1을 키보드에
입력하면 특정의 아스키 코드 신호가
표준 입력 장치에 전달이 되고 이는
운영체제가 하드웨어 Interupt 를
감지하여 특정의 아스키 코드 61(10진수)를
인식하게 합니다.
인식하는게 끝이 아닙니다.
운영체제가 받은 코드 61을
표준출력장치인 모니터의 스크린에
출력할 수 있도록 합니다.
사람이 손가락으로 1을 누르고
그 신호가 모니터에 표시되서
다시 1이라는 숫자를 확인하기 까지
복잡한 과정을 거치는데
결국 이것은 사람의 입력을 의미합니다.
약간 어려운 설명을 하는 것은
프로그래밍의 원리적인 부분에 대해서
너무 당연하다고 생각하기 때문에
생략하기 때문입니다.
키보드에서 1을 입력해서 연산을
시도하면 그것은 이진비트로
메모리에 저장될 때 32비트에서
0000 0000 0000 0001
로 하드웨어에 저장되지만
키보드에서 받는 신호를
아스키 코드로 변환했을 때
0000 0000 0011 1101
(10진수 61) 인 것 입니다.
10진수 61 아스키 코드에다가
1이외에 다른 값을 할당하지 않습니다.
그것이 리터럴 상수 1인 것 입니다.
어떻게 보면 당연한 것이지만
평소엔 전혀 신경쓰지 않는
내용이기 때문에 원리를 까먹고
있는데요. 리터럴 상수와 변수의
차이가 뭐냐?
라는 간단한 질문뒤에는 좀 더
심오한 세계가 있다는 부분입니다.
컴퓨터가 순수하게 계산을 하기
위해서는 십진법이라던가
아스키코드 같이 번거로운 것들을
사용할 필요가 없습니다.
순수한 계산을 위해서는 이것들은
오히려 컴퓨터를 느리게 합니다.
십진법과 아스키코드는 오로지
인간이 컴퓨터를 컨트롤 하기 위해서
만들어진 도구에 불과합니다.
인간의 언어와 컴퓨터 코드를
변환하는데 가장 많이 쓰이고
가장 중요하기 때문에 사용될 뿐이지
변환과정 자체는 컴퓨터를 느리게
할 뿐입니다.
아스키 코드는 그나마 7bit 시절이
있었지만 지금은 유니코드로 확장되서
최소 16bit 를 사용하고 있죠.
성능이 좋은 컴파일러는 이러한
수치 계산을 이진수 기계어 코드로
미리 변환하여 컴파일 해둡니다.
인터프리터 방식의 동적 언어와
컴파일 방식의 정적 언어의 결정적
차이점은 사전에 인간의 언어가
처리가 되냐 안되냐는 부분이 큽니다.
전자는 유연성은 있지만 성능을
잡아먹고 후자는 성능이 좋지만
유연성이 떨어집니다.
*******
리터럴 상수의 의미를 이해했다면
그 다음 단계로 가서 이들을 이용한
연산처리를 할 수 있습니다.
사칙연산은 보통 프로그래밍 언어와
상관없이 거의 똑같습니다.
사칙연산에는 연산자를 사용하는데
하스켈에서 보면 연산자는 함수와
의미가 같습니다.
소스코드 에디터로 사칙연산 코드를
작성해보겠습니다.
main = do
print (1 + 3)
print (5 - 2)
print (3 * 4)
print (10 / 2)
ghci> main
4
3
12
5.0
print는 다양한 타입을 출력할 수 있는데
특히 숫자의 출력에 유용합니다.
여기서 유의할 점은 마지막
10/2는 5.0 으로 부동소수점
타입으로 계산되었습니다.
더하기는 +
빼기는 -
곱하기는 *
나누기는 /
곱하기와 나누기 연산자는
프로그래밍을 해본적이 없다면
생소할 수 있습니다만,
키보드에 우리가 알고 있는
수학 기호가 없어서 이렇게
사용합니다.
사칙연산만 조합을 해도
다양한 연산을 할 수 있습니다만,
당연히 더 많은 기능이 가능하겠죠?
아래 코드는 제곱 ** 나머지 연산 `mod`
루트 sqrt 를 계산합니다.
main = do
print (2**3)
print (10 `mod` 7)
print (sqrt 36)
ghci> main
8.0
3
6.0
연산자 제곱의 기호는 ** 이고
루트는 함수 이름으로 sqrt 인데
이런 것을 너무 심각하게 생각할
필요는 없습니다.
하스켈에서는 사칙연산의 연산자
+ - * / 모두 함수입니다.
연산자와 함수에 대해서 비교하면
근본적으로 공통된 부분이 있습니다.
연산자가 가장 원초적인 연산을
담당하기 때문에 연산자라고 하는데
빡세게 분류하면 CPU 인스트럭션
수준에서 처리하는게 연산자이고
함수는 좀더... 복잡한 코드로
만들어져 있는 것 입니다.
CPU도 레지스터를 한개 이상
받아서 연산하는 것이기 때문에
함수에서 인자를 받는 것 처럼
뭐 똑같다고 볼 수도 있고
그렇습니다.
당연한 내용을 이야기하는 것
같긴 한데 이상하게 컴퓨터 교재를
보다 보면 이런 내용에 대해서는
약간 건너 뛰고 이야기 하는 책들이
대부분이라 어느 순간
빡치는 일도 많이 생깁니다.
여기까지 배경지식을 가지고
사칙연산을 한다면
하스켈 뿐만 아니라 대부분
컴파일 언어와 인터프리터 언어의
작동방식에 대해서 이해할 수
있을 것 입니다.
참고로 컴퓨터는 이진법으로 셈을 하고
2의 보수 방식을 사용합니다.
나중에 시스템 적으로 깊게 들어가길
원한다면 2의 보수 정도는
알고 있는게 좋습니다.
부동소수점은 정수와는 전혀
다른 원리로 저장이 됩니다.
방대한 실수를 표현해야 하기 때문인데
실수는 무한적으로 순환하는 경우가 많아서
숫자를 끓어야 하는 컴퓨터 안에서는
여러가지 제한이 있습니다.
그런 것의 내용을 구현한 것이
부동소수점 방식입니다. (IEEE 754)
자바 튜토리얼 (2-3) 자바의 변수 (실수형, 부동소수점 방식)
부동소수의 원리까지는 필요가 없습니다만
실수를 다루는 함수들은 알아두면
도움이 됩니다.
아래의 함수는 반올림 round 내림 floor
올림 ceiling 입니다.
main = do
print (round 6.7)
print (round 5.1)
print (floor 3.9)
print (floor 2.1)
print (ceiling 1.1)
print (ceiling 9.5)
ghci> main
7
5
3
2
2
10
하스켈에서 숫자를 다루는 기본에
대해서 알아봤습니다.
뭐니뭐니해도 프로그래밍은
기본에 충실한게 최고입니다.
어렵다고 피하지 말고
한 스텝씩 연습하면서 밟아나가다
보면 어렵지 않게 일정 수준에
도달하여 프로그래밍을 작성할 수
있게 됩니다.
하스켈은 수학적 증명이 가능한
프로그래밍 언어이므로 수학적
지식이 좀 더 있다면 좀 더
유용하게 사용할 수 있을 것 입니다.
다음 포스팅도 하스켈 기초에 대한
튜토리얼을 이어 나가겠습니다.