CPP Module 02 - ex01

19848 단어 cpp modulecppcpp

ex01

다음과 같이 동작하는 프로그램을 작성하라.
- 기존 ex00에서 사용한 코드를 재사용한다.
- constructor : 인자를 받아 이를 고정 소수점 값으로 변환해주는 생성자를 추가하라. 이 때 사용하는 fractional bits의 값은 ex00와 동일하다.
- float toFloat(void) const : 고정 소수점 값을 부동 소수점 값으로 변환한다.
- int toInt(void) const : 고정 소수점 값을 정수 값으로 변환한다.
- operator<< : 고정 소수점 값의 부동 소수점 표현을 출력 스트림에 입력하는 <<연산자에 대한 오버로드이다.

main.cpp와 그 실행결과는 다음과 같다.

부동 소수점과 고정 소수점

부동 소수점

부동 소수점은 흔히 사용하는 float, double에서 사용하는 방식이다.

부동 소수점은 위 그림과 같이 세 가지 파트로 구분된다.

  1. Sign - 부호 비트
  2. Exponent - 지수부
  3. mantissa - 가수부(과제에서는 fraction으로 표현)

이에 대해 살펴보기 전 정규화(Normalization)을 알아야 한다.

정규화(Normalization)

정규화는 프로그래밍에서 여러 의미를 갖지만, 여기에서는 2진수의 양식을

1.10101010.... * 2^n

과 같이 정수부에 1만 남기고 나머지를 실수부에 넣는 방식이다.
부동 소수점은 이렇게 정규화된 값을 지수부와 가수부에 나누어 저장한다.

부호 비트 - 1비트

부호 비트는 0이면 양수, 1이면 음수를 의미한다.

지수부 - 8비트

지수의 경우 2^n과 같거나 가장 가까운 숫자로 결정된다.
2진수로 계산하기 때문에 지수부에는 n을 저장하면 되는데, 여기서 지수 그 자체를 저장하는 것이 아닌 bias와 더한 값을 저장한다.
bias의 경우 (2^n-1) - 1로 구할 수 있으며, 32비트를 기준으로는 (2^8-1) - 1 = 127이 된다.

가수부 - 나머지

정규화를 진행한 부분 중 실수부만을 저장한다.
그 이유는 2진수를 정규화하면 정수부는 무조건 1이 되기 때문이며, 보통 이를 생략하기 때문이다.

그래서 만약 -17이라는 값을 32비트 부동 소수점으로 표현한다고 하면, 그 과정은

  1. 정규화를 진행한다.
    17을 2진수로 변환하면 10001이며, 이를 정규화 하면 1.0001 * 2^4가 된다.
  2. 부호 비트를 결정한다.
    -17이기 때문에, 부호 비트는 1이 된다.
  3. 지수부에 저장할 bias를 계산한다.
    17을 2진수로 변환한 뒤 이를 정규화한 값은 1.0001 * 2^4이므로, 지수는 4이며 bias는 127이므로 127 + 4 = 131을 지수부에 저장하게 되며, 이를 2진수로 변환하면 10000011 이다.
  4. 가수부에 저장할 값을 계산한다.
    정규화한 값중 허수부를 넣으면 되기 때문에, 남은 자리인 23칸에 0001을 적절하게 저장한다. 즉 00010000000000000000000이 될 것이다.

이러한 과정을 거치게 된다.

그러므로 -17을 부동 소수점으로 표현하면 11000001100010000000000000000000 가 된다.

고정 소수점

고정 소수점은 소수 부분을 표현할 비트의 수를 미리 정해둔 뒤에 10진수를 2진수로 변환한 값을 그대로 비트에 넣은 것이다.

만약 16비트를 사용하고, 7.625라는 실수를 고정 소수점(소수를 표현할 비트는 8)을 통해 저장하고자 한다면, 7.625를 2진수로 변환하면 111.101이 되기 때문에

위 사진과 동일하게 저장된다.

고정 소수점으로 변환

과제에서 요구하는 Fractional Bits는 8이므로, 이를 토대로 변환하면 된다.

정수를 고정 소수점으로 변환

기존의 값을 고정 소수점으로 변환하고자 하면, Fractional Bits가 존재할 수 있도록 8 비트를 고정으로 가지고 있어야 하기 때문에 << 연산을 통해 빈 비트 공간을 만들면 된다.

즉 변환하고자 하는 값을 n이라고 한다면

n << 8
과 같이 변환하면 된다.

