Chapter 23. Library Support for Numbers and Character Data

지환·2022년 3월 14일
0

여기선 number, character, character string 을 다루는 5가지 header에 대해 알아보겠다.
(C99에서 해당 자료를 다루는 여러 header들을 추가했는데 이는 chapter 25와 chapter 27에서 다루겠다.)

23.1 The <float.h> header

Characteristics of Floating Types

float, double, long double type의 range와 accuracy를 기술한 macro가 정의돼있다.
type이나 function은 없다.

FLT_ROUNDS

모든 floating types에 적용된다.
소숫점의 rounding direction의 현재 상태를 나타내는 macro이다.
상수를 나타내는 <float.h>내의 다른 macro들과 다르게 fesetround 함수를 통해 실행 중에 값을 변경할 수 있다.

ValueMeaning
-1Indeterminable
0Toward zero
1To nearest
2Toward positive infinity
3Toward negatie infinity

FLT_RADIX

모든 floating types에 적용된다.
radix(기수) of exponent representation을 기술한다. (최소값은 binary representation을 나타내는 2이다.)

DECIMAL_DIG

가장 큰 floating type의 significant digits(유효숫자) 개수를 나타낸다.(base 10)

좀 더 정확한 의미 :
https://stackoverflow.com/questions/39703648/what-is-the-difference-between-decimal-dig-and-ldbl-dig-in-float-h

FLT_EVAL_METHOD

implementation이 floating point arithmetic을 필요한 range와 precision보다 더 큰 범위를 사용해서 할지를 알려준다.
(아래 <math.h>에 type과도 관련있다.)

ValueMeaning
-1Indeterminable
0해당 type의 range와 precision에서 계산
1floatdoubledouble의 range와 precision에서 계산
2모든걸 long double의 range와 precision에서 계산

FLT_EPSILON

말고도 DBL_EPSILONLDBL_EPSILON이 있다.
값은 해당 type에서 표현되는(표현 가능한) 두 숫자 사이 가장 작은 차이이다.
https://dojang.io/mod/page/view.php?id=738
위 링크처럼 이를 이용해 두 값을 비교할때 사용할 수 있다.

else..

이외에 float point의 특징이 기술된 macro는 p.590 참고
(macro 앞에 붙은거 해석하는 법 -> FLT : float, DBL : double, LDBL : long double)


23.2 The <limits.h> Header

Sizes of Integer Types

integer type(character 포함)의 range를 기술한 macro가 정의돼있다.
type이나 function은 없다.

macro와 범위는 p.592 참고

사용 예시)
int type이 100,000을 저장할 수 있는지 보기위해 아래와 같이 할 수 있다.

#if INT_MAX < 100000
#error int type is too small
#endif

혹은, 특정 변수가 최대 100,000의 값을 hold해야된다면 아래와 같이 쓸 수 있다.

#if INT_MAX >= 100000
typedef int Quantity;
#else
typedef long int Quantity;
#endif

23.3 The <math.h> Header (C89)

Mathematics

C89에는 아래 part가 있었다.
1) Trigonometric functions
2) Hyperbolic functions
3) Exponential and logarithmic functions
4) Power functions
5) Nearest integer, absolute value, and remainder functions

Errors

<math.h>의 함수는 다른 library 함수와는 다르게 error를 처리한다.
<math.h>의 대부분 함수들은 error가 발생하면 error code을 errno라는 변수에 저장한다.(<errno.h>에 있음)

<math.h>의 함수들은 두가지 종류의 error을 감지하는데,

  1. Domain error
    : argument가 domain 범위를 벗어나는 경우, EDOMerrno에 저장하고, return 값은 implementation defined.
    (몇몇 implementation은 NaN return)

  2. Range error
    : 함수의 return값이 double의 범위를 벗어나는 경우, ERANGEerrno에 저장하고, return 값은 두가지 경우로 나뉜다.
    (1) 크기가 커서 벗어나는 경우(overflow), 결과의 부호에 맞게 positive or negative HUGE_VAL값을 반환한다. (HUGE_VAL<math.h>에 있고, IEEE표준에 infinity에 해당)
    (2) 크기가 작아서 벗어나는 경우(underflow), 0을 반환한다.
    (이 경우 ERANGE는 implementation에 따라 저장될수도있고 안될수도 있다.)

