GORAGOD.com

ปัญหาการใช้งานข้อมูลชนิด float บน MySQL เมื่อ select ค่ากลับออกมาแล้วได้ผลลัพท์ไม่เหมือนตอนที่ใส่เข้าไป

วันนี้ได้รับรายงานว่าเจอข้อผิดพลาดจากฐานข้อมูล MariaDB บนข้อมูลชนิด float โดยที่เมื่อใส่จำนวนเต็ม 1223484 หรือ 3660208 ในตอนที่ select ข้อมูลกลับออกมา กลับได้ค่าไม่เหมือนเดิม

ปัญหานี้เกิดกับข้อมูลชนิด FLOAT เท่านั้นนะครับ ผมเลยออกแบบการทดสอบตามนี้

สร้างตารางสำหรับทดสอบ
CREATE TABLE `test` (
  `decimal` decimal(10,0) NOT NULL,
  `float` float NOT NULL,
  `double` double NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


และใส่ข้อมูล 1223484 และ 3660208 ลงในตาราง
INSERT INTO `test` (`decimal`, `float`, `double`) VALUES
('1223484', 1223484, 1223484),
('3660208', 3660208, 3660208);


มาดูผลลัพท์กัน

จากรูปจะเห็นว่า ข้อมูลชนิด FLOAT เมื่อ select ออกมาแล้วได้ค่าไม่เหมือนตอนที่ใส่เข้าไป ในขณะที่ DECIMAL และ DOUBLE ไม่มีปัญหานี้

เอาจริงๆ ปัญหานี้ควรเกิดกับจุดทศนิยมมากกว่า เพราะมันเป็นข้อจำกัดที่รู้กันอยู่แล้ว และไม่ควรเกิดกับตัวเลขจำนวนเต็ม และยังอาจจะมีตัวเลขอื่นๆอีกที่สร้างปัญหานี้ได้ ดังนั้นผมแนะนำให้หลีกเลี่ยงปัญหานี้ด้วยการใช้ข้อมูลชนิดอื่นแทน ซึ่งผมเลือกเป็น DOUBLE หรือ REAL

วิธีการเปลี่ยนชนิดของข้อมูล มี 2 แนวทาง
แนวทางแรกมีขั้นตอนดังนี้
  1. เพิ่มคอลัมน์ใหม่เป็นชนิดข้อมูลที่ต้องการลงในตาราง 
    ALTER TABLE `test` ADD `float2` REAL NOT NULL AFTER `float`;

    เราจะได้คอลัมน์ float2 ชนิด REAL เพิ่มมา
  2. อัปเดทข้อมูลจากคอลัมน์ float ไปยัง float2
    UPDATE `test` SET `float2`=`float`


    จะเห็นว่าข้อมูลในคอลัมน์ float2 ที่ copy มาจาก float เป็นข้อมูลที่ถูกต้อง แต่เมื่อ select ออกมามันผิด
  3. สุดท้าย ลบคอลัมน์เก่า และเปลี่ยนชื่อ float2 ให้เป็น float แทน
    ALTER TABLE `test` DROP `float`;
    ALTER TABLE `test` CHANGE `float2` `float` REAL NOT NULL;


แนวทางแรกเหมาะกับคอลัมน์ที่เก็บเป็นเลขจำนวนเต็มเท่านั้นนะครับ แต่ไม่เหมาะกับคอลัมน์ที่เก็บเลขทศนิยม เนื่องจากข้อมูล FLOAT เมื่อแปลงเป็น DOUBLE แล้วจะได้ค่าที่เป็นทศนิยมที่ยาวกว่า และได้ค่าไม่เท่าเดิม ดังนั้นวิธีการแปลงด้วยวิธีแรกอาจทำให้ค่าที่จัดเก็บผิดพลาด (ในระดับจุดทศนิยม) ได้
วิธีการแปลงของผมสำหรับเคสแบบนี้ คือ
  1. เปลี่ยนคอลัมน์เป็นชนิด VARCHAR(50) ซึ่งความยาว 50 ขึ้นกับข้อมูลที่จัดเก็บนะครับ วิธีนี้จะทำให้ได้ค่าที่จัดเก็บเป็นตัวเลขเดิม
  2. เปลี่ยนข้อลัมน์เป็นชนิดที่ต้องการ (REAL หรือ DOUBLE) อีกที