2006년 7월 27일 목요일

오타를 수배합니다~

드디어 재고가 소진된 "클릭하세요 C# 2.0 프로그래밍"이 재판에 들어갑니다.

책을 읽으시면서 발견한 오타, 오류등을 이 포스트의 댓글이나 제 메일(steelblue@nate.com)로 보내주시면 이번 재판에 반영하려고 합니다. (댓글에 올려주시면 다른 분들도 보실 수 있으니 메일보다는 가급적 댓글을 이용해 주시기 바랍니다.)

오타를 많이 발견해서 보내주신 분께는 소정의 선물을 보내드리겠습니다. ㅋㅋ

"클릭하세요 C# 2.0 프로그래밍"을 사랑해 주신 모든 독자분들께 감사드립니다.

그럼 즐프~

2006년 7월 25일 화요일

실력 유지 6시간, 실력 향상 10시간.

얼마 전 한국을 방문하신 러시아의 한 피아니스트로부터 이야기를 들었습니다. 쌩 빼쩨르부르크 음악원의 피아노 교수님이신데, 지금도 실력을 유지하려면 6시간, 실력이 늘려면 10시간은 연습해야 한다고 하시더군요.

그래서 아무리 바빠도 최소한의 실력을 유지하기 위한 연습시간은 꼭 지키신답니다. 그 이야기를 듣고 조금 충격을 받았습니다.
그런 "대가"도 실력을 유지하기 위해서 저런 노력을 기울이는데 나는 혹시 멈춰 서 있지는 않는가? 봐야하고 공부해야 할 책도 많은데 왜 이렇게 게으른가? 하는 생각도 좀 들었고요.

요즘 유행하는 말마따나 "열공(열심히 공부하다)" 해야겠습니다. ㅋㅋ

2006년 7월 20일 목요일

C#과 떠다니는 소수점 이야기[3]

오래 기다리셨습니다. 아니, 아무도 안 기다리셨으려나? 뭐, 상관 없습니다. 저는 꿋꿋이 글을 써 나가겠습니다.


이제는 우리가 C# 코드에서 부동 소수점 자료형을 사용할 때 생각해야 하는 부분들에 이야기하려 합니다. 이것들은 C# 뿐 아니라 IEEE 754 규격을 따르는 모든 시스템에 해당 되는 내용입니다.(대체 어떤 이야기길래?)

float a = 0.1f 0.2f;


위 코드에서 a는 얼마가 될까요? 당연히 -0.1이겠지요? 그럼 다음 코드를 봅시다.

float b = 43.1f - 43.2f;


위 코드에서 b는 얼마가 될까요? 하핫, 여러분을 바보로 아냐고요? 아 죄송합니다. 제가 바보 같은 질문을 했습니다. 그럼 다음 코드를 작성해서 한번 실행해 보시기 바랍니다.

using System;

class FloatEx01

{

  static void Main(string[] args)

  {

       float a = 0.1f - 0.2f;

       Console.WriteLine(a);

       PrintOutInHex(a);

       float b = 43.1f - 43.2f;

       Console.WriteLine(b);

       PrintOutInHex(b);

  }

  private static void PrintOutInHex(float value)

  {

       byte[] bytes = BitConverter.GetBytes(value);

       foreach (byte b in bytes)

       {

           Console.Write("{0:X2} ", b);

       }

       Console.WriteLine();

  }

}

결과 :

-0.1

CD CC CC BD

-0.1000023

00 CE CC BD


, 이런. 이게 무슨 일이지! 내가 프로그래밍 언어를 잘못 선택한 게 아닌가?!. 진정하세요. 이것은 IEEE 754를 따르는 모든 시스템에서 나오는 문제입니다.


앞에서 설명한 것처럼 2진수로는 소수를 모두 표현할 수가 없습니다. 예를 들어 0.1을 이진수로 바꾸면 0.00011001100 의 순환 소수가 되지요. 게다가 우리는 한정된 비트 안에 이 수를 표현해야 하므로 비트 크기를 벗어나는 나머지 수들은 저장되지 못하게 됩니다. 그렇다고 나머지 수들을 버릴 수도 없기에 이 값들을 반올림해서 저장합니다. 따라서 우리가 얻을 수 있는 것은 정확한이 아닌 가까운 값 뿐입니다. 이로 인해 위와 같은 현상이 나타나는 것이죠. 이것을 반올림 오류(round off error)라고 합니다.


이를 어찌해야 할까요? 위 예제 코드에서 float으로 사용된 변수를 double로 바꿔 보면 오차가 훨씬 줄어드는 것을 확인할 수 있습니다.

0.1

