NaN ကဘာလို့ NaN နဲ့မတူတာလဲ

တရက်က ကျွန်တော့်ကို ဒီမေးခွန်းမေးလာတယ်။ အများစုက NaN == NaN စစ်ရင် false ထွက်တာကို JavaScript ရဲ့ပေါက်ကရဖြစ်နေကျဟာတွေထဲက တခုလို့ပဲ ထင်ကြပေမယ့် တကယ်က အဲလိုမဟုတ်ပါဘူး။

NaN ဆိုတာ value မဟုတ်ပဲ flag ပဲဖြစ်တယ်။ ဒါမှမဟုတ် standard တခုကို လိုက်နာပြီး အဲ standard က သတ်မှတ်ထားတဲ့အတိုင်း NaN နဲ့ NaN မတူဘူးလို့ လုပ်ထားရတာပါလို့ အလွယ်ဖြေလိုက်လို့ ရပေမယ့် ကွန်ပျူတာတွေ အလုပ်လုပ်ပုံ အခြေခံနဲ့ ဒသမကိန်းတွေ ကွန်ပျူတာထဲ ဘယ်လိုရှိနေလဲဆိုတာပါ ရှင်းပြလိုက်ပေးလိုက်ရင် ပိုကောင်းမယ်လို့ထင်တယ်။

ကွန်ပျူတာတွေက ၁ ဒါမှမဟုတ် ၀ ဆိုတဲ့ (အဖွင့်နဲ့အပိတ်) binary စနစ်အပေါ် အခြေခံထားတာဖြစ်လို့ 0.5 (တဝက်)၊ 0.25 (တစိတ်) ဒီလိုဒသမကိန်းတွေနဲ့ အလုပ်လုပ်ဖို့ ခက်ပါတယ်။ ဒသမကိန်းတွေကို 0 နဲ့ 1 ပဲသုံးပြီး ဘယ်လိုဖော်ပြမလဲလို့မေးရင် သမရိုးကျ memory bit တွေကို စီတဲ့နည်းနဲ့ ဘယ်လိုမှ မရနိုင်ပါဘူး။

ဥပမာ ကွန်ပျူတာက တန်ဖိုး 1 ကို 0000 0000 0000 0000 0000 0000 0000 0001 ဆိုပြီး 32 bits unsigned interger အနေနဲ့ သိမ်းလို့ရတယ်။ 1 ကနေ 2 ဖြစ်ချင်တာဆိုရင်လည်း နောက်ထပ် 1 နဲ့ 0 ကိုဆက်လှန်ပြီး 0000 0000 0000 0000 0000 0000 0000 0010 လို့ ပြောင်းသိမ်းရင်း နောက်ဆုံးမှာ 1111 1111 1111 1111 1111 1111 1111 1111 ဖြစ်လာတဲ့အထိ ဆက်သိမ်းသွားလို့ရတယ်။ decimal တန်ဖိုးနဲ့ဆိုဒါ 4,294,967,295 ပေါ့။

ဒီအချက်က binary system တွေအတွက် ဒသမကိန်းတွေကို သိမ်းရတာ ခက်စေတယ်။ ဘာလို့လဲဆိုရင် transistor လေးတွေအတွက်က low voltage (0) နဲ့ high voltage (1) ရယ်ပဲရှိတယ်။ true ဒါမှမဟုတ် false ပေါ့။ maybe ဆိုပြီး ဟိုလိုလိုဒီလိုလို ကြား state မရှိဘူး။ ဒီတော့ ကျွန်တော်တို့ ဒသမ ပြဿနာကို ဖြေရှင်းဖို့ နည်းလမ်းတခုက scientific notation သုံးတာပဲ။ 12,300 ကို scientific notation နဲ့ 1.23 x 10⁴ (1.23 times 10 to the power of 4 လို့ဖတ်ရ) လို့ရေးကြတယ်။ -5.67 × 10^(-3) ဆိုရင် -0.00567 ပေါ့။ ဒီ format မှာ အဓိက ၄ ပိုင်းရှိတယ်။

ပထမက significand (ဥပမာ 1.23, 5.67 ဆိုတဲ့ကိန်းတွေ)၊ ဒုတိယက base (ဒီမှာဆို 10) နောက်တခုက exponent (4 တို့ -3 တို့လို) ပြီးတော့ အပေါင်းအနုတ်ဆိုတဲ့ sign ဆိုပြီး ၄ ပိုင်းရှိမယ်။ ဆိုတော့ ကျွန်တော်တို့ ဒီ notation ကို binary စနစ်ထဲသာ ထည့်နိုင်မယ်ဆိုရင် ဒသမကိန်းတွေ သိမ်းလို့ရပြီပေါ့လေ။ တကယ်လည်း အဲလိုလုပ်ခဲ့တာပါပဲ။

