C++17 - 숫자to문자열, 문자열to숫자 변환 표준 라이브러리

C++17에서 숫자를 문자열로 변환, 문자열을 숫자로 변환 해주는 라이브러리가 새로 생김
출처

특징

  • 서식 지정은 문자열이 아니다
  • 동적 메모리 할당을 하지 않는다
  • 로케일 무시
  • 가상 함수 호출이 아니다
  • 버퍼 오버런 방지
  • 반환 값에서 에러를 반환한다
  • 입력 문자열의 빈 공간은 무시하지 않는다
  • 선두의 0x는 해석 하지 않는다.

헤더 파일

#include <charconv>

문자열에서 숫자로 변환

std::from_chars_result from_chars(const char* first, const char* last, /*see below*/& value, int base = 10);
std::from_chars_result from_chars(const char* first, const char* last, float& value, std::chars_format fmt = std::chars_format::general);
std::from_chars_result from_chars(const char* first, const char* last, double& value, std::chars_format fmt = std::chars_format::general);
std::from_chars_result from_chars(const char* first, const char* last, long double& value, std::chars_format fmt = std::chars_format::general);

struct from_chars_result {
    const char* ptr; // 숫자 값으로 해석 할 수 없는 문자로의 포인터
    std::errc ec; // 변환 실패한 경우의 에러 코드
};

enum class chars_format {
    scientific = /*unspecified*/, // %e와 같음
    fixed = /*unspecified*/,      // %f와 같음
    hex = /*unspecified*/,        // %a와 거의 같음
    general = fixed | scientific   // %g와 같음
};

사용 예

const char s[] = "1975s";
int i = 0;
auto r = std::from_chars(s, s + sizeof(s) - 1, i);

assert(i == 42);
assert(r.ptr == s + 4); 
assert(r.ec == std::errc{});

r.ptr은 숫자로 해석할 수 없는 문자(위 코드에서는 s)를 가리킨다.

마이너스 등호 사용

const char s[] = "-1975s";
int i = 0;
auto r = std::from_chars(s, s + sizeof(s) - 1, i);

assert(i == -1975);
assert(r.ptr == s + 5); 
assert(r.ec == std::errc{});

플러스 등호는 사용 불가

const char s[] = "+1975s";
int i = 114;
auto r = std::from_chars(s, s + sizeof(s) - 1, i);

assert(i == 114);
assert(r.ptr == s); 
assert(r.ec == std::errc::invalid_argument);

숫자가 너무 큰 경우 에러

const char s[] = "12345678901234567890";
int i = 114;
auto r = std::from_chars(s, s + sizeof(s) - 1, i);

assert(i == 114);
assert(r.ptr == s + sizeof(s) - 1); 
assert(r.ec == std::errc::result_out_of_range);

이 경우도 r.ptr은 숫자로 해석할 수 없는 문자를 가리키므로 최후의 숫자 다음의 문자(즉 종단)를 가리키고 있다.

16진수는 가능

const char s[] = "DEADBEEF";
int i = 42;
auto r = std::from_chars(s, s + sizeof(s) - 1, i, 16);

