JSON Toolkit

למה מספרים שלמים גדולים מאבדים דיוק

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

בינוניכ-4 דקות קריאהפורסם 2026-10-30עודכן 2026-11-04
למה מספרים שלמים גדולים מאבדים דיוק ב-JSON ואיך לשמור אותם

שורה תחתונה: התייחסו למזהי 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.

קריאה נוספת