9A 99 99 99 99 99 B9 3F

0.100000000000001

00 9A 99 99 99 99 B9 3F


그래도 여전히 오차가 존재합니다만, 이 정도면 그래도 꽤 쓸만해 졌습니다. double 형은 64비트(8바이트)의 자료형으로써 11비트의 지수부와 52비트의 가수부로 이루어져 있기 때문에 float 보다 정확한 값을 나타낼 수 있습니다.


double은 8바이트 자료형이니 float 보다 느려지지 않겠냐구요? 두 자료형의 처리 속도는 거의 차이가 없습니다. 메모리를 두 배나 사용하긴 하지만 여러분의 프로그램이 동작해야 하는 PC가 충분한 메모리를 갖고 있다면 double을 사용하는 것이 정신 건강에 이로울 것입니다.

다만, 여전히 아래의 코드는 여전히 실망스러운 결과를 낳죠. not equal을 출력합니다.

double a = 0.2 - 0.1;

double b = 43.2 - 43.1;

if (a == b)

  Console.WriteLine("equal");

else

  Console.WriteLine("not equal");


부동 소수점 자료형을 직접 비교한다는 것은 위험한 일입니다. 정 비교가 필요하다면 오차 허용치를 이용하여 근사값끼리 비교하는 방법이 있습니다. 아래와 같이 말이죠.

const double EPSILON = 0.0001; // 허용오차

private bool isEqual(double x, double y) // 비교 함수.

{

  return (((x - EPSILON) < y) && (y < (x + EPSILON)));

}

//

if (isEqual(a,b))

Console.WriteLine("equal");

else

  Console.WriteLine("not equal");


컴퓨터가 소수 하나도 정확하게 처리를 못한다는 것은 정말 슬픈 일입니다. 그러나 이것은 현재 CPU의 구조상 어쩔 수 없는 일입니다. 우리가 할 수 있는 것은 최대한 정확하게 데이터를 처리하기 위해 float 대신 double을 사용하는 것과, 이 오류와 관련해서 생길 수 있는 프로그램의 영향을 줄이는 것뿐입니다.

하지만 C#은 또 하나의 해결책을 제시합니다. 바로 decimal형입니다. 이 자료형은 부동 소수형이 아니며, 29 자리수의 수를 지원합니다. 소수점은 29자리 안에서 이동합니다. 반올림 오류가 일어나서는 안 되는 재부/금융/회계 분야에 적합한 자료형입니다. 다만 그 크기가 16바이트나 되고 부동 소수점 자료형보다 속도가 훨씬 느리다는 단점이 있습니다. 이러한 Trade-Off는 어쩔 수 없습니다. 정확한 계산을 위해서라면 속도와 메모리는 조금 희생해야죠. ^^;

, 이렇게 해서 부동 소수점에 대해 이야기를 나눠봤습니다. 역시나 재미가 없죠? 다음에는 비교에 대해 이야기를 해볼까 합니다. 그럼 즐프하세요~


(이 포스트는 추후 수정될 수도 있습니다.)

2006년 7월 13일 목요일

준비해야 할 포스트..

1. 떠다니는 소수점 이야기.. 완결편
2. 비교에 대하여.
3. Endian에 대하여.
4. 그 담은 계속 생각해 보겠음...

... 출판을 목적으로 하고 있진 않지만, "클릭~ C# 2.0" 돌자들이 "내공"을 다질 수 있는 내용으로 하려고 합니다.

외공 중심의 무술은 일단 당장 실전에 사용이 가능하고 빠른 시간 안에 일정 수준까지 올라갈 수 있다는 장점이 있지만, 내공 중심의 무술은 성장이 느린 반면 어느 수준에 이르면 외공 중심의 무술보다 더 강하다죠... (이건 무협영화를 보면서 내린 결론입니다. 믿거나 말거나 ㅋㅋ)

당연히 재미는 별로 없을거라 생각하지만 연재 주기가 일정치 않고 업데이트가 매우 느리므로 공부하는 데에도 부담이 없으리라 봅니다.(음.. 단점을 마치 장점처럼..)

원래 오늘 부동 소수점 이야기를 올리려 했는데 주 초부터 철야를 하는 바람에 늦잠을 자서 못 올렸습니다.
그래서... 쓸데 없는 이야기를 좀 끄적였습니다. ^^;;

좋은 하루 보내세요!

2006년 7월 11일 화요일

MBTI 유형 검사 결과

MBTI 유형 검사를 해봤더니 전 ENTJ형이랍니다. 난 이게 무슨 "혈액형별 성격"같은 건 줄 알았는데, 좀 다르군요. 사람의 성격을 잘 반영하는 것 같습니다.