ဒီကနေ့ ကျွန်တော်တို့ ကွန်ပျူတာထဲက ဒသမကိန်းတွေရဲ့ နောက်ကွယ်က 1 နဲ့ 0 တွေက ပုံမှန် integer တွေလို သမရိုးကျ bit စီနည်းနဲ့ စီထားတဲ့ ကိန်းတွေ မဟုတ်ပဲ exponent, significand နဲ့ sign ဒီ ၃ ခုကို ဖော်ပြတဲ့ 1 နဲ့ 0 အဖြစ်ရှိနေတယ်။ ဘာလို့ base မပါလဲဆိုတာက base အတွက် 2 ကိုသုံးဖို့ သတ်မှတ်လိုက်ကြလို့ပါ။ ဒီတော့ 12,300, -0.00567 စသဖြင့် real number ရအောင် ပြန်တွက်ရမယ့် ပုံသေနည်းက (-1)^(sign) x significand x 2^(exponent) ဆိုပြီးဖြစ်လာတယ်။

ဒီပုံစံမှာ ဒသမကိန်း 1.5 (1.5 x 2⁰) ကို ကွန်ပျူတာက 0011 1111 1100 0000 0000 0000 0000 0000 အဖြစ် သိမ်းလို့ရသွားတယ်။ ပထမဆုံး bit ဖြစ်တဲ့ 0 က အပေါင်းအနုတ်ကို ပြတဲ့ sign ဖြစ်ပြီး 0 ဆိုတာ အပေါင်းလို့ ဆိုလိုတယ်။ သင်္ချာ အတိအကျဆိုရင် (-1)^(sign) ပေါ့။ (-1)^0 က 1 မို့အပေါင်းဖြစ်သွားတယ်။ သူ့နောက်က 8 bits (011 1111 1) က 127 ပေမယ့်လည်း အဲဒါ တကယ်က exponent တန်ဖိုး 0 ကို encode လုပ်ထားတာ။ significand အတွက် ကျန်တဲ့ 23 bits (100 0000 0000 0000 0000 0000) ကလည်း decimal တန်ဖိုး 4,194,304 ဖြစ်နေပေမယ့် သူ့ကို decode လိုက်ရင် 1.5 ရလာမယ်။

အဓိကအချက်က ဒီ bit တွေကို ကျွန်တော်တို့ decode လုပ်တဲ့အခါ တခါတလေမှာ အဓိပ္ပါယ်ဖော်လို့မရတဲ့ 1 နဲ့ 0 အတွဲတွေကို ကြုံကိုကြုံရနိုင်တယ်ဆိုတာပါပဲ။ အဲအခါကျ ဒီ bit တွေကို သင်္ချာနည်းအရ အဓိပ္ပါယ်ရှိတဲ့ ဒသမကိန်း ပြန်ဖြစ်အောင် ဖော်လို့ မရပါဘူး။ အဲဒီလို decode မရတဲ့ pattern တွေက အခု 32 bits (0000 0000 0000 0000 0000 0000 0000 0000) မှာကိုပဲ သန်းနဲ့ချီပြီး ရှိပါတယ်။ ဒါကြောင့် သင်္ချာနည်းအရ အဓိပ္ပါယ်မရှိလို့ NaN ထွက်သွားတဲ့ calculation ၂ ခုရဲ့အောက်ခြေက bit တွေက မဖြစ်မနေ တူနေမှာမျိုး မဟုတ်ပါဘူး။ NaN ချင်းတူတာတောင်မှ 1 နဲ့ 0 အတွဲတွေက တူမယ်လို့ ယူဆလို့မရပါဘူး။

ဒီနေ့​​ခေတ် programming language အများစုက floating point အတွက် ဒီ IEEE 754 standard နဲ့အညီ implement လုပ်ကြတယ်။ အဲဒီထဲမှာ JavaScript ကလည်း ချွင်းချက် မဟုတ်ဘူး။ ဒါကြောင့် NaN == NaN ကတော့ JavaScript မှမဟုတ်ပဲ Python, Java, C/C++, Go, Ruby, Swift, Rust, Kotlin, PHP, Perl, Haskell, Scala, TS နဲ့တခြားသော IEEE 754 standard ကိုလိုက်နာထားတဲ့ language တိုင်းမှာ false ထွက်နေမှာပဲ။

ဒီလောက်ဆို NaN ဘာလို့ existential crisis ဖြစ်နေလဲ အကြမ်းဖျင်း နားလည်လောက်ပြီ ထင်ပါတယ်။