실수를 고정 소수점으로 변환

이 경우는 정수와 동일하게 << 연산을 통해 Fractional Bits의 공간을 마련해주면 된다.

다만 여기서 주의할 점으로, floatdouble과 같은 부동 소수점의 경우 <<과 같은 쉬프트 연산 자체가 불가능하기 때문에, 이를 우회하여 표현해야 한다.

또한 현재 고정 소수점으로 저장할 클래스의 변수의 타입이 int형 이므로 일부 데이터가 누락될 수 있다.
그러므로 비교적 정확한 변환을 위해 허용 함수인 roundf를 통해 반올림을 진행할 필요가 있다.

즉 변환하고자 하는 값을 n이라고 한다면

roundf(n * (1 << 8))
과 같이 변환하면 된다.

고정 소수점을 정수로 변환

이 경우는 특정 값을 고정 소수점으로 변환한 것을 다시 원위치 시키면 된다.

즉 변환하고자 하는 값을 n이라고 한다면

n >> 8
과 같이 변환하면 된다.

고정 소수점을 실수로 변환

과제에서 이 경우는 부동 소수점 -> 고정 소수점 -> 부동 소수점의 변환 방식을 의미한다.
그러므로 기존 Fixed 클래스에 저장하고 있는 고정 소수점을 float로 강제 형변환하여 부동 소수점으로 변환한 뒤, 고정 소수점의 Fractional Bits를 표현하기 위해 << 연산한 값을 다시 되돌리면 된다.

즉 변환하고자 하는 값을 n이라고 한다면

float(n) / (1 << 8)
과 같이 변환하면 된다.

<< 연산자 오버로딩

<< 연산자가 사용된 코드와 그 실행 결과를 확인하면 알 수 있다.

Fixed const b( 10 );
Fixed const c( 42.42f );

std::cout << "b is " << b << std::endl;
std::cout << "c is " << c << std::endl;

해당 코드의 동작 결과는

b is 10
c is 42.4219

이므로 Fixed 클래스에 대한 << 연산은 Fixed 클래스에 저장된 고정 소수점 값을 toFloat 함수를 통해 부동 소수점으로 출력하는 것을 확인할 수 있다.

코드

Fixed.hpp

#ifndef FIXED_HPP
# define FIXED_HPP

# include <iostream>
# include <cmath>

class Fixed {
	public:
		Fixed(void);
		Fixed(const int fixed);
		Fixed(const float fixed);
		Fixed(const Fixed &src);
		~Fixed(void);

		Fixed &	operator=(Fixed const &src);

		int		getRawBits(void) const;
		void	setRawBits(int const raw);
		
		int		toInt(void) const;
		float	toFloat(void) const;
	private:
		int					_value;
		const static int	_bits;
};

std::ostream &	operator<<(std::ostream & s, const Fixed & fixed);

#endif

Fixed.cpp

#include "Fixed.hpp"

const int	Fixed::_bits = 8;

Fixed::Fixed(void) {
	_value = 0;
	std::cout << "Default constructor called" << std::endl;
}

Fixed::Fixed(const int value)
{
	_value = value << _bits;
	std::cout << "Int constructor called" << std::endl;
}

Fixed::Fixed(const float value)
{
	_value = roundf(value * (1 << _bits));
	std::cout << "Float constructor called" << std::endl;
}

Fixed::Fixed(const Fixed &src)
{
	std::cout << "Copy constructor called" << std::endl;
	*this = src;
}

Fixed::~Fixed(void)
{
	std::cout << "Destrutor called" << std::endl;
}

Fixed&	Fixed::operator=(const Fixed &src)
{
	std::cout << "Assignation operation called" << std::endl;

	_value = src._value;
	return (*this);
}

int	Fixed::getRawBits(void) const
{
	return (_value);
}

void	Fixed::setRawBits(int const raw)
{
	_value = raw;
}

int	Fixed::toInt(void) const
{
	return (_value >> _bits);
}

float	Fixed::toFloat(void) const
{
	return (float(_value) / (1 << _bits));
}

std::ostream&	operator<<(std::ostream &out, const Fixed &fixed)
{
	out << fixed.toFloat();
	return out;
}

실행 결과는 다음과 같다.

참조

Introduction of Floating Point Representation
Fixed Point vs. Floating Point Illustration
컴퓨터에서의 실수 표현: 고정소수점 vs 부동소수점
Introduction of Floating Point Representation

좋은 웹페이지 즐겨찾기