ENTJ 유형의 사람들은 일반적으로 책임을 받아들이고 문제를 직접적으로 다루며, 특히 혼란이나 비효율성과 관련된 상황에서 직접적으로 문제를 처리한다.

이들은 자신이 속한 조직체에 구조를 제공하며 자신의 개인적인 목표와 자신의 속한 조직의 목표를 달성하기 위해 전략들을 고안해 낸다.

이들은 광범위하고 행동지향적인 계획들을 전개하며, 이러한 계획들이 완성될 때까지 필요한 에너지와 힘을 제공한다. ENTJ 유형의 사람들은 "책임을 지는" 사람들이며 자신 뿐만 아니라 다른 사람들의 외부 환경까지도 조직화하는 사람들이다.

이들은 "아니오"라는 대답을 하지 않는다. 그 대신 이들은 그런 도전을 극복할 수 있는 방식을 찾기 위해 자신이 지니고 있는 모든 정보나 자원들을 활용한다.

이들은 자신이 지닌 분석적이고 전략적이 사고과정을 사용할 수 있는 사용함에서 최상의 능력을 보인다.

단점 역시 존재하는데

* 요구하거나 비판적이거나 협박할 수 있다.
* 일을 삶의 다른 부분에까지 옮겨놓을 수 있다.
* 성급히 결정하느라 중요한 사실과 세부사항을 간과할 수 있다.
* 다른 사람의 투입과 공헌을 요구하거나 허용하지 않을 수 있다.

라는 군요.. -.-; 장점들은 "오, 그렇지. 나랑 일치하잖아"라면서 받아들이면서 단점들은 받아들이기가 힘들군요 ^^;; 단점 역시 그런대로 제가 생각하던 제 단점이랑 일치하는 것으로 보입니다.

이 결과가 실제의 나라고 하더라도 "난 이런 사람이야" 라고 이 검사결과를 받아들이기 보다는 변해가는 과정중에 있는 한 때의 캐릭터로 받아들이려 합니다. 10년 전의 나는 지금의 내가 아니었던 것처럼, 앞으로 5년 후 10년 후엔 지금의 나와는 다른 모습을 가질 수도 있을 것이기 때문입니다.

지금도 한창 클 나이이므로.. ㅋㅋㅋ

2006년 7월 9일 일요일

C#과 떠다니는 소수점 이야기[2]

이제 제가 하고자 하는 이야기를 할 수 있게 됐습니다.

C#
이 제공하는 float double 부동 소수점 자료형은 IEEE 754 규격을 따릅니다. IEEE 754가 뭔고 하니, 4바이트(32비트) 8바이트(64비트)를 이용하여 부동소수점을 표현하는 방식을 IEEE라는 기관에서 정의한 규격입니다.

이 규격에 따르면 부동소수점은 부호 비트, 지수, 가수로 나누어 표현되며, 4바이트 부동 소수점의 경우 아래의 그림과 같이 부호가 1비트, 지수가 8비트, 가수가 23비트를 사용합니다.









(
그림 1 : 32bit 부동 소수점 자료형의 구조)


부호 비트는 수가 음수인지 양수인지를 구분하기 위해 사용합니다. 0이면 양수, 1이면 음수죠. 지수는 소수점의 위치를 가리키기 위해 사용되고, 가수는 정규화된 값이 담깁니다.


좀 어렵죠? (, 사실 머리에 쏙 들어오는 그런 내용은 아닙니다. 하지만 이 규격을 만든 사람들도 고민을 엄청나게 많이 했습니다. 공부하는 우리는 고생이 덜한 편이라는 것에 위안을 가집시다.)


부호 비트는 명확하니 설명이 더 필요 없을 것 같고, 지수와 가수에 대해 이야기를 좀 해보겠습니다.

우선 가수에 대해 생각해 봅시다. 2진수는 항상 1로 시작합니다. 1001, 11, 1111, 111111 등등 확실히 2진수는 0으로 시작할 수는 없습니다. 10진수나 16진수라면 경우의 수가 많아지지만 2진수는 0이 아니면 1이기 때문에 항상 1로 시작된다는 것을 확신할 수 있습니다.


그래서 가수부의 비트에는 시작하는 1을 제외한 나머지 값만 저장됩니다. 이것으로 23비트만으로 24비트를 담는 효과를 내게 해 주죠. 한편, 가수는 1.xxxxxxx의 값으로 저장됩니다. 예를 들어 11.11 2진수는 가수부에 1.111, 101.01 1.0101로 저장됩니다.
시작하는 1을 제외한 나머지 값만 담기므로 가수부의 비트에 담기는 값은 11.11의 경우는 .111, 101.01의 경우는 .0101만 담기게 됩니다. 이 과정을 일정한 형식으로 바꾸는 정규화(normalization)라고 합니다.