Functions

함수가 너무 많아서 다 적긴 그렇고, 필요하면 찾아볼 수 있게 간단하게 적어둬야지.

  1. 삼각함수 (radian 인자)
    return 값 범위가 제한돼있다.
    추가로 PI macro도 있음

  2. Hyperbolic 함수 (radian 인자)

  3. Exponential & Logarithmic 함수
    e^x, ln 함수 등

  4. modf 함수 : double 숫자를 입력받아 소숫점 위 아래로 분리해준다. (반환방식은 포인터와 return 값으로..)

  5. frexp 함수 : double을 입력받아 f×2^n 형태로 만들어서 f와 n을 반환한다. (f=0이거나 0.5<=f<1)
    마찬가지로 반환 방식은 포인터를 넘겨받아 n을 저장해주고, f는 return 한다.
    ldexp 함수 : f×2^n 형태에서 f와 n을 입력받아 둘을 합친다. 즉, 위 함수를 거꾸로 한다.

  6. power 함수 : 지수, 제곱근

  7. Nearest Integer, Absolute Value(절댓값), Remainder 함수
    fmod 함수 : % operator엔 floating-point opearnds가 올 수 없지만, fmod 함수에는 올 수 있다.


The <math.h> Header (C99)

Mathematics

C99버전은 C89<math.h>를 모두 포함한다. 따라서 C99에서 추가된 부분을 여기서 설명하겠다.
C99에서 다양한 기능을 추가한 이유는
1. IEEE floating point standard를 더 잘 지원하기위해(C에서 IEEE 쓰도록 강제하진 않지만, 대부분 system에서 사용한다..)
2. floating-point arithmetic을 더 잘 control하기 위해
3. Fortran 프로그래머에게 C를 appeal하기 위해
이다.

system programming, embedded systems 개발자에겐 딱히 필요없는 part일 수도 있다.
하지만 engineering, mathmatics, science applications 개발자에겐 유용할 것이다.

IEEE Floating-Point Standard

C89 math.h를 사용하기 위해선 IEEE 754(IEC 60559)에 대해 자세히 알 필요가 없었지만, C99 버전을 이해하려면 좀 더 자세히 봐야한다.
아래는 "IEEE 754" 특징(정보)들 (이걸 C에서 표현하는건 다른 문제다.)

  • Positive/Negative zero
    : sign bit의 값에 따라 0도 positive와 negative로 나뉜다. 가끔 이걸 다르게 처리해야 할때가 있다.

  • Subnormal numbers
    : floating point 숫자가 너무 작아져서 표현하기 어려워지면 IEEE 754에선 평소와 다르게 저장한다. 작아질수록 정확도는 떨어진다.
    저장되는 방식에 대한 자세한 설명은 http://egloos.zum.com/studyfoss/v/4960394 참고

  • Special values
    : IEEE 754에선 3개의 special values를 표현한다.
    (1)positive infinity, (2)negative infinity, (3)NaN(Not a Number)
    (양수를 0으로 나누면 (1)이고, 음수를 0으로 나누면 (2)이다. 수학적으로 정의되지 않은, 0을 0으로 나누는 등의 결과는 (3)이다.)
    infinity는 실제로 expression에 들어가 수학에서처럼 쓰인다.(ex. 1/inf == 0)
    NaN이 expression에서 쓰이면 결과는 NaN이다.
    Standard는 NaN에 대한 여러 표현법을 갖는다(quite NaN, signaling NaN). NaN의 exponent는 모두 1이지만, fraction은 전체가 0만 아니면 된다.

  • Rounding direction
    : 정확한 floating point 값으로 표현될 수 없을때 현재의 rounding direction이 어떤 값으로 표현될지를 정한다.(FLT_ROUNDS 기능 생각하면 되지 싶네)
    (1)가까운 값으로 (2)0으로 (3)양무한대로 (4)음무한대로
    기본은 (1)이다.

  • Exceptions
    5가지의 exceptions이 있다.
    (1)overflow, (2)underflow, (3)division by zero, (4)invalid operation, (5)inexact
    이 중 하나가 감지되면, exception이 raised 됐다고 한다.
    (4번은 결과가 NaN인 경우, 5번은 결과가 rounded돼야하는 경우)

    exception에 관해...
    C에는 exception을 try catch throw하는 개념 자체가 없다.
    여기서 말하는 exception은 IEEE 754에서 정의하는 float point에서의 exception(예외 상황)이다.
    C에서는 IEEE 754(꼭 얘만 지원하는건 아니지만..)를 지원하기 위해 <fenv.h> 헤더파일을 만들었다.
    거기보면 floating point exception 확인하는거랑 등등 나와있으니 궁금하면 보도록
    (floating point exception이 발생하면 status flag가 세팅됨.
    각 exception을 나타내는 macro는 그냥 해당 exception을 지원하면 정의되고,
    status flag에 해당 exception macro 값이 bit단위로 setting됨)

