JSON Toolkit

ทำไมจำนวนเต็มขนาดใหญ่ถึงสูญเสียความแม่นยำ

สรุปสำคัญ: จำนวนเต็มที่เกิน 2^53-1 สูญเสียความแม่นยำใน JSON.parse snowflake ID, หมายเลขคำสั่งซื้อ และ bigint ในฐานข้อมูลล้วนเจอปัญหานี้ จึงควรเก็บเป็น string ตลอดทั้งเส้นทาง

ระดับกลางอ่านประมาณ 4 นาทีเผยแพร่ 2026-10-30อัปเดต 2026-11-04
ทำไมจำนวนเต็มขนาดใหญ่สูญเสียความแม่นยำใน JSON และจะเก็บไว้อย่างไร

สรุปสั้น ๆ: ให้ถือ snowflake ID, หมายเลขคำสั่งซื้อ และ primary key แบบ bigint เป็น string ตลอดการ serialize การส่งผ่าน และการ parse; ใช้ชนิดตัวเลขเฉพาะเมื่อพิสูจน์ได้ว่าฟิลด์อยู่ในช่วง 2^53-1 และบังคับด้วย constraint maximum ใน Schema

เพราะความเสียหายเกิดขึ้นระหว่างการ parse อย่างเงียบ ๆ

JSON.parse('{"id": 9007199254740993}');
// { id: 9007199254740992 }

หลักที่หายไปแล้ว ไม่มีอะไรกู้คืนได้

2^53 มาจากไหน

JavaScript มีชนิดตัวเลขเดียว: IEEE 754 double precision ที่มี significand 53 บิต (โดยนัยหนึ่งบิต ชัดเจน 52 บิต) จำนวนเต็มที่แทนได้แม่นยำจึงหยุดที่ ±(2^53-1):

-9007199254740991 .. 9007199254740991

เหนือเส้นนั้น ค่าที่แทนได้ติดกันจะห่างกัน 2, 4, 8 9007199254740993 เป็นจำนวนคี่ และที่ขนาดนั้นค่าที่แทนได้ทุกตัวเป็นจำนวนคู่ มันจึงปัดไปเป็น 9007199254740992

ฟิลด์ที่โดนผลกระทบ

ฟิลด์ จำนวนหลัก ความเสี่ยง
snowflake ID 18–19 สูง หลักท้ายเปลี่ยน
หมายเลขคำสั่งซื้อ / ธุรกรรม 16–20 สูง
primary key แบบ bigint ในฐานข้อมูล 19 สูง
เงินในหน่วยย่อย — ต่ำ
timestamp ระดับมิลลิวินาที 13 ปลอดภัย

สิ่งที่เหมือนกัน: ค่าเหล่านี้เป็น identifier ไม่ใช่ปริมาณ identifier ถูกส่งผ่าน ไม่เคยถูกบวกหรือคูณ string จึงเป็นชนิดที่ถูกต้อง

วิธีแก้สี่แบบ

serialize เป็น string backend แปลงฟิลด์ bigint ก่อนเขียน response ง่ายและยั่งยืน; ให้ระบุในเอกสารว่าฟิลด์เหล่านี้เป็น string

ให้ serializer จัดการ ToStringSerializer ของ Jackson สำหรับ Long, tag json:",string" ของ Go แนวคิดเดียวกัน: ให้ค่าอยู่นอกเส้นทางตัวเลข

parse ด้วยการรองรับ BigInt json-bigint และไลบรารีที่คล้ายกัน โปรดทราบว่า BigInt ผสมกับการคำนวณด้วย Number ไม่ได้ และ JSON.stringify ปฏิเสธมัน

จำกัดช่วงค่า ถ้าฟิลด์ไม่สามารถเกิน 2^53 ได้จริง ให้เพิ่ม maximum ใน Schema เพื่อให้ข้อมูลนอกช่วงล้มเหลวตอน validation แทนที่จะล้มเหลวอย่างเงียบ ๆ ในเบราว์เซอร์

จะรู้ได้อย่างไรว่าคุณมี bug นี้

เอาค่า ID ที่รู้มา round-trip: serialize, ส่งผ่าน, parse, เทียบทีละอักขระ หรือตรวจ raw response body — ตัวเลข 19 หลักที่ไม่มีเครื่องหมายคำพูดและมาถึงโดยหลักท้ายผิด เกือบแน่นอนว่าเสียหายตอน parse

คำถามที่พบบ่อย

ทำไม JSON.parse เปลี่ยนหลักท้ายโดยไม่ error?

JavaScript มีชนิดตัวเลขเดียวคือ IEEE 754 double precision จำนวนเต็มที่เกิน 2^53-1 จึงลงได้เพียงค่าที่แทนได้ใกล้ที่สุด JSON.parse ถือว่านั่นเป็นการแปลงตัวเลขปกติ: มันไม่ throw และไม่เตือน

frontend จะเก็บจำนวนเต็มขนาดใหญ่ไว้ครบได้อย่างไร?

ใช้ไลบรารีอย่าง json-bigint เพราะ JSON.parse ทำไม่ได้ ต้นทุนคือ BigInt ผสมกับการคำนวณด้วย Number ไม่ได้ และ JSON.stringify ปฏิเสธมัน โค้ดรอบข้างจึงต้องเปลี่ยนด้วย

เอกสาร API ควรระบุว่าฟิลด์เหล่านี้เป็น string ไหม?

ควร การ serialize เป็น string เป็นวิธีที่ง่ายและยั่งยืนที่สุด แต่จะได้ผลก็ต่อเมื่อผู้ใช้ถือค่าเป็นข้อความ ชนิดของฟิลด์จึงต้องถูกเขียนลงใน API contract

อ่านเพิ่มเติม