assert(i == 0xDEAD'BEEF);
assert(r.ptr == s + 8); 
assert(r.ec == std::errc{});

선두에 0x 가 붙으면 에러

const char s[] = "0xDEADBEEF";
int i = 42;
auto r = std::from_chars(s, s + sizeof(s) - 1, i, 16);

assert(i == 0);
assert(r.ptr == s1); 
assert(r.ec == std::errc{});

에러는 아니지만 선두의 0만 변환 된다.

부동 소수정

const char s[] = "42.195km";
double d = 0;
auto r = std::from_chars(s, s + sizeof(s) - 1, d);

assert(i == 42.195);
assert(r.ptr == s + 6); 
assert(r.ec == std::errc{});

앞의 정수와 같이 선두에 + 등호가 붙으면 에러

지수부의 + 등호는 괜찮음

const char s[] = "2.99792458e+8m/s";
double d = 0;
auto r = std::from_chars(s, s + sizeof(s) - 1, d);

assert(i == 2.997'924'58e+8);
assert(r.ptr == s + 13); 
assert(r.ec == std::errc{});

fmt가 scientific인 경우는 지부가 없으면 에러

const char s[] = "299792458m/s";
double d = 0;
auto r = std::from_chars(s, s + sizeof(s) - 1, d, std::chars_format::scientific);

assert(i == 0);
assert(r.ptr == s); 
assert(r.ec == std::errc::invalid_argument);

숫자에서 문자열로 변환

std::to_chars_result to_chars(char* first, char* last, /*see below*/ value, int base = 10);
std::to_chars_result to_chars(char* first, char* last, float       value);
std::to_chars_result to_chars(char* first, char* last, double      value);
std::to_chars_result to_chars(char* first, char* last, long double value);
std::to_chars_result to_chars(char* first, char* last, float value,  std::chars_format fmt);
std::to_chars_result to_chars(char* first, char* last, double value, std::chars_format fmt);
std::to_chars_result to_chars(char* first, char* last, long double value, std::chars_format fmt);
std::to_chars_result to_chars(char* first, char* last, float value, std::chars_format fmt, int precision);
std::to_chars_result to_chars(char* first, char* last, double value, std::chars_format fmt, int precision);
std::to_chars_result to_chars(char* first, char* last, long double value, std::chars_format fmt, int precision);

struct to_chars_result {
    char* ptr;    //출력된 최후의 문자 다음의 포인터
    std::errc ec;  // 변환 실패한 경우의 에러 코드
};

사용 예

int i = 42;
char s[10];
auto r = std::to_chars(s, s + sizeof(s), i);

assert("42"sv == std::string_view(s, r.ptr - s));
assert(r.ec == std::errc{});

출력은 ‘\0’으로 종단 되지 않으므로 주의

16진수

int i = 42;
char s[10];
auto r = std::to_chars(s, s + sizeof(s), i, 16);

assert("2a"sv == std::string_view(s, r.ptr - s));
assert(r.ec == std::errc{});

선두에 0x 는 붙지 않는다. 영문자 출력은 소문자(대문자 버전은 없다).

버퍼가 부족하면 에러

int i = 114'514;
char s[5];
auto r = std::to_chars(s, s + sizeof(s), i);

assert(r.ptr == s + sizeof(s));
assert(r.ec == std::errc::value_too_large);

변환 에러의 경우 ptr은 마지막을 가리킨다. 변환 에러의 경우 버퍼 안이 어떻게 될지는 미정의.

부동 소수점

double d = 100'000;
char s[10];
auto r = std::to_chars(s, s + sizeof(s), d);

assert(r.ec == std::errc{});
assert("1e+05"sv == std::string_view(s, r.ptr - s));

포맷을 지정하지 않으면 로케일 C의 printf에서 %f와 %e로 변환한 경우에 문자열이 짧아지게 된다.
같은 길이의 경우에는 %f를 우선.
printf의 %g와는 다르므로 주의.

정밀도 지정

double d1 = 3.1415926653589793238462643383279;
char s[50];
auto r = std::to_chars(s, s + sizeof(s), d1);
assert(r.ec == std::errc{});

double d2 = 0;
auto r2 = std::to_chars(s, r1.ptr, d2);
assert(d1 == d2)
assert(r.ec == std::errc{});

부동 소수점은 문자열 화 할 때 정밀도를 지정하지 않은 경우 라운드트립 해서 결과가 같아 지도록 처리된다.
포맷을 지정한 경우도 같다.

double d = 3.1415926653589793238462643383279;
char s[100];
auto r = std::to_chars(s, s + sizeof(s), d, std::chars_format::general, 3);

assert(r.ec == std::errc{});
assert("3.14"sv == std::string_view(s, r.ptr - s));

정밀도의 지정 내용은 printf의 대응하는 서식과 같다.


이 글은 2019-11-21에 작성되었습니다.