Types

C99에서 math.h에 두개의 types 추가
floating point arithmetic의 performance를 극대화하기위해 사용

  1. float_t : at least as wide as float
  2. double_t : at least as wide as double (+ at least as wide as float_t)

<float.h> 헤더의 FLT_EVAL_METHOD와 관련있다.

Value of FLT_EVAL_METHODMeaning of float_tMeaning of double_t
0floatdouble
1doubledouble
2long doublelong double
OtherImplementation-definedImplementation-defined

Macros

INFINITY : positive or unsigned infinity의 float 버전을 나타낸다. (infinity를 지원하지 않는 implementation이면 얘는 overflow된 float value를 나타낸다.)
NAN : "not a number"의 float 버전을 나타낸다.(정확히 "quite NaN")
(quite NaN이 지원안되면 NAN macro는 정의되지 않는다.)

특정 함수하고만 관련되는 macro는 그 함수 설명할때 나옴

Errors

most part에선 C89버전과 같은 방식으로 error를 처리한다. 약간의 twist가 있음.

C99에선 math_errhandling macro를 통해 implementation이 어떻게 error를 signal할지 정할 수 있게 한다.
(1)errno에 저장된 값으로 알릴지
(2)floating-point exception으로 알릴지
(3)아니면 두 방법 모두 사용할지

1) MATH_ERRNO : 1
2) MATH_ERREXCEPT : 2
3) math_errhandling : MATH_ERRNO(1)이거나, MATH_ERREXCEPT(2)이거나, 둘을 bitwise OR(|) 연산한 값(3)

(math_errhandling값은 프로그램내에서 변경될 수 없다.)
(math_errhandling은 꼭 macro가 아니어도 된다.)
(math_errhandling에 어떤 값이 저장돼있냐에 따라 어떻게 error를 signal할지 정해진다.)

왜 floating point exception으로 알리는 방법도 추가한걸까?
errno에 저장된 정보만으로는 정확히 무엇때문에 error가 발생했는지 알기 쉽지 않아서 추가한게 아닐까.. 라고 일단 추측해본다.

각 error 발생했을때 어떻게 처리되는지 표준별로 정리 (C89 내용은 위에서 봤던거임)

Domain error

return 값은 C89의 경우 Implementation defiend..
(NaN으로 처리하는 경우가 보통인듯.)

아래는 error signal 방식

  • (C89)
    errnoEDOM을 저장한다.

  • (C99)
    1) math_errhandling & MATH_ERRNO == nonzero(MATH_ERRNO bit가 set된 경우),
    errnoEDOM을 저장한다.
    2) math_errhandling & ERREXCEPT == nonzero(MATH_ERREXCEPT bit가 set된 경우),
    invalid floating point exception이 raised된다.

Range error

1. Overflow

  • (C89)
    1) return 값
    : 부호에 맞는 HUGE_VAL 값을 반환한다.
    2) error signal 방식
    : errnoERANGE를 저장한다.

  • (C99)
    1) return 값
    : default rounding인 경우 or overflow 된 값이 "exact infinity 인 경우(ex.log(0.0)),
    return type에 맞게 HUGE_VAL, HUGE_VALF, HUGE_VALL 중 하나를 반환한다.
    2) error signal 방식 1 (math_errhandling & MATH_ERRNO == nonzero)
    : errnoERANGE를 저장한다.
    3) error signal 방식 2 (math_errhandling & MATH_ERREXCEPT == nonzero)
    : divide-by-zero exception이 raised되거나(결과가 exact infinity인 경우),
    overflow exception이 raised된다(결과가 excat infinity가 아닌 그냥 overflow인 경우).

