관련 문서: 컴퓨터/표현
프로그래밍 언어 문법 | ||
{{{#!wiki style="margin: -16px -11px; word-break: keep-all" | <colbgcolor=#0095c7><colcolor=#fff,#000> 언어 문법 | C(포인터 · 구조체) · C++(자료형 · 클래스 · 이름공간 · 상수 표현식 · 특성) · C# · Java · Python · Kotlin · MATLAB · SQL · PHP · JavaScript · Haskell |
마크업 문법 | HTML · CSS | |
개념과 용어 | 함수 · 인라인 함수 · 고차 함수 · 람다식 · 리터럴 · size_t · 상속 · 예외 · 조건문 · 참조에 의한 호출 · eval | |
기타 | == · === · deprecated · NaN · null · undefined · 모나드 · 배커스-나우르 표기법 | |
프로그래밍 언어 예제 · 목록 · 분류 | }}} |
1. 개요
Not a Number, NaNNaN은 숫자가 아닌 값들을 대표하는 오류값이다. 컴퓨터에서의 실수 표현 (부동소수점) 표준인 IEEE 754에서 정의되었으며, 이 표준을 따르는 많은 컴퓨터 시스템 및 프로그래밍 언어에서 사용한다. 이 값은 주로 계산 결과를 실수로 정의할 수 없을 때, 오류가 발생했음을 표시하는 값이다. 예를 들면 음수의 제곱근을 계산하려고 해 그 결과가 허수가 되거나, 수를 표시하지 않는 텍스트를 수로 변환했을 때 그 결과가 되기도 하며, 0으로 나눈 몫처럼 결과가 정의되지 않은 연산을 할 때도 NaN을 반환한다.
무한과는 다르다. IEEE 754 에서는 양의 무한과 음의 무한을 나타내는 별도의 값을 제시하고 있으며, NaN과 다른 성질을 지닌다. 예를 들어, "0 < Infinity"는 "참"이 되도록 정의되어 있지만 "0 < NaN"은 "거짓"이 되도록 표준에 정의되어 있다. 후술하겠지만 사실 NaN은 모든 연산에 대해서 (심지어 자기 자신과의 비교에 대해서도) 거짓을 반환하도록 정의되어 있다. 다만 NaN과 무한 모두 수학적인 "실수"가 아니기 때문에 실제로 프로그래밍을 할 때에는 두 값 모두 일종의 예외로 처리하게 되는 경우가 많아 그런 관점에서 비슷하게 취급 되긴 한다.
2. 필요성
어떤 사람은 NaN 대신 예외를 던지는게 더 나을 것이라고 생각할 수도 있다. 하지만 경우에 따라 꼭 그렇지 않다. 프로그램을 작성하면 엄청나게 많은 연산을 하게 되기 때문에, 실수 연산을 할 때마다 일일히 예외를 처리하도록 하는 것은 프로그램을 작성하는 입장에서 매우 비효율적인 일이다. 이 때문에 모든 연산이 종료되었을 때 예외를 한 번만 처리할 수 있도록 하면 이는 매우 읽기 좋고 유지보수하기 좋은 프로그램을 작성할 수 있게 해준다.3. 특징
NaN은 결과가 실수(부동소수점)가 되어야 하는 모든 연산에 대해서는 NaN을, 결과가 진리값(Boolean)이 되어야 하는 모든 연산에 대해서는 거짓을 반환하도록 되어있다. 이는 NaN이 오류 상태를 조용히 전파(propagate)하도록 설계되었기 때문이다.예를 들어 다음과 같은 의사코드를 생각해보자:
#!syntax javascript
function f(a, b) {
c = a / b
d = c + 1
e = d * d
f = e + d
return f
}
a, b를 모두 0으로 하여 이 함수를 실행하면 c의 값이 NaN이 되며, 그 결과 d의 값도 NaN이 되어, 최종적인 함수의 실행 결과가 NaN이 된다. 즉, NaN을 사용하면 각각의 연산에 대해 항상 조건을 검사하는 대신, 최종적인 하나의 값만 검사할 수 있게 된다. 이런 조용한 전파가 NaN의 설계 이유이자 가장 강력한 특징이기 때문에 이로 인해 NaN은 자기 자신과 같은지 확인했을 때 "거짓"이 되는 유일한 값이 되며, 비직관적인 행동을 보여주기도 한다. 예를 들어,
#!syntax javascript
x = NaN
x == NaN // False
NaN은 이처럼 값의 같음을 확인하는 일반적인 검사로 걸러내기가 어려우며, 오히려 자기 자신과의 비교 연산이 거짓이 된다는 이 유일무이한 특성을 통해 확인해야 한다.
NaN이 자기 자신과의 비교가 거짓이 된다는 점을 이해하는 다른 방법이 있는데, 이는 NaN이 하나의 단일한 값이 아니라 어떤 오류 상태를 표시하는 값이라는 것을 인지하는 것이다. 예를 들어, 두 실수 변수 a, b에 대해
a := square root of -1
이고, b := 0 / 0
이라고 하자. a는 결과가 실수로 표현할 수 없는 허수이기 때문에 NaN으로 표시되며, b는 연산 자체가 정의되지 않기 때문에 NaN으로 표시된다. 그런데 이런 두 값의 비교 a == b
가 True가 된다는 것은 두 값이 생성된 맥락을 고려해봤을 때 이상한 일이다. 만약 프로그램에 a와 b가 같을 때 실행되도록 한 분기문이 있다면 이는 프로그램이 매우 이상한 오류를 일으키도록 할 수 있다. 즉, NaN은 단일한 값이 아니라 (여러 원인에 의한) 오류 상태를 전파하기 위한, 어떤 상태를 나타내는 값이기 때문에 이런 특수한 동작을 하도록 설계되었다.여러 프로그래밍 언어에서도 이런 NaN의 표준대로 작동한다. 자바스크립트에서
NaN == NaN
은 false
가 나오는 것을 확인할 수 있고, 일치 연산자 ===를 사용해도 NaN === NaN
은 false가 나온다[* 역설적으로, 이걸 이용해 NaN을 구분하는 함수를 만들 수 있다! #!syntax javascript
const isNaN2 = _ => _ !== _
]. 전역 함수로 isNaN()이 있지만, 이 함수는 정확히 말하면 '어떤 값이 숫자로 변환될 수 있는지'를 알려주는 함수인 데에다, null을 false
로 판단하는 등의 문제가 있었다. (이 문제는 ES6에서 Number.isNan()
함수가 나오며 해결되었다.)한 가지 주의할 점은 NaN은 부동소수점 표준의 일부이기 때문에 정수 타입의 연산과는 관련이 없다는 점이다. 즉 같은 0 / 0 이더라도 0이 정수 타입이면 Exception을, 0이 실수 타입이면 NaN이 된다. 이는 표준을 따르는 프로그래밍 언어라면 모두 동일하다. 대부분의 언어가 0을 정수 리터럴로, 0.0을 부동소수점 리터럴로 인식하지만, 일부 예외도 있다. 예를 들어, JavaScript에서는 Number 타입이 기본적으로 실수(부동소수점)이기 때문에
0
이라고 쓰면 실수 0으로 생각한다. 이로 인해 JS 에서는 0 / 0
이라고만 써도 결과가 NaN이 된다. 이는 리터럴을 표기하는 방법에 관한 것이므로, JS에서 0 / 0이 NaN으로 평가되는 것을 보고 "정수 간의 연산의 결과도 NaN이 될 수 있다"는 식의 오해를 하지 않아야 한다. 다시 강조하지만, NaN은 부동소수점 표준의 일부이다. 덧붙여 JavaScript에서 정수를 나타내려면 0n
과 같이 표기할 수 있으며 이 때 값은 BigInt 타입이 된다. 따라서 JS에서 0n / 0n
이라고 쓰면 조용하게 NaN을 주는 대신 Exception이 발생한다. (단, BigInt 타입은 2020년에 표준에 추가되었기 때문에 오래된 환경에서는 작동하지 않을 수 있다.)4. 각종 언어에서의 NaN 예시
- C#
{{{#!syntax csharp
Console.WriteLine(double.NaN);
}}}
- JavaScript
{{{#!syntax javascript
parseInt("abc") // NaN
0 / 0 // NaN
- Java
{{{#!syntax java
System.out.println(Double.NaN);
}}}
- Python
{{{#!syntax python
print(numpy.NaN)
}}}
- Swift
{{{#!syntax swift
let y = x + Double.nan
print(y == Double.nan)
print(y.isNaN)
}}}이 코드에서 "print(y == Double.nan)" 코드는 상술한 이유로 false를 내뱉지만, print(y.isNaN) 코드는 true를 출력한다. 일반적인 상황에서 Swift는 NaN 에러를 출력하지 않지만,
#!syntax swift .isNaN
을 변수명 뒤에 붙이는 것으로 해당 변수가 숫자인지, 숫자가 아닌지 Bool값을 출력할 수 있다. 숫자가 아니면 true, 숫자라면 false.이와 같은 논리로, 하기 코드도 NaN으로 인식된다.
{{{#!syntax swift
let number: Double = 0.0 / 0.0{{{#!syntax swift
print(number.isNaN)
}}}단순 print(0/0)을 돌려도 NaN이 출력되지는 않고, print(0.0/0.0)을 실행해야 nan이 출력된다. 이는 상술한 바와 같이 NaN이 실수를 나타내는 부동소수점 표준의 일부이기 때문에 실수 (부동소수점) 연산에서만 등장할 수 있는데, Swift가 0 리터럴은 정수로, 0.0 리터럴은 실수로 생각하기 때문이다.
5. 관련 문서
[1] (아무것도 없음) + 'n' 부분의 값이 NaN이 된다.