טיפוס מספר אחד לכל דבר (כמעט)
רוב השפות נותנות לכם טיפוסים נפרדים למספרים שלמים ולשברים. JavaScript, מבחינה היסטורית, נתנה לכם טיפוס אחד: Number. בין אם כתבתם 42, 3.14 או -0.001, קיבלתם את אותו טיפוס פרימיטיבי: float בדיוק כפול של 64 ביט לפי IEEE 754.
זה נוח: אין המרות בין int ל-float, ואין גלישה ב-2^31. אבל לייצוג כ-float יש השלכות, והן מפילות מתחילים לעתים קרובות. טיפוס מספרי שני, BigInt, נוסף ב-2020 כדי לטפל במקרים שבהם Number לא מספיק.
ההפתעה של Floating-Point
הריצו את זה:
השורה הראשונה מדפיסה 0.30000000000000004. השנייה מדפיסה false. זו לא מוזרות של JavaScript: Python, Java, C וכל שפה שמשתמשת ב-float לפי IEEE 754 מתנהגות אותו דבר.
הסיבה: אי אפשר לייצג את 0.1 ואת 0.2 בדיוק בבינארי, בדיוק כמו שאי אפשר לכתוב 1/3 בדיוק בעשרוני. נשמר הקירוב הבינארי הקרוב ביותר, ושגיאות זעירות מצטברות. המודל המנטלי: התייחסו לערכי Number עם נקודה עשרונית כאל קירובים שבמקרה קרובים מאוד למה שכתבתם.
לכסף, אל תשמרו $19.99 בתור 19.99. שמרו סנטים כמספרים שלמים, 1999, ועצבו בזמן התצוגה. זה ההרגל הטוב ביותר להתחמקות מבאגים של float.
השוואת שברים בבטחה
מכיוון ששוויון לא אמין, השוו עם סבולת כשצריך:
Number.EPSILON הוא ההפרש הקטן ביותר בין 1 לבין המספר הבא שאפשר לייצג: סבולת ברירת מחדל סבירה לערכים הקרובים ל-1. לגדלים גדולים מאוד או קטנים מאוד תרצו סבולת שגדלה יחד עם הקלט.
טווח המספרים השלמים הבטוח
מספרים שלמים עד גודל מסוים כן מדויקים ב-float של 64 ביט. מעבר לזה מתחילים לאבד דיוק, ביט אחרי ביט:
2^53 - 1 הוא המספר השלם האחרון שכל המספרים השלמים מתחתיו ניתנים לייצוג. מעבר לו, חלק מהמספרים השלמים פשוט לא קיימים בטיפוס Number: הם מעוגלים לשכן הקרוב ביותר. זה באג שקט של השחתת נתונים שמחכה לקרות אם אתם מפענחים מזהים של 64 ביט ממסד נתונים כמספרי JSON רגילים.
הכירו את BigInt
BigInt הוא טיפוס פרימיטיבי נפרד למספרים שלמים בדיוק שרירותי. יוצרים אותו על ידי הוספת n לליטרל של מספר שלם, או על ידי קריאה ל-BigInt(...):
ל-BigInt אין גבול עליון מלבד הזיכרון הזמין. זה הכלי הנכון עבור:
- מזהים ממסד נתונים או מזהי snowflake של Twitter/X שעוברים את
2^53. - חישובים קריפטוגרפיים.
- כל אריתמטיקה של מספרים שלמים שבה תוצאות מדויקות חשובות יותר ממהירות גולמית.
זה לא הכלי הנכון למונים יומיומיים, לאינדקסים של מערכים או לכסף בסנטים: Number רגיל מהיר יותר ועובד עם כל API בשפה.
אריתמטיקה של BigInt
כל האופרטורים הרגילים עובדים, כל עוד שני האופרנדים הם BigInt:
חילוק נחתך לכיוון אפס: אין BigInt עם שבר. אם אתם צריכים שבר, חזרתם לתחום של Number (או לספרייה עשרונית).
אל תערבבו טיפוסים
הכלל שמפיל אנשים: אי אפשר לערבב Number ו-BigInt באותו ביטוי.
ההשוואה היא החריג היחיד: <, > ו-== ממירים מעבר לגבול:
אז == רואה אותם כשווים, ו-=== לא. אם כבר אימצתם === בכל מקום (וכדאי), התייחסו להשוואות מספריות בין שני הטיפוסים כסימן לבעיה בתכנון: בחרו צד אחד והמירו.
המרה ביניהם
שתי המרות, שתי מלכודות:
המעבר Number → BigInt קפדני: שברים ו-NaN זורקים שגיאה. המעבר BigInt → Number מתירני אבל מאבד מידע: כל מה שמעל MAX_SAFE_INTEGER מעוגל. אם אתם ממירים BigInt שקיבלתם משרת, שאלו את עצמכם אם אתם באמת צריכים לעשות את זה.
ערכי Number מיוחדים
כבר שאנחנו כאן, שלושה ערכים בטיפוס Number שאינם מספרים במובן המתמטי:
Infinity ו--Infinity מופיעים כשמחלקים באפס או חורגים מטווח ה-float. NaN ("not a number", לא מספר) מופיע כשפעולה אריתמטית לא מפיקה תוצאה בעלת משמעות.
NaN ידוע בכך שהוא לא שווה לעצמו: זה חלק מתקן IEEE 754, לא באג של JS. השתמשו ב-Number.isNaN(x) כדי לבדוק. הפונקציה הגלובלית הישנה isNaN ממירה קודם את הארגומנט שלה, וזה נותן תשובות שגויות (isNaN("hello") מחזירה true). תמיד העדיפו את הגרסה Number.isNaN.
פענוח מספרים ממחרוזות
קלט משתמש ומספרים ב-JSON מגיעים לעתים קרובות כמחרוזות. שלוש דרכים להמיר:
Number() קפדנית: כל דבר שאינו מספרי נותן NaN, חוץ מהמחרוזת הריקה ומרווחים, שנותנים 0. parseInt ו-parseFloat סלחניות: הן קוראות כמה שהן יכולות ועוצרות. בחרו את מה שמתאים לכוונה שלכם, ובדקו NaN לפני שמשתמשים בתוצאה.
לפענוח BigInt ממחרוזות, השתמשו ב-BigInt("123"): היא קפדנית וזורקת שגיאה על קלט לא תקין.
ספר כללים קצר
- למונים, מתמטיקה, קואורדינטות ורוב המספרים היומיומיים: השתמשו ב-
Number. - לכסף: המירו לסנטים שלמים והשתמשו ב-
Number, או פנו לספרייה עשרונית. - למספרים שלמים גדולים מ-
2^53(מזהים ממסד נתונים, קריפטו, קומבינטוריקה): השתמשו ב-BigIntעם הסיומתn. - השוו שברים עם סבולת, לא עם
===. - בדקו תוצאות לא תקינות עם
Number.isNaNו-Number.isFinite, לא עם הגרסאות הגלובליות. - אל תערבבו
Numberו-BigIntבאותו ביטוי: המירו במפורש.
הבא בתור: null מול undefined
ל-JavaScript יש שתי דרכים להגיד "אין ערך", null ו-undefined, והן לא ניתנות להחלפה זו בזו. בהמשך: מה כל אחת מהן אומרת, במה הן שונות ובאיזו להשתמש מתי.
שאלות נפוצות
למה 0.1 + 0.2 לא שווה ל-0.3 ב-JavaScript?
כי טיפוס ה-Number של JavaScript הוא float של 64 ביט לפי IEEE 754, ואי אפשר לייצג את 0.1 ואת 0.2 בדיוק בבינארי. התוצאה היא 0.30000000000000004. זה לא באג של JavaScript: זה קורה גם ב-Python, ב-Java ובכל שפה אחרת שמשתמשת באותו פורמט float. לכסף, המירו למספרים שלמים (אגורות או סנטים) או השתמשו בספרייה עשרונית.
מה זה BigInt ב-JavaScript ומתי כדאי להשתמש בו?
BigInt הוא טיפוס פרימיטיבי מספרי נפרד למספרים שלמים שגדולים מ-Number.MAX_SAFE_INTEGER (2^53 פחות 1). יוצרים אותו עם n בסוף, כמו 9007199254740993n, או עם BigInt(value). השתמשו בו למזהים של 64 ביט ממסד נתונים, לקריפטוגרפיה, או לכל חישוב במספרים שלמים שבו הדיוק חשוב יותר מהמהירות.
אפשר לערבב Number ו-BigInt ב-JavaScript?
לא. 1n + 1 זורק TypeError: Cannot mix BigInt and other types. המירו במפורש עם BigInt(n) או Number(b). אופרטורי השוואה כמו < ו-== כן עובדים בין השניים, אבל === מחזיר false כי הטיפוסים שונים.