2. Underflow

  • (C89)
    1) return 값
    : 0
    2) error signal 방식
    : 몇몇 implementation은 errnoERANGE 저장

  • (C99)
    1) return값
    : 함수 return type에서 가장 작은 normalized positive number보다 작거나 같은 수를 반환한다.
    (0이거나 subnormal number일 수 있다.)
    2) error signal 방식 1 (math_errhandling & MATH_ERRNO == nonzero)
    : errnoERANGE를 저장할 수 있다.
    3) error signal 방식 2 (math_errhandling & MATH_ERREXCEPT == nonzero)
    : underflow exception이 raised 될 수 있다.

    underflow의 경우 efficiency를 이유로 implementation이 errno에 저장하지 않거나,
    exception을 raise하지 않을 수도 있다.

Functions

C89 함수들에서 버전이 많이 추가됐다. 예를들어 C89에선 sqrt함수가 double밖에 처리 못했는데 sqrtfsqrtl을 만들어서 floatlong double도 처리할 수 있게 했다.

여기도 다 적긴 그렇고 간단하게만 적어보겠다.
근데 새로운 함수들은 대부분 highly sepcialized 함수라서 이 책에서도 완벽히 설명하진 않았다고 한다.
필요하면 자세히 찾아보는걸로..

  1. real floating type(float, double, long double)이 normal한지 구분해주는 함수들..
    infinity인지, zero인지, nan인지, subnormal인지..

  2. Nearest integer 구하는 함수 조심해야될게 integer를 반환하긴하는데 이게 int type일수도 있고 float type일 수도 있다.

  3. floating point number에 low-level detail을 조작할 수 있는 함수들은 "Manipulation Functions"로 분류돼있다.
    sign을 복사하거나, 바로 다음or이전의 표현가능한 값을 추출하거나..

  4. fmax, fmin : 둘 중 더 큰(작은) 값 반환

  5. fma(Floating Multiply-Add) : 곱셈과 덧셈을 수행한다.
    이를 사용하는 이유는 그냥 operation으로 계산하는 것보다는 적어도 빠르기 때문이다. rounding operation도 한번만 일어나기때문에 결과도 더 정확할 수 있다.
    이를 contraction이라고 부르는데, pargma directive를 통해 일반 계산식에서 사용할지 말지 정할 수 있다.
    #pragma STDC FP_CONTRACT on-off-switch
    on-off-switch 자리에 ON, OFF, DEFAULT가 올 수 있다.
    꺼져있어도, fma함수를 통해 explicit contraction을 수행할 수 있다.

  6. Comparison Macros, 어떤게 더 큰지 등을 비교하는 macro 함수를 제공한다.
    얘네는 relational operator의 "quiet" version이라고 할 수 있는데, exception을 raise하지 않고 작업을 수행하기 때문이다.


23.5 The <ctype.h> Header

Character Handling

  1. character-classification 함수

  2. character case-mapping 함수

이렇게 두 종류의 함수를 제공한다. (자세한 list는 책 참고)

해당 기능을 수행하기위해 굳이 여기 함수를 쓸 필요는 없지만,
장점이 있다.
1) 최적화된 속도(대부분 macro로 구현됨)
2) more portable program으로 만들 수 있다. any character set에서 작동한다.
3) locale이 변경되면 <ctype.h> 함수들도 그에 맞게 적용된다.

<ctype.h>모든 함수int를 argument로 받고, int를 return한다.
그래서 argument가 char type이라면 주의해야한다.
charunsigned type이거나 ASCII 같이 7-bit character set을 사용한다면 상관없지만,
signed type이거나 8-bit character set을 사용한다면 int로 변환했을때 negative value가 될 수 있다.
이런 경우 unsigned char로 casting 하는게 안전하다.


