Java의 정석 제 2장-진법
시작 전 바로가기
자바의 정석 1장 정리 바로가기
자바의 정석 2장 변수 바로가기
이번 챕터의 경우 제가 기존에 알던 내용들은 생략하거나 간단하게 넘어갔음에도 불구하고, 너무 내용이 길어져서 두 개의 게시물로 나누어 작성하겠습니다.
진법
2진수는 10진수와 같은 숫자를 표기하기 위해서는 훨씬 더 많은 자릿수를 요한다.
그렇지만, 2진수는 10진수를 온전히 표현할 수 있으며 덧셈이나 뺄셈 같은 연산도 10진수와 동일하게 계산 가능하다.
비트와 바이트
한 자리의 2진수는 비트라고 부르며 이 1비트는 컴퓨터가 값을 저장할 수 있는 최소한의 단위이다.
1비트를 8개 묶으면 바이트라고 불리는데, 바이트는 데이터의 기본 단위로 사용된다.
워드의 크기는 CPU의 크기에 따라 다른데 32비트 CPU에서는 4바이트가 1워드이며.
64비트 CPU에서는 8비트가 1워드이다.
8진법과 16진법
앞서 말했다시피 2진법은 숫자의 자릿수가 지나치게 늘어나는 문제가 생긴다.
그 묹를 부완하기 위해 8진법이나 16진법이 사용되기 시작하였다.
이 둘은 자리수가 짧아질뿐 아니라 서로 간의 변환 방법 또한 매우 간단하다는 장점이 있다.
변환하기
Java에서는 10진수를 입력하고 Integer.toBinaryString(decimal)-2진법
, Integer.toOctalString(decimal);-8진법
, ` Integer.toHexString(decimal);-16진법`으로 간단하게 작성하면 된다.
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int n;
string s;
void convert_binary(int n) {
while (n > 0) {
if (n % 2 == 1)
s += '1';
else
s += '0';
n /= 2;
}
}
void convert_eight(int n) {
while(n > 0) {
s += to_string(n % 8);
n /= 8;
}
}
void convert_hex(int n) {
s.clear();
while (n > 0) {
int remainder = n % 16;
if (remainder < 10) {
s += remainder + '0';
}
else {
s += remainder - 10 + 'A';
}
n /= 16;
}
}
int main() {
cin >> n;
convert_binary(n);
reverse(s.begin(), s.end());
cout << s << "\n";
s.clear();
convert_eight(n);
reverse(s.begin(), s.end());
cout << s << "\n";
convert_hex(n);
cout << s << "\n";
}
사실 더 간단한 방법이 충분히 있을 거 같은데, 나는 최대한 원리가 드러나도록 c++로 구현을 해보았다.
실수의 진법 변환
10진 소수점수를 2진 소수점수로 변환을 하기 위해서는 소수점수에 2를 계속 곱한다.
이 과정을 소수부가 0이 될 때까지 해야하며, 위의 결과에 정수부만 위에서 아래로 순서대로 적은 뒤 ‘0.’을 앞에 붙이면 된다.
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
string convert_fraction_to_binary(double fraction) {
string result = "0.";
while (fraction > 0) {
fraction *= 2;
result += to_string(static_cast<int>(fraction));
fraction -= floor(fraction);
}
return result.empty() ? "" : result;
}
int main() {
double input;
cin >> input;
int integer_part =floor(input);
double fraction_part = input - integer_part;
string binary_fraction_part = convert_fraction_to_binary(fraction_part);
cout << "Binary: " << binary_fraction_part << endl;
return 0;
}
result에 ` 0.`을 추가한 이유는 일단 첫번째 예제가 정수부가 0일때가 기준이었기 때문이다.
만약 정수부가 0이 아니라면 정수부 따로 소수부 따로 변환한 뒤에 더해주면 된다.
기본형
논리형
논리형에는 boolean타입 하나가 들어가는데 기본값은 false
이다.
자바에서 다루는 최소 단위가 바이트여서 1바이트 크기를 가지며, 스위치 등의 논리 구현에서 자주 사용된다.
문자형
문자형은 char 하나뿐이다. 묹를 저장하기 위한 변수를 선언할 때 사용되는데 단 하나의 문자만 사용 가능하다.
또한 문자의 유니코드 정수가 저장되기 때문에
public class Main {
public static void main(String[] args) {
int asciiValue = (int) 'A';
System.out.println("ASCII value of A is " + asciiValue);
}
}
이런 식으로 사용하면 직접 ‘A’의 아스키코드 값을 구할 수 있다.
또한 char ch=A
대신 직접 문자의 유니코드를 저장하는 방식 역시 가능하다.
특이점
char타입에는 문자의 아스키 코드 값이 저장되니 정수형과 동일하게 표현된다.
그러나, 문자에는 음수가 포함이 될 필요가 없기 때문에 0~65535
의 범위를 가지게 된다.
또한 값은 해석의 단계가 중요하기 때문에 아까처럼 ‘A’를 int로 변환하면 65가 출력되지만
저 단계 없이 char ch='A'
로 작성하게 되면 A가 출력되게 된다.
이를 통해 컴퓨터에는 문자열이 숫자로 저장되지만, 특별한 일이 없으면 출력 시에는 그 저장된 아스키 코드 번호를 기반으로 다시 문자로 번역하는 과정을 거친다는 것을 알 수 있다.
정수형
정수형은 생각보다 종류가 많다. 4개의 종류로 각각
byte/short/int/long
으로 이루어져있고 오른쪽으로 올 수록 단위가 1부터 2개씩 증가한다.
정수형을 선택할 때에는 크기가 작더라도 int의 사용을 권한다.
int보다 작은 바이트나 short의 경우 범위를 넘어서는 오버 플로우가 뜨기 쉬우며
JVM의 피연산자 스택은 피연산자를 4바이트로 어차피 변환하기 때문에 int를 사용하는 것이 효율적이다.
20억을 넘어서는 숫자를 다룰 때에는 long을 사용해야하고, 만약 저장공간을 절약해하는 상황이라면 byte나 short를 사용한다.
오버플로우란
원인: 타입이 표현할 수 있는 값의 범위를 넘어서는 것
에러 발생의 개념이 아니라 예상했던 결과를 얻지 못하게 된다.
예방법으로는 당연 값의 범위를 잘 타겟하고 그에 따른 자료형을 선택하는 방법이 있다.
결과: 최대값에 1을 더하면 범위를 넘엇는 것이기 때문에 최소값으로 넘어가게 되고
최소값에서 1을 빼버리면 최대값으로 넘어가버린다.
간단한 예시로는 TV리모컨으로 채널을 돌리는 작업을 떠올리면 된다.
부호가 있는 정수의 오버플로우
부호가 있고 없고에 따라 최대값과 최소값 역시 다르기 때문에 오버플로우의 발생 시점도 다른다.
부호가 없는 정수의 경우 표현범위가 0-15
이며 오버플로우 발생 시 이 값이 계속 반복된다.
부호가 있는 정수의 경우 표현 밤위가 -8~7
이므로 이 값이 무한하게 반복된다.
실수형
실수형에는 float과 double이 있으며 정밀도와 크기가 다르다.
double의 경우 15자리까지 정밀성을 보장하며 크기는 64비트이다.
float의 경우 7자리까지 정밀성을 보장하며 크기는 32비트이다.
실수형은 소수점도 표현해야하기 때문에 정수형처럼 얼마나 큰 수를 표현할 수 있는가를 고려함과 동시에
얼마나 0에 가깝게 표현이 가능한지까지 고려해야한다.
그렇기 때문에 오버플로우가 아닌 아주 작은 값으로 인해 일어나는, 언더플로우라는 오류도 있다.
언제 뭘 쓸까
연산속도의 향상이나 메모리 절약을 하려면 float를 선택하는 것이 좋다.
더 큰 값의 범위가 필요하거나 높은 정밀도를 요햔다면 double을 선택해야한다.
저장방식
실수형과 표현방식이 달라서, 실수형은 값을 부동소수점수의 형태로 저장한다.
부동소수점수는 부호,지수,가수의 세 부분으로 이루어져있다.
부호
0이면 양수 1이면 음수
양의 실수를 음의 실수로 바꾸려면 그저 부호 비트만 0에서 1로 변경하면 된다.
지수
부호가 있는 정수. 지수의 범위는 -127~128(float)
,-1023~1024(double)
이다.
가수
실제값을 저장하는 부분으로 아까 말한 정밀도와 연관된다.
부동소수점의 오차
무한소수의 존재 및 지나치게 작은 소수 떄문에 저장시 오차가 발생할 수 있다.
가수부가 표현 가능한 비트수를 넘어가면 손실 부분이 생기게 된다.
대신 표현을 할 수 있는 범위는 늘어나게 되었다는 장점이 있다.
형변환(casting)
서로 다른 타입간의 연산 수행을 위해 다른 타입을 변환하는 일
형변환 방법
` (타입)피연산자`의 형태로 사용한다.
이때 피연산자의 값은 변수의 형변환 후에도 손상이 생기는 등의 변화가 있지는 않다.
정수간 형변환
큰 타입에서 작은 타입으로 변환하면 값의 손실이 발생할 수 있다.
그렇기 때문에 원칙적으로는 작은 타입에서 큰 타입으로 변경해주는 것이 좋다.
실수형 간의 변환
실수형 역시 작은 타입에서 큰 타입으로 변환해주는 것이 좋다.
또한 float타입의 범위가 넘는 double을 float으로 현변환 한다면 무한대로 나오는 에러를 경험할 수 있다.
반대로 float를 double로 변환 시 불필요한 0이 소수점 끝자리에 많이 붙을 수 있는데 이 부분은 출력 시 양식을 맞출 수 있는 방법도 있다.
정수형과 실수형 간의 변환
위에서처럼 간단하게 채우고 자르는 방식으로 반환하면 저장공간이 다르기 때문에 문제가 생길 수 있다.
정수형을 실수형으로
정수는 소수점 이하의 값이 없으므로 비교적 어렵지 않다.
간단하게 말하면 정수를 2진수로 변환 후 실수의 정장형식으로 저장한다.
실수형이 정수형보다 큰 저장범위를 갖기 때문에 저장공간에 대한 걱정은 할 필요 없다.
대신 정밀도 제한으로 인한 오차는 발생 가능하다.
예를 들어 int는 10자리의 정밀도까지 최대 요구하는데, float는 7자리까지만 정밀성을 보장 가능하므로, 15자리 까지 정밀도를 보장하는 double을 써야하는 것이다.
실수형을 정수형으로
실수형의 소수점 이하 값은 버려진다.
정수형으로 변환 시 반올림이 발생하는 것은 아니기 때문에 이부분에 유의해야한다.
또한 실수의 소쉄을 버리고 남은 정수가 정형의 저장범위를 넘을 시 당연 오버플로우가 발생하게 된다.
자동 형변환
형변환을 생략할 시 컴파일러가 자동으로 수행하는 형변환이다.
이때 데이터의 손실 및 오버플로우 방지를 위하여 반환값은 좀 더 큰 저장공간을 가진 자료형으로 자동 형변환 된다.
댓글남기기