3.2 The Floating-Point Problem
ถ้าคุณเคยเห็น 0.1 + 0.2 = 0.30000000000000004 แล้วงง — ยินดีต้อนรับ นี่ไม่ใช่ bug มันคือข้อจำกัดทางกายภาพของคอมพิวเตอร์
Callback: จำได้ไหม?
Section titled “Callback: จำได้ไหม?”ใน Module 0.5 เราเคยเห็นว่า 0.1 + 0.2 ไม่เท่ากับ 0.3 — ตอนนั้นเรารู้ว่ามันเกิดขึ้น ตอนนี้เราจะเข้าใจว่า ทำไม และที่สำคัญกว่า — จะป้องกันยังไง
ทำไมมันเกิดขึ้น
Section titled “ทำไมมันเกิดขึ้น”ลองนึกภาพ เศษส่วนที่ไม่มีวันจบ:
1/3ในฐานสิบ =0.333333...ไม่มีวันเขียนจบ0.1ในฐานสอง =0.0001100110011...ไม่มีวันจบเหมือนกัน!
คอมพิวเตอร์มี 64 bits เก็บตัวเลข — มันต้อง ตัดทิ้ง ที่ไหนสักที่ ทุกครั้งที่ตัดทิ้ง = error เล็กๆ สะสม
พิมพ์สูตรเหล่านี้:
=0.1 + 0.2 → 0.3 (ดูถูก!)=TEXT(0.1 + 0.2, "0.00000000000000000") → 0.30000000000000004 (ไม่ถูก!)=(0.1 + 0.2) = 0.3 → FALSE!Sheets ซ่อน error ด้วยการปัดเศษตอนแสดงผล — แต่ค่าจริงผิดอยู่ ถ้าคุณเช็คด้วย = จะเจอ FALSE
วิธีแก้ใน Sheets:
=ROUND(0.1 + 0.2, 2) = 0.3 → TRUEใช้ ROUND() ก่อนเปรียบเทียบเสมอ ถ้าเกี่ยวกับเงิน
#include <stdio.h>int main() { float f = 0.1f + 0.2f; double d = 0.1 + 0.2;
printf("float: %.20f\n", f); // 0.30000001192092895508 printf("double: %.20f\n", d); // 0.30000000000000004441
// float error ใหญ่กว่า double มาก! // แต่ทั้งคู่ไม่ใช่ 0.3 พอดี
// วิธีแก้: เก็บเป็น integer cents int price_cents = 4990; // แทน 49.90 บาท int tax_cents = 349; // แทน 3.49 บาท int total = price_cents + tax_cents; printf("total: %d.%02d baht\n", total / 100, total % 100); // total: 53.39 baht — แม่นยำ! return 0;}C ไม่มี decimal type built-in — วิธีที่ปลอดภัยคือเก็บเป็น integer cents แล้วแปลงตอนแสดงผล
# ปัญหาprint(0.1 + 0.2) # 0.30000000000000004print(0.1 + 0.2 == 0.3) # False!
# วิธีแก้ 1: decimal.Decimalfrom decimal import Decimala = Decimal('0.1') # ต้องใส่เป็น string!b = Decimal('0.2')print(a + b) # 0.3 — แม่นยำ!print(a + b == Decimal('0.3')) # True
# ระวัง! Decimal(0.1) ≠ Decimal('0.1')print(Decimal(0.1))# 0.1000000000000000055511151231257827021181583404541015625# ↑ เพราะ 0.1 ถูกแปลงเป็น float ก่อนเข้า Decimal
# วิธีแก้ 2: integer centsprice = 4990 # 49.90 บาทtax = 349 # 3.49 บาทtotal = price + taxprint(f"{total // 100}.{total % 100:02d} baht") # 53.39 baht-- ปัญหา: REAL / DOUBLE PRECISION = floatSELECT CAST(0.1 AS REAL) + CAST(0.2 AS REAL);-- ผลลัพธ์ไม่ใช่ 0.3 พอดี
-- วิธีแก้: NUMERIC (exact decimal)SELECT CAST(0.1 AS NUMERIC(10,2)) + CAST(0.2 AS NUMERIC(10,2));-- 0.30 — แม่นยำ!
-- ตัวอย่างจริง: ตารางราคาสินค้าCREATE TABLE invoices ( id INTEGER, subtotal NUMERIC(12, 2), -- ไม่ใช่ REAL! tax NUMERIC(12, 2), total NUMERIC(12, 2));
INSERT INTO invoices VALUES (1, 99.99, 7.00, 106.99);
-- ตรวจสอบSELECT subtotal + tax = total FROM invoices;-- true — เพราะใช้ NUMERIC ไม่ใช่ floatสรุป Decision Table
Section titled “สรุป Decision Table”| สถานการณ์ | ห้ามใช้ | ใช้แทน |
|---|---|---|
| ราคาสินค้า | REAL / float | NUMERIC(10,2) / Decimal |
| ยอดรวมใบแจ้งหนี้ | DOUBLE PRECISION | NUMERIC / integer cents |
| เปอร์เซ็นต์ภาษี | float | Decimal / NUMERIC(5,4) |
| น้ำหนักสินค้า | — | float ใช้ได้ (ไม่ต้องแม่นยำ 100%) |
| คะแนนเฉลี่ย | — | float ใช้ได้ |