23.6 The <string.h> Header

String Handling

string을 다루는 함수도 있지만, string이 아닌 block of memory를 다루는 함수들(mem으로 시작)도 있다.

Copying Functions

memcpy & memmove & strcpy & strncpy

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

void *memmove(void *s1, const void *s2, size_t n);

char *strcpy(char * restrict s1, const char * restrict s2);

char *strncpy(char * restrict s1, const char * restrict s2, size_t n);

restrict가 쓰인 parameter의 경우 source와 destination이 overlap돼선 안된다.

strncpy는 굳이 null terminated string이 필요하지는 않다. n까지만 복사하고 짤리기 때문에 null character는 복사되지 않을 수도 있다.
하지만 null character를 중간에 만나면 그 이후로는 어떤 문자가 오든 상관않고 n번째까지 모두 null character로 작성한다.
ex)

char source[] = {'h', 'o', 't', '\0', 't', 'e', 'a'};

strncpy(dest, source, 7);   //h, o, t, \0, \0, \0, \0  으로 저장됨.

Parameter
위 함수들 모두 다 첫번째 인자point to the destination, 두번째 인자point to the source를 받는다.

Return
위 함수들 모두 다 첫번째 인자(a pointer to the destination)을 return한다.

Concatenation Functions

strcat & strncat

char *strcat(char * restrict s1, const char * restrict s2);

char *strncat(char * restrict s1, const char * restrict s2, size_t n);

s1의 뒤에 s2를 이어붙인다.
둘 다 null terminated string이어야 한다.
마지막에 null character를 추가한다.

strncat(str1, str2, sizeof(str1) - strlen(str1) - 1);
1을 빼줌으로써 null character가 들어갈 공간도 잘 생각해줘야된다.(공간충분하면 알아서 들어감)

Comparison Functions

memcmp & strcmp & strncmp

int memcmp(const void *s1, const void *s2, size_t n);

int strcmp(const char *s1, const char *s2);

int strncmp(const char *s1, const char *s2, size_t n);

parameter로 받은 두 (character) array의 contents를 비교한다.

mismatch가 발견되자마자 return한다. 반환값은 mismatch된 element를 비교해서 첫번째 인자의 것이 작다면 음수, 같다면 0, 크다면 양수를 반환한다.

mismatch가 발견되지 않는다면,
memcmpn까지
strcmp\0이 나올때까지
strncmp\0이 나오거나 n까지
비교를 진행한다.

strcoll

int strcoll(const char *s1, const char *s2);

locale을 고려해야되는 상황에선 이 함수들을 사용한다.

strcollstrcmp와 비슷하지만, 결과가 현재 locale에 따라 달라진다.

strxfrm

size_t strxfrm(char * restrict s1, const char * restrict s2, size_t n);

대부분의 경우 strcoll이 잘 작동하지만, 비교를 한번 이상 해야하거나 comparison 결과에 영향을 주지 않고 locale을 바꿔야 하는 경우에는
strxfrm(string transform) 함수가 대안이 될 수 있다.
(자세한 사용 예시는 p.619 윗부분 code 참고)

Parameter
이 함수는 두번째 argument를 locale에 따라 변환하여 첫번째 argument에 저장한다.(구체적으로 무엇을 어떻게 변환되는지는 기술되지 않음)

세번째 argument는 첫번째 argument에 작성될 길이를 제한한다.(\0 포함한 길이니까 실제 크기는 +1 이어야 나중에 \0 추가할 수 있음)

Return
그리고 변환된 string의 길이를 반환한다.

원래 string에 `strcoll`을 적용한 결과와,
이 함수를 적용해 변환된 string에 `strcmp`를 적용한 결과는 같아야 한다.
(그렇게 보장하는듯)

Search Functions

strchr & _memchr

char *strchr(const char *s, int c);

void *memchr(const void *s, int c, size_t n);

둘 다 c 문자가 처음 나오는 지점의 pointer를 반환한다.
해당 문자가 없으면 null pointer를 반환한다.

두 함수 차이 : strchr은 null character까지 찾지만, memchr은 명시된 길이 n까지만 찾는다.