예를 들어 7.25를 정규화하여 가수 비트에 담아봅시다. 7 2진수로 111, 0.25 0.01이니 합치면 111.01이 됩니다. 이를 정규화하면 1.1101이 되죠. 시작되는 1은 제거하고 담는다고 했죠? 그럼 1101만 가수 비트에 담으면 됩니다
.








(
그림 2: 저장된 가수의 모습)


이렇게 해서 가수를 담았습니다. 그런데 소수점의 위치는 상실했습니다. 7.25 111.01(2진수)인데, 1.1101로 저장됐으니 어떻게 이를 복원하죠? 이 문제를 위해 지수가 사용됩니다. (지수가 여자 이름이 아닙니...... 쿨럭;)


지수는 수의 규모를 파악하는 데 유용하게 사용됩니다. 예를 들어 10진수의 경우 72.18 * 102 7218이 되고, 2진수의 경우 1.01 * 22 101이 됩니다. 앞서 잃어버린 소수점의 위치는 이러한 지수의 속성을 이용하여 되찾을 수 있습니다.


한편 지수가 담기는 비트 역시 성능을 위한 고민의 산물입니다. 32비트(4바이트) 부동 소수 자료형의 경우 지수는 126 부터+127 까지의 값을 가질 수 있는데, 2의 보수로 이 값을 표현하면 비교 연산이 엄청 복잡해 집니다(이미 부호가 있는 자료형 안에 또 부호가 들어가 있습니다. 게다가 이 값은 지수라구요.) 연산이 복잡해지면 성능 또한 저하되는 것은 자명하지요.
그래서 지수도 실제 값에 127을 더해 저장하는 바이어싱(biasing)을 사용합니다. 결국 지수부에 담기는 비트에는 0보다 큰 양수만 담깁니다.


조금 전에 7.25의 가수를 비트에 저장했었는데, 이번에는 지수를 담아보겠습니다. 7.25 2진수 111.01 1.1101 * 22로 표현할 수 있으니, 지수는 2입니다. 그런데 실제로 비트에 담을 때는 지수에 127을 더하므로 129를 저장하면 됩니다. 129 2진수로 변환하면 10000001이 되고 이를 지수 비트에 저장하면 다음과 같습니다










(그림 3: 저장된 지수와 가수의 모습)


7.25
의 부호 비트 값은 0이므로 7.25 float 에 담긴 비트의 모습은 최종적으로 다음과 같습니다.










(
그림 4: 7.25 32비트 부동 소수형 비트에 저장된 모습)


제대로 저장됐는지 확인해 볼까요? 저장된 비트 값을 다음 공식을 이용하여 복원해 보면 됩니다.


(-1)부호 * 2(지수 127) * (1+가수
) = 1 0* 2(129 127) * (1+0.1101) = 111.01(2진수) = 7.25(10진수)

이렇게 해서 IEEE 754 규격에 대해 알아봤습니다. 아직 C# 코드는 한 줄도 안 나왔는데 엄청 이야기가 길어졌습니다. 남은 이야기는 다음 포스트에 계속됩니다. ^^

( 이 포스트는 추후 수정될 수 있습니다.)

2006년 7월 7일 금요일

우연히 검색하다가...

오늘 뭔 기대를 했는지 몰라도 네이버에서 제 이름 "박상현"으로 검색을 해봤습니다. 밑에 보니 "클릭하세요 C# 2.0 프로그래밍"이 이더군요. 클릭해서 들어가 봤드니 7월 1주 C# 분야 1위에 올라 있더군요. 이런 영광이..ㅠ,ㅠ

원래 순위라는 게 올라갔다가도 얼마든지 내려갈 수 있기 때문에 얼른 캡쳐해서 가져왔습니다. 1위에 랭크된 것 보다도 이런 희귀한(?) 장면을 캡쳐할 수 있었다는 게 너무 기분 좋았습니다. ㅋㅋ

좋은 주말 보내세요~ ^^

2006년 7월 6일 목요일

C#과 떠다니는 소수점 이야기[1]

"C#의 음수 표현 방식에 대한 이야기"를 읽어본 독자들은 1바이트에 담긴 아름다움을 느끼실 수 있었으리라 생각합니다. (안 읽어봤다면 읽어보세요 클릭!)

이번에는 float과 double과 같은 부동 소수점(Floating Point) 자료형이 어떻게 구성되는가와 이들을 사용할 때 생각해야 되는 부분들에 대해 이야기를 나누고자 합니다.

