למה מספרים שלמים גדולים מאבדים דיוק
שורה תחתונה: מספרים שלמים מעל 2^53-1 מאבדים דיוק ב-JSON.parse. מזהי snowflake, מספרי הזמנה ו-bigints ממסד נתונים כולם נתקלים בזה, אז שמרו אותם כמחרוזות לאורך כל הדרך.

שורה תחתונה: התייחסו למזהי snowflake, מספרי הזמנה ומפתחות ראשיים מסוג bigint כמחרוזות לאורך סריאליזציה, העברה ופירוס; שמרו טיפוס מספרי רק כשמוכח שהשדה נשאר בטווח 2^53-1, ואז אכפו זאת עם אילוץ maximum ב-Schema.
כי הנזק קורה בזמן הפירוס, בשקט.
JSON.parse('{"id": 9007199254740993}');
// { id: 9007199254740992 }
הספרה החסרה נעלמה; אין מה לשחזר.
מאיפה מגיע 2^53
ל-JavaScript יש טיפוס מספרי אחד: IEEE 754 double precision, עם 53 ביטים של significand (אחד מרומז, 52 מפורשים). מספרים שלמים מדויקים נעצרים אפוא ב-±(2^53-1):
-9007199254740991 .. 9007199254740991
מעל הקו הזה, ערכים סמוכים ניתנים לייצוג במרווחים של 2, 4, 8. 9007199254740993 הוא אי-זוגי, ובגודל הזה כל ערך שניתן לייצוג הוא זוגי, כך שהוא מתעגל ל-9007199254740992.
השדות שנפגעים
| שדה | ספרות | סיכון |
|---|---|---|
| מזהי snowflake | 18–19 | גבוה, הספרות האחרונות משתנות |
| מספרי הזמנה / עסקה | 16–20 | גבוה |
| מפתחות ראשיים מסוג bigint במסד נתונים | 19 | גבוה |
| כסף ביחידות משנה | — | נמוך |
| חותמות זמן במילישניות | 13 | בטוח |
החוט המשותף: אלה מזהים, לא כמויות. מזהה מועבר, ולעולם לא מחובר או מוכפל, כך שמחרוזת היא הטיפוס הנכון.
ארבעה תיקונים
סריאלזו כמחרוזת. הבקאנד ממיר שדות bigint לפני כתיבת התשובה. פשוט ועמיד; תעדו שהשדות האלה הם מחרוזות.
תנו לסריאליזטור לטפל בזה. ToStringSerializer של Jackson עבור Long, תג json:",string" של Go. אותו רעיון: הרחיקו את הערך מנתיב מספרי.
פרסו עם תמיכת BigInt. json-bigint וספריות דומות. שימו לב ש-BigInt לא יכול להתערבב בחשבון עם Number, ו-JSON.stringify דוחה אותו.
הגבילו את הטווח. אם השדה באמת לא יכול לעבור 2^53, הוסיפו maximum ב-Schema כך שנתונים מחוץ לטווח ייכשלו באימות במקום להיכשל בשקט בדפדפן.
איך לדעת אם יש לכם את הבאג הזה
קחו מזהה ידוע ועברו איתו הלוך ושוב: סריאלזציה, העברה, פירוס, השוואה תו אחר תו. או בדקו את גוף התשובה הגולמי — מספר בן 19 ספרות בלי גרשיים שמגיע עם הספרות האחרונות השגויות הוא כמעט בוודאות אובדן בזמן פירוס.
שאלות ותשובות
למה JSON.parse משנה את הספרות האחרונות בלי שגיאה?
ל-JavaScript יש טיפוס מספרי אחד, IEEE 754 double precision, כך שמספרים שלמים מעל 2^53-1 יכולים לנחות רק על הערך הסמוך שניתן לייצוג. JSON.parse מתייחס לזה כהמרה מספרית רגילה: הוא לא זורק ולא מזהיר.
איך החזית יכולה לשמור מספרים שלמים גדולים ללא שינוי?
השתמשו בספרייה כמו json-bigint, כי JSON.parse לא יכול. המחיר הוא ש-BigInt לא יכול להתערבב בחשבון עם Number ו-JSON.stringify דוחה אותו, כך שגם הקוד שמסביב צריך להשתנות.
האם תיעוד ה-API צריך לציין שהשדות האלה הם מחרוזות?
כן. סריאליזציה למחרוזות היא התיקון הפשוט והעמיד ביותר, אבל הוא עובד רק אם צרכנים מתייחסים לערך כטקסט, לכן טיפוס השדה חייב להיכתב בחוזה ה-API.