p = strchr(str, 'f');
p = strchr(p + 1, 'f');
// 이렇게 두번째 f를 찾게 할 수 있다.

strpbrk

char *strpbrk(const char *s1, const char *s2);

두번째 argument(문자열)에 포함된 아무 문자와 일치하는, 첫번째 argument 왼쪽 끝에 있는 pointer를 반환한다.
p = strpbrk(str, "mn"); //str에서 m이나 n중에 먼저 나오는 곳의 pointer 반환

strspn & strcspn

size_t strspn(const char *s1, const char *s2);

size_t strcspn(const char *s1, const char *s2);

strspns2에 들어온 string에 포함된 아무 문자와도 일치하지 않는 s1에서의 첫번째 position을 반환한다.

strcspns2에 들어온 string에 포함된 아무 문자와도 일치하는 s1에서의 첫번째 position을 반환한다.

strstr

char *strstr(const char *s1, const char *s2);

s1에서 s2 문자열을 처음으로 포함하는 지점을 반환한다.
찾지못하면 null pointer를 반환한다.

strtok

char *strtok(char * restrict s1, const char * restrict s2);

특정 delimiting character(s2)를 포함하지 않는 연속된 문자인 token을 찾는 함수이다.
(strspn과는 좀 다르지..)

그렇게 s1에서 찾은 token의 마지막 character 뒤에 \0을 추가한다.(s1에 추가하는 거임. token 뒤에 무슨 문자가 있든, 그냥 덮어쓴다.)
그리고 해당 token의 첫번째 character를 반환한다.
아무 token도 찾을 수 없으면 NULL pointer를 반환한다.

strtok(NULL, s2); 처럼 첫번째 인자가 NULL pointer인 경우 이전 strtok 호출에 이어서 시작한다. 다음 token을 찾고, 그 token 뒤에 \0을 추가한다.
이때 s2는 굳이 이전과 같은 문자열일 필요는 없다.
이렇게 이어서 호출하는건 NULL pointer를 반환할 때까지 계속 할 수 있다.

단점으론 한번에 하나의 string 밖에 진행하지 못한다는 점,
여러 delimiters를 하나의 delimiter와 동일하게 대해서 비어있는 field가 있는 경우 적합 X
(그냥 비어있는 곳인데 아예 없다고 체크해버려서 그런가? -> 아래 링크 참고)

https://benpfaff.org/writings/clc/strtok.html

Miscellaneous Functions

memset & strlen

void *memset(void *s, int c, size_t n);

size_t strlen(const char *s);

memsets가 가리키는 block에 n bytes를 c로 모두 초기화 한다.
ex. memset(a, ' ', sizeof(a));
첫번째 argument를 반환한다.

strlen은 null character(\0)를 제외한 string의 길이를 반환한다.

<string.h>의 함수들에 argument로 string literal을 넘겨줄때는 잘 보고 해야한다.
해당 argument가 수정이 되거나 하면 UB가 될 수 있다.
ex. strtok에 첫번째 인자로 string literal 넘겨줄 수 없다.

Q&A

<math.h> 함수 중에 그냥 결과값을 1 빼거나 더해주는 간단한 함수가 있던데 이런게 굳이 필요한가?
0에 가까운 수에선 1을 빼거나 더하는 것도 round-off error로 정확한 결과가 나오지 않을 수도 있다.
따라서 좀 더 정확한 결과를 위해 따로 함수를 정의한다.

nextafter 함수에서 x와 y가 같으면 y를 반환한다는데, 그게 뭐 차이가 있나?
-0.0+0.0은 구분이 된다.

<string.h> 함수가 너무 많다. copy 함수도 이게 다 필요한가 싶다.
보면 어떤건 string용, 어떤건 일반 memory block용,
그리고 어떤건 safety를 포기하고 더 빠른 대신, 어떤건 좀 더 느려도 안전하다.
각각 이런 차이들이 있으니 잘 고려해서 사용하자.

strspn 이름이 신기함
해당 character set의 문자를 포함하면서 최대한 "span" 될 수 있는 지점을 찾는다는 의미라고 볼 수 있다고 하네 ~ ㅎㅎ

0개의 댓글