안녕하세요. 개발자 모도리입니다.
이번에는 잠시 다른 주제를 다뤄 보겠습니다. (EOS Dapp 개발 공부를 못하고 있습니다.) 부동 소수점이란 무엇인가 알아보도록 하겠습니다. 이 주제 선택한 이유는 @creamer7님께서 작성하셨던 [EOS 속보] 댄으로 부터 온 메세지 + 댄이 직접 대답하는 Q & A 의 댓글에서 시작되었습니다.^^;
전반적인 내용을 다 다루려면 분량이 너무 길어 질 것 같아서, 음수 부분은 생략하고 양수에 대한 부분만 작성하겠습니다.
정수를 표현하는 방식
일반적으로 사람이 표현하는 숫자 123을 표현하는 방식에는 여러가지가 있습니다. 우리가 일상적으로 사용하는 숫자 표현방식은 10진수입니다. 123을 여러가지 표현방식으로 나타내면 아래와 같습니다. 자세한 진법 변환 방법은 아래 참고 자료에 첨부하겠습니다.
- 16진수 : 7B
- 10진수 : 123
- 8진수 : 173
- 2진수 : 1111011
컴퓨터가 실수를 표현하는 방식
2진수 표현
컴퓨터는 숫자를 표현할 때 기본적으로 2진수를 사용합니다.
아래 표를 참고해서 숫자를 나타내 보면
13 = 8 + 4 + 1 이므로 해당 자리 숫자를 1로 표현하고 나머지는 0으로 표현합니다. 그러면 1101이 됩니다.
0.75 = 0.5 + 0.25 이므로 0.11 로 표현할 수 있습니다.
이렇게 쉽게 끝나면 얼마나 좋을까요! ㅠㅠ
263.3 같은 실수를 2진수로 표현해 보면
(상세 방법은 유튜브 영상에 자세히 나와 있습니다.)
- 263 => 100000111
- 0.3 => 0.01001100110011......(0011)의 무한 반복입니다.
이렇게 2진수로 표현하지 못하는 소수가 발생합니다. 어쩔 수 없이 컴퓨터에는 표현할 수 있는 가장 근사치의 값이 저장됩니다.
이 근사 값을 저장하는 방법에는 두 가지가 있습니다.
고정 소수점
- 정수를 표현하는 비트 수와 소수를 표현하는 비트 수를 미리 정해 놓고 해당 비트 만큼만 사용해서 숫자를 표현하는 방식
- 예) 실수 표현에 4byte(32bit)를 사용하고 그 중 부호(아래에서 괄호로 표시) 1bit, 정수 16bit, 소수 15bit를 사용하도록 약속해 놓은 시스템에 있다고 가정합니다. 이렇게 약속 된 시스템에서 263.3을 표현하면 (0)0000000100000111.010011001100110 이렇게 표현됩니다.
- 정수를 표현하는 bit를 늘리면 큰 숫자를 표현할 수 있지만 정밀한 숫자를 표현하긴 힘듭니다. 그래서 소수를 표현하는 bit를 늘릴 경우 정밀한 숫자를 표현할 수 있지만 큰 숫자를 표현하지 못합니다.
이런 문제를 해결하기 위해서 소수점을 고정하지 않고 둥둥 떠 다닐 수 있게 하는 부동 소수점(floating point)을 사용하고 있습니다.
부동 소수점
부동 소수점을 표현하는 방식도 정하는 방식에 따라 다를 수 있지만 일반적으로 사용하고 있는 방식은 IEEE에서 표준으로 제안한 방식입니다.
우선 고정 소수점으로 나타낸 263.3을 2진수 부동 소수점 방식으로 변환해 보겠습니다. 100000111.010011001100110... 으로 표현되던 것을 맨 앞에 있는 1 바로 뒤로 소수점을 옮겨서 표현하도록 변환합니다. 그러면 1.00000111010011001100110... * 2^8(2의 8승) 으로 표현 됩니다.
- 2^8의 8을 지수라고 하고 하늘색 부분에 기록합니다. (IEEE 754 표현 방식에서는 127 + 지수를 기록합니다.)
- 소수점 이후 숫자열 전체를 가수라고 하고 연두색 부분에 기록합니다.
이 방식에 따라서 263.3을 기록하면
- 부호 비트(1 bit) : 0 (양수)
- 지수 비트(8 bit) : 10000111 (127 + 8 = 135)
- 가수 비트(23 bit) : 00000111010011001100110
이렇게 표현할 수 있습니다.
하지만! 여기서도 0.010011001100110은 정확히 0.3을 나타낼 수는 없습니다. 10진수로 나타내 보면 0.29998779296875을 나타냅니다.
부동 소수점 오류 예
0.1을 100번 더하면 10이 나와야 되는데...
0.1을 정확히 표현할 수 없기 때문에 발생하는 오류입니다.
JAVA
Python
C
만약 이렇게 오류가 발생할 수 밖에 없는 부동소수점 연산을 블록체인 상에서 사용한다면 어떤 일이 벌어질까요? Token Decimal이 18자리인 EOS의 경우 0.3 EOS를 전송했는데, 갑자기 중간에 0.0000122070312499889 EOS가 훅~ 증발해 버립니다. ㅠㅠ
그래서 이런 일이 일어난 것을 방지하기 위해서 블록체인 구현 시 floating point 연산 지원을 하지 않습니다. 그럼 어떻게 소수점 단위로 코인(토큰)을 주고 받을 수 있을까요?
블록체인 구현 시 부동 소수점 처리
비트코인
- 분할 가능 범위 : 소수점 아래 8자리
- 0.00000001 BTC = 1 Satoshi
- 1 BTC = 100000000 Satoshi
이더리움
- 분할 가능 범위 : 소수점 아래 18자리
- 0.000000000000000001 Ether = 1 Wei
- 1 Ether = 100000000000000000 Wei
참고로 Gas비 계산에 사용되는 Gwei는
- 1 Gwei = 1000000000 Wei
입니다.
이오스
지난 번 포스팅
[EOS] Smart Contract 개발 준비 3 - 샘플 스마트 컨트랙트 실행
에서 샘플컨트랙트 실행 과정에서 1000.0000 CUR를 초기 발행했는데, balance를 확인해 보면 10000000 이라고 찍혀 있는 것을 확인하셨을 겁니다.
혹시 감이 오시나요??
사실 실제로 내부적으로 거래가 일어날 때에는 해당 코인의 가장 작은 단위를 이용해서 실수 연산이 아닌 단순 정수 연산을 수행하고 있었습니다. 사용자가 0.00000001 BTC를 전송할 경우 transaction에 0.00000001 BTC가 실려 가는 것이 아니라 그냥 1 Satoshi이 실려 가는 겁니다. 그래서 부동 소수점으로 인한 오류를 신경쓰지 않아도 되는 것이고요.
물론 서버간의 통신에서는 문제가 없겠지만, 다른 3rd party 클라이언트에서 이런 문제를 인식하지 못하고 그냥 구현하게 될 경우 발생하는 문제를 A note on numbers in Ethereum and Javascript에서 다루고 있습니다.
@creamer7님과@sirin418님 덕분에 몰랐던 사실을 알게되었네요. 감사합니다.^^
다음에는 다시 EOS Dapp 개발 포스팅으로 돌아오겠습니다~!!
참고 자료
- 진법변환 방법
- How can I represent decimal values in Solidity?
- Bitcoin and floating point accuracy?
- Integer or float used in Bitcoin?
- Floating point inaccuracy examples
- Float not allowed in solidity vs decimal places asked for token contract
- [c언어] 실수 표현 문제 발생 이유 or 오차 발생 이유!
- [머구리씨] C언어-08고정소수점(fixed-point)과 부동소수점(floating-point) 표현방식을 구별하자. 정규화
- Decimal to IEEE 754 Floating Point Representation
- A note on numbers in Ethereum and Javascript
- Inside an Ethereum transaction
쉽고 친절한 설명 감사드립니다 :)
더 쉽게 말하면 부동소수점으로 인해 더 정확한 처리가 가능해졌다고 생각하면 되겠죠?
읽어 주셔서 감사합니다. 쉽게 써보려 노력했는데, 쉽지가 않네요 ㅋ
스마트 컨트랙트 구현이 조금 더 편리해(쉬워)졌다? 정도 일 것 같네요.^^
친절한 설명 감사합니다.
한가지 의문이 있는데 고정소수점, 부동소수점 모두 움직이지 않는 느낌인데 floating point를 왜 부동소수점으로 번역했는지도 혹시 아시나요?
정 반대뜻처럼 보여서요 ^^
급하게 설명하다 보니 그 부분이 잘 설명되지 않았네요. 본문의 예로 263.3 이라는 숫자를 정수 부분과 소수 부분으로 이뤄진 고정 소수점 표현으로 나타내려면 오로지 263.3 한 가지 표현 밖에 없습니다. 그런데 지수, 가수를 이용해서는 2.633 * 10^2 / 26.33 * 10^1 / 2633 * 10^-1 이런식으로 소수점을 옮겨가며 표현할 수 있기 때문에 부동 소수점 표현이라고 합니다. 그런데 이것을 컴퓨터에서 표현하기 위해서 약속된 것은 맨 앞에 1자리(2진수로 표현했을 때 1)만 놔두고 나머진 다 소수점 이하로 내려서(정규화해서) 표현하자라고 정하여서... 마치 고정된 것 처럼 느끼셨을 것 같네요. 이 내용은 참고 자료 중에 [머구리씨] C언어-08고정소수점(fixed-point)과 부동소수점(floating-point) 표현방식을 구별하자. 정규화 유튜브 영상에 자세히 나와 있습니다. ^^
잘 읽었습니다. 제 질문을 포스팅으로 바꿔서 해주시다니, 감동입니다 ㅠㅠ
아닙니다~ ㅋ 즉시 답변 드릴 수 있었으면 더 좋았는데, 저도 겉핥기식으로 알고 있던 것 이번 기회에 제대로 공부했습니다!
대충 지수를 이용해서 연산한다는것만 알고있었는데 더확실하게 알게 되었네요 감사합니다!!~
도움 되셨다니 기쁘네요~! 읽어 주셔서 감사합니다! ^^
ERC20 토큰의 경우 소수점 이하 18자리를 사용하고, 개발 문서에는 아래 처럼 명시되어 있네요.
https://github.com/EOSIO/eos/wiki/Glossary
참고로 EOS에서는
1 larimer = 0.0001 EOS 로 정의 되어 있네요.
1 satoshi 처럼 댄 라리머 자신의 이름을 썼네요. 뭔가 귀요미 느낌 ㅋㅋ