다른 설명을 시작하기에 앞서 여러분이 한가지 알아둬야 할 것이 있습니다. 바로 10진수를 어떻게 2진수로 표현하는가입니다. 사실 이런 내용을 몰라도 프로그래밍을 잘하는 분들이 많습니다만 공부하는 데 돈이 드는 것도 아니니 조금 시간과 노력을 투자해서 알아둡시다. 게다가 별로 어렵지도 않습니다.

우선 정수를 2진수로 변환하는 방법은 수를 2로 나눠 나머지를 기록하고, 2보다 작은 수가 되어 더 이상 나눌 수 없을 때 계산을 종료하여 기록한 나머지를 밑에서부터 읽어나가는 것입니다.

예를 들어 123을 2진수로 표현하면 다음과 같습니다.

     몫    나머지
123   61    1
61    30    1
30    15    0
15    7     1
7     3     1
3     1     1
1     0     1

123(10진수) = 1111011(2진수)

어렵지 않죠? 이번엔 10진수의 소수를 2진수로 바꾸는 방법에 대해 설명하겠습니다.

소수를 2진수로 바꾸려면 해당 소수에 2를 곱해서 그 결과가 0보다 작으면 0을 기록하고, 1보다 크면 1을 기록한 뒤 결과의 정수 부분을 버리고 소수부분에 다시 2를 곱해 나가는 방식으로 계산하면 됩니다. 예로 10진수의 0.25를 2진수로 바꿔보겠습니다.
    
     결과    0보다큰가?   2진수
0.25  0.5    X           0.0
0.5   1.0    X           0.01 <-- 1.0의 정수 부분 1을 버리면 0만 남으므로 계산끝

또 다른 예를 들어볼까요? 이번에는 0.12를 2진수로 바꿔 보겠습니다.

     결과    1보다큰가?    2진수
0.12  0.24    X           0.0
0.24  0.48    X           0.01
0.48  0.96    X           0.000
0.96  1.92    O           0.0001    <-- 1.92에서 정수 부분 1을 버린다.
0.92  1.84    O           0.00011   <-- 1.84에서 정수 부분 1을 버린다.
0.84  1.68    O           0.000111   <-- 1.68에서 정수 부분 1을 버린다.
0.68  1.36    O           0.0001111  <-- 1.36에서 정수 부분 1을 버린다.
0.36  0.72    X           0.00011110
0.72  1.44    X           0.000111101 <-- 1.44에서 정수 부분 1을 버린다.
....

0.12는 끝이 안나는 군요. ^^; 0.12를 끝까지 계산해보진 않았지만, 만약 결과가 0이 되지 않는다면 끝임없이 2진수 변환을 진행하게 됩니다. 이것은 2진수가 가진 한계 때문입니다. 마치 분수 1/3을 소수로 표현하면 0.33333333333333333333333333333333333333...이 되는 것처럼 말이죠. 다행히도, float는 4byte, double은 8byte안에서 수를 표현해야 하므로 무한대로 계산하지는 않습니다. "부동 소수점 자료형의 "정밀도(Precision)"라는 속성이 어떤 것인지 좀 감이 잡히시죠?

... 다음에 계속 이어집니다. ( 이 포스트는 추후 수정될 수 있습니다.)

2006년 7월 3일 월요일

7월입니다!

벌써 7월달이 됐습니다. 2006년도 이제 꺾이기 시작하는군요.^^

요 근래에 약 8개월 동안 준비해온 BMT도 1위로 잘 끝내서 우리회사가 납품 대상자로 선정되되는 일이 있었고, C++ 원고도 마무리해서 출판사로 넘어갔습니다.
여전히 바쁘긴 하지만 큰 일들이 끝나서 그런지 심리적 여유가 조금은 생겨서 이것 저것 생각을 많이 하고 있습니다. 제일 많이 하는 고민은 "다음 포스트엔 어떤 글을 올릴까"이죠.(다음 C# 포스트는 "C#의 부동 소수점 이야기"가 될 것입니다.) ㅋㅋㅋ
그 다음 고민(?)들은 미리 이야기할 수 있는 것들이 아니라 지금 말씀 드리진 못하지만, 다음에 고민이 정리되는대로(궁금하죠? 별 거 없습니다. ㅋㅋ) 알려드리겠습니다.

어쨌거나 한주가 또 시작됐습니다. 모두들 즐프하세요! ^^

(아래는 글이 썰렁해서 넣어본 그림입니다. 제가 재작년에 슥슥 끄적여 놓은 게 컴터에 있더군요^^)