Lobsters

oh, apparently it's not possible to portably check for string-to-float conversion errors in standard c

표준 C에서는 문자열의 부동소수점 변환 오류를 이식성 있게 확인하기 어렵습니다

strtod 계열 함수의 오류 보고 방식은 표준 C와 POSIX에서 다릅니다. 오버플로·언더플로에는 math_errhandling과 구현별 제약이 따르고, 숫자 변환에 실패했는지는 errno가 아니라 endptr로 확인해야 합니다.

AI 요약

문자열을 부동소수점 값으로 바꾸는 strtod, strtof, strtold 계열 함수는 math.h 함수는 아니지만 math_errhandling의 영향을 받습니다. 이 매크로는 errno 사용(MATH_ERRNO)과 부동소수점 예외(MATH_ERREXCEPT) 가운데 구현이 지원하는 오류 보고 방식을 나타냅니다. 작성자는 앞선 글에서 다룬 수학 함수 오류 처리에 이어, 이 변환 함수들의 표준 C와 POSIX 규칙이 어떻게 다른지 살펴봅니다.

오버플로와 언더플로

표준 C에서는 변환 결과만 보고 오류 발생 여부를 판별하기 어렵습니다. errno를 지원하는 구현이라면 errno를 0으로 초기화한 뒤 호출하고 ERANGE인지 확인해야 합니다. 그렇지 않다면 호출 전에 FE_OVERFLOW 또는 FE_UNDERFLOW 예외를 지우고, 호출 뒤 fetestexcept로 검사해야 합니다. 단, 언더플로 보고는 구현이 생략해도 되므로 표준 C만 대상으로 하면 언더플로를 항상 감지한다고 보장할 수 없습니다.

POSIX 규칙은 더 강합니다. 표현 가능한 범위를 넘는 값에는 부호에 맞는 HUGE_VAL 계열 값을 반환하고 errno를 ERANGE로 설정합니다. 언더플로 때도 errno를 ERANGE로 설정해야 합니다. 이 때문에 POSIX를 대상으로 할 때는 반환값과 ERANGE를 함께 확인하면 됩니다. 반면 표준 C에서는 math_errhandling이 errno 사용을 알리지 않으면 변환 함수가 오류 때 errno를 설정하지 않을 수 있습니다. 작성자는 POSIX가 표준 C보다 강한 요구사항을 둘 수 있다는 점을 짚으면서도, POSIX 규칙이 표준 C와 양립하는지는 분명하지 않다고 설명합니다.

잘못된 입력은 endptr로 확인합니다

입력이 숫자로 해석되지 않을 때는 표준 C가 이를 오류로 규정하지 않습니다. errno에 기대지 말고 두 번째 인자로 endptr의 주소를 넘긴 뒤, 호출 후 endptr와 입력 시작 주소 nptr가 같은지 검사해야 합니다. 같다면 변환한 문자가 없습니다.

POSIX는 변환이 불가능하면 0을 반환하고 errno를 EINVAL로 설정할 수도 있다고 규정합니다. ‘may’라는 표현 때문에 구현마다 동작이 달라질 수 있습니다. 실제로 glibc는 strtod("x", ...)에서 errno를 설정하지 않지만 musl은 EINVAL을 설정합니다. 두 구현 모두 POSIX 규칙을 따를 수 있으므로 errno만 검사하는 코드는 이식성이 없습니다. 작성자는 musl의 동작이 표준 C의 errno 규정과도 맞지 않을 가능성을 제기하며, 표준 문구가 더 명확해야 한다고 주장합니다.

작성자가 정리한 대응 방법

오버플로를 POSIX 환경에서 확인하려면 errno를 초기화하고, 결과의 절댓값이 HUGE_VAL인지와 errno가 ERANGE인지 함께 검사합니다. 표준 C 환경에서는 math_errhandling에 따라 errno 또는 부동소수점 예외를 확인해야 합니다. 예외 플래그를 읽고 지우는 코드는 컴파일러가 부동소수점 환경 접근을 고려하도록 해야 합니다. GCC에서는 -ftrapping-math가 관련되며, -ffast-math를 쓰면 기본 설정이 달라질 수 있습니다. 표준 pragma인 FENV_ACCESS도 있지만 GCC는 이를 지원하지 않는다고 작성자는 설명합니다.

언더플로는 POSIX 환경에서 errno가 ERANGE인지 확인할 수 있습니다. 표준 C만 대상으로 하면서 구현 문서가 언더플로 보고 방식을 명시하지 않는다면 보편적인 검사법은 없습니다. 작성자는 결과가 0일 때 입력의 지수 앞에 0이 아닌 숫자가 있는지 직접 검사하는 대안을 들지만, 모든 경계 사례를 처리하기 어렵다고 덧붙입니다. 입력 자체가 숫자로 변환되지 않았는지는 어떤 환경에서도 endptr == nptr로 확인하고 errno에는 의존하지 않는 것이 요점입니다.

Lobsters 반응

  • @Screwtape — 가끔 Python 같은 고수준 언어를 쓰다가 지치면 기계에 더 가까운 저수준 언어를 직접 써보고 싶어집니다. Go와 Java는 너무 기업적이고, Zig·Odin·Hare는 너무 유행을 타며, Rust는 너무 산업적이고 C++는 러브크래프트 소설처럼 난해합니다. “알겠어. 50년 동안 다듬어져 작고 단순한 C를 쓰면 되겠네”라고 생각하다가 이런 글을 읽습니다. Forth를 더 알아봐야 할지도 모르겠습니다.

원문: sebsite.pw / 번역·요약: Trawling