12 Nisan 2019 Cuma

IEEE 754 - Nan - Matematiksel Olarak Hesaplanamayan Bir Durumda Ortaya Çıkar

Giriş
NaN maksimum exponent ve sıfır olmayan bir mantissa olarak temsil edilir.

Örnek
Şöyledir. Exponent 11 bit tamamı 1. Mantissa'nın da başındaki bit 1.
 1 1111111 1111 1000 00000000 00000000 00000000 00000000 00000000 00000000
 ||             ||                                                       |
 |<-   Exp     -><-                     Mantissa                        ->
  Sign
NaN Hangi Durumlarda Ortaya Çıkar
Nan Belirsiz ve Tanımsız olarak tabir edilen durumlarda ortaya çıkar. Açıklaması şöyle
NaN is a concept defined in IEEE 754 standard for floating-point arithmetic, not being a number is not the same as negative infinity or positive infinity, NaN is used for arithmetic values that cannot be represented, not because they are too small or too large but simply because they don't exist.

1. Belirsiz Durumu- Indeterminate
Belirsiz (0.0 / 0.0) , (sonsuz / sonsuz) gibi işlem sonucu çıkar. Belirsizi bulmak kolay. a / b = c denklemini a = b x c şeklinde de yazabilirim.

0 = 0 x c denkleminde c herhangi bir sayı olabilir. Bu yüzden belirsizdir.
sonsuz = sonsuz x c denkleminde c herhangi bir sayı olabilir. Bu yüzden belirsizdir.

2. Tanımsız Durumu - Undefined
Tanımsız (x / 0.0) gibi bir işlem sonucu çıkar.
Örnek
a = 0.0 x c şeklinde bir denklem yazarsam a'nın her zaman 0 olduğunu bilirim. Ancak a 0'dan farklıdır diye bir varsayımla başlarsam kendimle çelişirim. Bu yüzden işlem tanımsızdır.
Örnek
Bir diğer tanımsız ise şöyle
Math.sqrt(-2.0)
Örnek
Bir diğer tanımsız şöyle
1/0 = ∞ //too large     
log (0) = -∞ //too small
sqrt (-1) = NaN //is not a number, can't be calculated
Nan ve Infinity Arasındaki Fark Nedir
Açıklaması şöyle. Yani sonuç tanımsız ise Nan, overflow var ise Infinity döndürülmeye çalışılır.
A NaN is returned (under IEEE 754) in order to continue a computation when a truly undefined (intermediate) result has been obtained. An infinity is returned in order to continue a computation after an overflow has occurred.
Örnek
Elimizde şöyle bir kod olsun. Negatif sonsuzun kare kökü tanımsızdır Nan döner. Negatif Sonsuzun üssü aslında yine sonsuzdur ve Nan değil Infinity döner.
Math.sqrt(Double.NEGATIVE_INFINITY); // NaN
Math.pow(Double.NEGATIVE_INFINITY, 0.5); // Infinity
Genel Kural
Genel kural olarak şunu söyleyebiliriz. Bir aritmetik işlemin sağ veya sol tarafında NaN varsa, sonuç NaN çıkar.

Aritmetik İşlem
Yukarıdaki kurak gereği toplama, çıkarma, bölme ve çarpma işlemleri hep NaN verir.
float qNaN = std::numeric_limits<float>::quiet_NaN();
float result = qNaN - qNaN; //nan verir
Karşılaştırma
Örnek
NaN sayısı bir başka NaN sayısı ile ==, <, > işlemine göre karşılaştırılamaz.
double a = Double.NaN;
double b = Double.NaN;
System.out.println(a == b); // false
System.out.println(a < b); //  false
System.out.println(a > b); //  false
Visual Studio ile debug ederken NaN iki şekilde görülebilir. İlki -1.#IND000000000000 (indetermined belirsiz) diğeri ise -1.#INF000000000000 (undefined tanımsız)  sabitleridir.

Örnek
C++ standardında bir hata var. std::min() ve std::max() hatalı sonuç veriyor. Elimizde şöyle bir kod olsun.
#include <iostream>
#include <cmath>
#include <algorithm>

int main(int, char**)
{
  double one = 1.0, zero = 0.0, nan = std::nan("");

  cout << "std::min(1.0, NaN) : " << std::min(one, nan) << endl;
  cout << "std::min(NaN, 1.0) : " << std::min(nan, one) << endl;

  cout << "std::min_element(1.0, 0.0, NaN) : " << std::min({one, zero, nan}) << endl;
  cout << "std::min_element(NaN, 1.0, 0.0) : " << std::min({nan, one, zero}) << endl;

  cout << "std::min(0.0, -0.0) : " << std::min(zero, -zero) << endl;
  cout << "std::min(-0.0, 0.0) : " << std::min(-zero, zero) << endl;
}
Çıktı olarak şunu alırız
$ g++ --std=c++17 ./test.cpp
$ ./a.out
std::min(1.0, NaN) : 1
std::min(NaN, 1.0) : nan
std::min_element(1.0, 0.0, NaN) : 0
std::min_element(NaN, 1.0, 0.0) : nan
std::min(0.0, -0.0) : 0
std::min(-0.0, 0.0) : -0

1. Programlama Dillerinde NaN
C++
std::isnan kulllanılır.

Java
Geçersiz bir sayıyı temsil etmek için Float.NaN ve Double.NaN kullanılır

C#
Java ile aynıdır.

2. NaN Kontrolü
Bir sayının NaN olup olmadığı şöyle kontrol edilebilir. Maksimum exponent için 0X7FF yani 11 bit kullanılıyor. Geriye kalan 53 bit 0XFF ile alınıyor. Eğer Exponent 0X7FF ve mantissa sıfırdan büyükse sayımız 0X7FF0000000000000'ten büyüktür.
public unsafe static bool IsNaN(double d)
{
  return (*(UInt64*)(&d) & 0x7FFFFFFFFFFFFFFFL) > 0x7FF0000000000000L;
}
veya şöyle yapılabilir. Aşağıdaki kod exponent ve mantissa'yı daha net gösteriyor.
inline constexpr bool
isnan(const std::uint32_t bits) noexcept
{
  constexpr std::uint32_t exponent = UINT32_C(0x7f800000);
  constexpr std::uint32_t mantissa = UINT32_C(0x007fffff);
  return ((bits & exponent) == exponent) && ((bits & mantissa) != 0);
}

inline constexpr bool
isnan(const std::uint64_t bits) noexcept
{
  constexpr std::uint64_t exponent = UINT64_C(0x7ff0000000000000);
  constexpr std::uint64_t mantissa = UINT64_C(0x000fffffffffffff);
  return ((bits & exponent) == exponent) && ((bits & mantissa) != 0);
}

Hiç yorum yok:

Yorum Gönder