ข้าม​ไป​ยัง​เนื้อหา

การ​หาร​ที่​ปัด​คนละ​ทาง และ shard ที่​แบ่ง​ไม่​ตรง​กัน

ระบบ​ออ​เด​อร์​ของ​คุณ​แบ่ง​งาน​ออก​เป็น​สอง shard ด้วย​นิพจน์​ที่​สั้น​ที่สุด​เท่า​ที่​จะ​เขียน​ได้ คือ id % 2 ทุก service ใน​ระบบ copy บรรทัด​นี้​ไป​ใช้​เหมือน​กัน​ทุก​ตัว​อักษร

แล้ว​วัน​หนึ่ง​ก็​มี​ออ​เด​อร์​ที่ id เป็น -7 เดิน​เข้า​มา ค่า​แบบ​นี้​ไม่​ควร​มี แต่​สาม​บท​แรก​วัด​ไป​แล้ว​ว่า จำนวนเต็ม​ติดลบ​เดิน​เข้า​ท่อ​ได้​จริง โดย​ไม่มี​ใคร​ตั้งใจ​และ​ไม่มี​อะไร​แดง

id % 2 ขอ​งอ​อเด​อร์​ใบ​นั้น​ก็​คือ -7 % 2 ซึ่ง probe วัด​ไว้​ครบ​ทั้ง​ห้า​ภาษา service ที่​เขียน​ด้วย Java ได้ -1 ส่วน service ที่​เขียน​ด้วย Python ได้ 1 จาก​นิพจน์​เดียวกัน​และ id ใบ​เดียวกัน

คำถาม​ของ​บท​นี้​จึง​ไม่ใช่​ว่า​ใคร​ถูก แต่​เป็น​ว่า ทำไม​สอง​ภาษา​ที่​ทำ​ตาม​สเปก​ของ​ตัว​เอง​ครบ​ทุก​ข้อ ถึง​ตอบ​คนละ​เครื่องหมาย และ​คำ​ตอบ​เป็น​กฎ​ข้อ​เดียว​ที่​ต่าง​กัน​แค่​ทิศทาง

📦 orderctl — ของ​ชิ้น​เดียวกัน​กับ​คอร์ส​ภาษา​อื่น​ใน​เสา​นี้

orderctl คือ​เครื่อง​มือ​ประมวล​ผล​ออ​เด​อร์​ที่​คอร์ส​ภาษา​ใน​เสา​นี้​สร้าง​ซ้ำ​ที​ละ​ภาษา repo ตัวอย่าง กำลัง​จัด​ทำ ระหว่าง​นี้​ทุก​ตัวเลข​ใน​บท​นี้​รัน​เอง​ได้​ด้วย npm run verify:integer-semantics ซึ่ง​เรียก python3 scripts/integer-semantics/probe.py · ไม่​ต้อง​มี Docker และ​ไม่​ต้อง​ต่อ​เน็ต

สิบ​ช่อง​นี้​แบ่ง​เป็น​สอง​แถว และ​แถว​ของ​เศษ​มี​สี่​ช่อง​ที่​ตรง​กัน กับ Python ช่อง​เดียว​ที่​ต่าง

ทุก​ช่อง​ข้าง​ล่าง​ถาม​คำถาม​เดียวกัน คือ​เอา -7 ตั้ง แล้ว​หาร​ด้วย 2 และ​ขอ​เศษ​ของ​การ​หาร​นั้น ทรง​ที่ probe รัน​จริง​ไม่​เหมือน​กัน​ทุก​ภาษา จึง​ต้อง​อ่าน​รายการ​นี้​ก่อน​อ่าน​ตาราง

  • Java — ประกาศ int a = -7, b = 2 แล้ว​เรียก a / b กับ a % b
  • Go — ประกาศ a, b := -7, 2 แล้ว​เรียก a / b กับ a % b
  • Rust — ประกาศ let a: i32 = -7 กับ let b: i32 = 2 ที่​ห่อ​ด้วย black_box แล้ว​เรียก a / b กับ a % b
  • Node — เขียน -7 / 2 กับ -7 % 2 ลง​ใน​นิ​พจน์​ตรงๆ
  • Python — เขียน -7 // 2 กับ -7 % 2 ลง​ใน​นิ​พจน์​ตรงๆ · ตัว​ดำเนิน​การ​หาร​จำนวนเต็ม ของ​มัน​คือ // ไม่ใช่ /
การก​ระ​ทำJavaGoRustNodePython
การ​หาร-3-3-3-3.5-4
เศษ-1-1-1-11

ตาราง​นี้​มี​สิบ​ช่อง และ​ควร​อ่าน​มัน​ตาม​ที่​มัน​เป็น ไม่ใช่​ตาม​ที่​หัวข้อ​บท​ชวน​ให้​คิด

  • แถว​ของ​เศษ​มี​สี่​ช่อง​ที่​ตอบ -1 เท่า​กัน​หมด และ​มี​ช่อง​ของ Python ช่อง​เดียว​ที่​ตอบ 1 นี่​คือ​ตาราง​ที่​มี​ช่อง​ต่าง​อยู่​ช่อง​เดียว ไม่ใช่​ตาราง​ที่​ให้​คำ​ตอบ​ห้า​แบบ
  • แถว​ของ​การ​หาร​มี​สาม​ช่อง​ที่​ตอบ -3 เท่า​กัน ที่​เหลือ​คือ Python ซึ่ง​ตอบ -4 และ Node ซึ่ง​ตอบ -3.5 อัน​เป็น​ค่าที่​ไม่ใช่​จำนวนเต็ม​ตั้งแต่​ต้น

ช่อง​เดียว​ที่​ต่าง​ใน​แถว​ของ​เศษ คือ​ช่อง​ที่​บท​นี้​ตาม​ไป​จนถึง​ความ​เสีย​หาย เพราะ % ต่าง​หาก​ที่ โปรแกรม​เอา​ไป​ใช้​เป็น index และ index ที่​ผิด​ฝั่ง​คือ index ที่​ชี้​ออก​นอก​ช่วง

ทำไม​ตาราง​นี้​มี​ห้า​ภาษา ทั้ง​ที่​บท​ที่ 2 มีหก

probe ผูก​ทรง​ของ Kotlin ไว้​ทรง​เดียว คือ​การ​ลบ​แบบ UInt ที่​บท​ที่ 2 ใช้ มัน​ไม่​ได้​รัน​การ​หาร ของ​ค่า​ลบ​ใน​ภาษา​นั้น​เลย

กติกา​ของ​คอร์ส​นี้​คือ​บท​เรียน​พิมพ์​ได้​เฉพาะ​ค่าที่ measured.json ถือ​อยู่ ช่อง​ที่ probe ไม่​ได้​วัด คือ​ช่อง​ที่​ไม่มี​อะไร​ผูก บท​นี้​จึง​ไม่มี Kotlin อยู่​ใน​ตาราง และ​ไม่​เดา​คำ​ตอบแทน​มัน​ด้วย

ผล​หาร​ทาง​คณิตศาสตร์​ของ -7 กับ 2 ไม่ใช่​จำนวนเต็ม สอง​กฎ​นี้​เลือก​จำนวนเต็ม​คนละ​ตัว​ให้​มัน

-7 หาร​ด้วย 2 ไม่​ลงตัว คำ​ตอบ​ที่​เป็น​จำนวนเต็ม​จึง​ต้อง​เลือก​เอา และ​มี​จำนวนเต็ม​สอง​ตัว​ที่​ขนาบ ค่า​นั้น​อยู่ คือ -4 ทาง​ซ้าย​กับ -3 ทาง​ขวา สี่​ภาษา​ที่​มี​การ​หาร​จำนวนเต็ม​เลือก​หนึ่ง​ใน​สอง​ตัว​นี้ ส่วน Node ไม่​เลือก​สัก​ตัว

truncated divisiontruncated divisionการ​หาร​จำนวนเต็ม​ที่​ปัด​เข้าหา​ศูนย์ · เป็น​กฎ​ของ Java Go และ Rust ทั้ง​สาม​ภาษา ซึ่ง​ตอบ -7/2 ได้ -3 เท่า​กัน​หมด เลือก​ตัว​ที่​อยู่​ใกล้​ศูนย์​กว่า ซึ่ง​คือ -3 floored divisionfloored divisionการ​หาร​จำนวนเต็ม​ที่​ปัด​ลง​หา​ค่าที่​น้อย​กว่า​เสมอ · Python ใช้​กฎ​นี้​ภาษา​เดียว จึง​ตอบ -7//2 ได้ -4 ต่าง​จาก​สาม​ภาษา​ที่​ปัด​เข้าหา​ศูนย์​อยู่​หนึ่ง​หน่วย ส่วน Node ไม่มี​การ​หาร​จำนวนเต็ม​ให้​เทียบ เลือก​ตัว​ที่​น้อย​กว่า​เสมอ ซึ่ง​คือ -4 ความ​ต่าง​ของ สอง​กฎ​นี้​โผล่​เฉพาะ​ตอน​ผล​หาร​ติดลบ ตอน​ที่​ผล​หาร​เป็น​บวก​ทั้ง​สอง​กฎ​ให้​ค่า​เดียวกัน

สเปก​ของ​ทั้ง​สี่​ภาษา​ที่​มี​การ​หาร​จำนวนเต็ม เขียน​กฎ​ของ​ตัว​เอง​ไว้​ตรงๆ และ​ไม่มี​ภาษา​ไหน​ปล่อย​ให้ เรื่อง​นี้​เป็น​ราย​ละเอียด​ของ​การ implement

  • JLS หัวข้อ 15.17.2 ระบุ​ว่าการ​หาร​จำนวนเต็ม​ปัด​เข้าหา 0 แล้ว​นิยาม​ผล​หาร​เป็น​ค่าที่​มี​ขนาด ใหญ่​ที่สุด​เท่า​ที่​จะ​ยัง​ทำให้ |d ⋅ q| ≤ |n| เป็น​จริง
  • สเปก​ของ Go หัวข้อ Integer operators ใช้​คำ​ว่า truncated towards zero แล้ว​เรียก​กฎ​นี้ ด้วย​ชื่อ truncated division ใน​วงเล็บ​ของ​มัน​เอง
  • The Rust Reference หัวข้อ​ตัว​ดำเนิน​การ​เลขคณิต เขียน​ไว้​ที่​เชิงอรรถ​ของ​ตาราง​ว่าการ​หาร จำนวนเต็ม​ปัด​เข้าหา​ศูนย์ และ​เศษ​ของ​มัน​นิยาม​จาก​การ​หาร​แบบ​ตัด​เข้าหา​ศูนย์
  • Python Language Reference หัวข้อ 6.7 ระบุ​ว่า // ให้​ผล​ของ​การ​หาร​ทาง​คณิตศาสตร์ ที่​เอา​ฟังก์ชัน floor ไป​ครอบ ซึ่ง​คือ​การ​ปัด​ลง​เสมอ
flowchart TD
  A["ผลหารของ -7 กับ 2 ไม่ลงตัว"] --> B{"เลือกจำนวนเต็มด้วยกฎไหน"}
  B -->|"ปัดเข้าหาศูนย์ · Java Go Rust"| C["ผลหาร -3"]
  B -->|"ปัดลง · Python"| D["ผลหาร -4"]
  B -->|"ไม่เลือกเลย · Node"| E["-3.5 ซึ่งไม่ใช่จำนวนเต็ม"]
  C --> F["เศษ -1 · ตามเครื่องหมายของตัวตั้ง"]
  D --> G["เศษ 1 · ตามเครื่องหมายของตัวหาร"]
  E --> H["Math.trunc ได้ -3 · ส่วน % ได้ -1"]

คำ​บรรยาย​ภาพ: ช่อง​ที่​ตัดสิน​มี​ช่อง​เดียว คือ​ช่อง​สี่เหลี่ยม​ข้าวหลาม​ตัด​ด้าน​บน สอง​สาขา​ที่​เลือก จำนวนเต็ม​ไป​แล้ว ไม่​ได้​เลือก​เครื่องหมาย​ของ​เศษ​แยก​อีก​ข้อ มัน​ตาม​ผล​หาร​ที่​เลือก​ไป​เท่านั้น ส่วน​สาขา ของ Node ไม่มี​ผล​หาร​ที่​เป็น​จำนวนเต็ม​ให้​ตาม เศษ​ของ​มัน​จึง​มา​จาก​กฎ​ของ​ตัว​เอง

สเปก​ทั้ง​สอง​ฝั่ง​ผูก​เศษ​ไว้​กับ​ผล​หาร​ด้วย​เอกลักษณ์​รูป​เดียวกัน เศษ​จึง​ไม่มี​อิสระ​เหลือ​เลย

JLS หัวข้อ 15.17.3 เขียน​เอกลักษณ์​ไว้​ว่า (a/b)*b+(a%b) ต้อง​เท่ากับ a ส่วน Python Language Reference หัวข้อ 6.7 เขียน​ของ​มัน​ว่า x == (x//y)*y + (x%y) สอง​บรรทัด​นี้​คือ สมการ​รูป​เดียวกัน ต่าง​กัน​แค่​ชื่อ​ตัว​ดำเนิน​การ

เมื่อ​สมการ​เป็น​รูป​เดียวกัน และ​ผล​หาร​ถูก​เลือก​ไป​แล้ว​คนละ​ตัว เศษ​ก็​เหลือ​ค่าที่​เป็น​ไป​ได้​ค่า​เดียว ใน​แต่ละ​ฝั่ง มัน​ไม่ใช่​การ​ตัดสิน​ใจ​อีก​ข้อ​ที่​ภาษา​ทำ​แยก​จาก​กัน

  • ฝั่ง​ที่​ปัด​เข้าหา​ศูนย์ ผล​หาร​เป็น -3 เศษ​จึง​ต้อง​เป็น -1 เพราะ -3 * 2 + (-1) กลับ​ไป​ได้ -7
  • ฝั่ง​ที่​ปัด​ลง ผล​หาร​เป็น -4 เศษ​จึง​ต้อง​เป็น 1 เพราะ -4 * 2 + 1 กลับ​ไป​ได้ -7 เหมือน​กัน

สเปก​ทั้ง​สอง​ฝั่ง​ยัง​เขียน​ผล​ที่​ตาม​มา​ของ​เอกลักษณ์​นี้​ไว้​เป็น​ประโยค​ของ​ตัว​เอง​ด้วย และ​สอง​ประโยค​นั้น คือ​นิยาม​ของ เครื่องหมาย​ของ​เศษmodulo signเครื่องหมาย​ของ​เศษ​ที่​เหลือ​จาก​การ​หาร · มัน​ตาม​ตัว​ตั้ง​ใน​เกือบ​ทุก​ภาษา แต่​ตาม​ตัว​หาร​ใน Python · id % n จึง​ให้​ค่า​คนละ​ฝั่ง​เมื่อ id ติดลบ และ​สอง service แบ่ง shard ไม่​ตรง​กัน ที่​บท​นี้​วัด

  • JLS ระบุ​ว่า​ผล​ของ % จะ​ติดลบ​ได้​ก็​ต่อ​เมื่อ​ตัว​ตั้ง​ติดลบ และ​เป็น​บวก​ได้​ก็​ต่อ​เมื่อ​ตัว​ตั้ง​เป็น​บวก
  • Python Language Reference ระบุ​ว่า % ให้​ผล​ที่​มี​เครื่องหมาย​เดียว​กับ​ตัว​ถัด​มา​เสมอ ซึ่ง​คือ​ตัว​หาร หรือ​ไม่​ก็​เป็น​ศูนย์

ใน​นิพจน์ id % 2 ตัว​หาร​เป็น​บวก​เสมอ ฝั่ง​หนึ่ง​จึง​คืน​ค่า​ตาม​เครื่องหมาย​ของ id ส่วน​อีก​ฝั่ง คืน​ค่าที่​ไม่​ติดลบ​เสมอ นี่​คือ​ที่มา​ของ -1 กับ 1 ใน​ตาราง​ข้าง​บน

ทำไม​สาม​ภาษา​ที่ compile ถึง​ใส่​ค่า​ไว้​ใน​ตัวแปร

Shapes.java main.go และ shapes.rs ใส่​ทั้ง​สอง​ค่า​ไว้​ใน​ตัวแปร​ก่อน​หาร ด้วย​เหตุผล​เดียว​กับ กับ​ดัก​ของ literal ใน​บท​ที่ 1 คือ​สิ่ง​ที่ compiler พับ​ค่า​ให้​ล่วง​หน้า​ได้ ไม่ใช่​สิ่ง​ที่​เกิด​ตอน runtime

ที่​นิพจน์​นี้​ไม่มี​การ​ล้น​ให้ compiler ปฏิเสธ ต่าง​จาก 10u32 - 25u32 ของ​บท​ที่ 1 ทิศ​ของ​การ​ปัด จึง​เป็น​กฎ​ของ​ภาษา ไม่ใช่​ผล​ของ​ธง​ตอน compile

/ ของ Node ไม่​ได้​เลือก​จำนวนเต็ม​ให้​เลย ส่วน % ของ​มัน​ไป​อยู่​ฝั่ง​เดียว​กับ Java Go และ Rust

-7 / 2 ใน Node ได้ -3.5 ซึ่ง​ไม่ใช่​จำนวนเต็ม ภาษา​นี้​ไม่มี​ตัว​ดำเนิน​การ​หาร​จำนวนเต็ม​แยก ต่าง​หาก คน​เขียน​จึง​ต้อง​เลือก​กฎ​การ​ปัด​ด้วย​มือ​ทุก​ครั้ง​ที่​ต้องการ​จำนวนเต็ม

Math.trunc(-7 / 2) ได้ -3 ซึ่ง​เป็น​ค่า​เดียว​กับ​ที่​อีก​สาม​ภาษา​ให้ ตัว​ที่​พา​ไป​อยู่​ฝั่ง​นั้น​คือ​ชื่อ ฟังก์ชัน​ที่​คน​เขียน​พิมพ์​เอง ไม่ใช่​กฎ​ที่​ภาษา​บังคับ

-7 % 2 ใน Node ได้ -1 หน้า Remainder ของ MDN อธิบาย​ว่า % ของ JavaScript เป็น remainder ไม่ใช่ modulo และ​ระบุ​ว่า​มัน​รับ​เครื่องหมาย​ของ​ตัว​ตั้ง​เสมอ ซึ่ง​ตรง​กับ -1 ที่​วัด​ได้

หน้า​เดียวกัน​นั้น​เขียน​ความ​ต่าง​ของ​สอง​คำ​นี้​ไว้​ตรงๆ ว่า​เมื่อ​สอง​ฝั่ง​มี​เครื่องหมาย​ต่าง​กัน ผล​ของ modulo จะ​รับ​เครื่องหมาย​ของ​ตัว​หาร ส่วน​ผล​ของ remainder รับ​เครื่องหมาย​ของ​ตัว​ตั้ง และ​สอง​ค่า​นั้น ต่าง​กัน​ได้​หนึ่ง​เท่า​ของ​ตัว​หาร

MDN ยัง​เขียน​รูป​ที่​ใช้​แทน​ไว้​ให้​ด้วย คือ ((n % d) + d) % d สำหรับ​คน​ที่​ต้องการ modulo จริงๆ ใน JavaScript รูป​นี้​คือ​ทาง​ที่ service ฝั่ง Node ใช้​ตาม​ฝั่ง Python ได้ ถ้า​ตกลง​กัน​ว่า​จะ​ตาม​ฝั่ง​นั้น

ช่อง​ที่​ต่าง​ช่อง​เดียว​ใน​ตาราง เดิน​ทาง​ไป​จบ​ที่ index ของ shard และ​ปลาย​ทาง​คืน​สอง​คำ​ตอบ

กลับ​ไป​ที่ id % 2 ขอ​งอ​อเด​อร์​ที่ id เป็น -7 นิพจน์​นั้น​คือ -7 % 2 ทุก​ตัว​อักษร และ​ปลาย​ทาง ของ​มัน​คือ index ที่​ใช้​เลือก shard

  • service ฝั่ง Java Go Rust หรือ Node ได้ -1 ซึ่ง​อยู่​นอก​ช่วง 0 ถึง 1 ที่ shard สอง​ใบ​มี
  • service ฝั่ง Python ได้ 1 ซึ่ง​อยู่​ใน​ช่วง และ​ชี้​ไป​ที่ shard ใบ​ที่​สอง​อย่าง​เรียบร้อย

ผล​คือ​แถว​เดียวกัน​มี​เจ้าของ​สอง​คำ​ตอบ ฝั่ง​หนึ่ง​บอก​ว่า​เป็น​ของ shard ใบ​ที่​สอง อีก​ฝั่ง​บอก​ว่า ไม่มี shard ไหน​รับ ทั้ง​ที่​ทั้ง​สอง​ฝั่ง​รัน​นิพจน์​ชุด​เดียวกัน​กับ id ใบ​เดียวกัน

การ​หาร​ก็​ให้​รูป​เดียวกัน​เมื่อ​เอา​ไป​หาร​บิล ออ​เด​อร์​ที่​ยอด​สุทธิ​เป็น -7 หน่วย และ​ต้อง​แบ่ง​ครึ่ง ให้​สอง​ฝั่ง จะ​ได้​ฝั่ง​ละ -3 ใน Java Go และ Rust ส่วน Python ได้​ฝั่ง​ละ -4

ฝั่ง Node ได้ -3.5 ซึ่ง​ไม่ใช่​จำนวน​หน่วย​ที่​จ่าย​ได้​เลย แล้ว​คน​เขียน​ก็​ต้อง​เลือก​กฎ​การ​ปัด​เอง ตรง​จุด​นั้น โดยที่​ไม่มี​อะไร​บอก​ว่า​อีก​สี่ service เลือก​กฎ​ไหน​ไว้

สิ่ง​ที่​ทำให้ bug ตัว​นี้​อยู่​ได้​นาน​คือ​ไม่มี​ฝั่ง​ไหน​ผิด ทั้ง​สอง​ฝั่ง​ทำ​ตาม​สเปก​ของ​ภาษา​ตัว​เอง​ครบ เทสต์​ของ​แต่ละ service เขียว​หมด เพราะ​ภายใน​ภาษา​เดียว​คำ​ตอบ​สอดคล้อง​กับ​ตัว​เอง​เสมอ

ราคา​ของ​การ​ทำให้​สอง service ตรง​กัน

ทาง​แก้​ทุก​ทาง​มี​ราคา และ​ราคา​ของ​มัน​คือ​สิ่ง​ที่​ทำให้​ทีม​เลื่อน​การ​แก้​ออก​ไป

  • ครอบ​ด้วย​รูป​ของ MDN คือ ((n % d) + d) % d ราคา​คือ​ทุก​จุด​เรียก​ยาว​ขึ้น และ​ต้อง​ครอบ ให้​ครบ​ทุก​ภาษา​ใน​ระบบ ไม่ใช่​ครอบ​เฉพาะ​ภาษา​ที่​จับ​ได้
  • บังคับ​ให้ id ไม่​ติดลบ​ตั้งแต่​ต้นทาง ราคา​คือ​ต้อง​มี​ด่าน​ที่​ขอบ​ของ​ระบบ​จริงๆ และ​บท​ที่ 1 ถึง​บท​ที่ 3 วัด​ไว้​แล้ว​ว่า​ค่า​ติดลบ​เกิด​ขึ้น​เอง​กลาง​ท่อ​ได้ ไม่​ได้​เข้า​มา​ทาง​ขอบ​อย่าง​เดียว
  • ย้าย​นิพจน์​ไป​ไว้​ที่​เดียว เช่น​ให้​ฝั่ง database คำนวณ index ให้ ราคา​คือ service ทุก​ตัว ต้อง​เรียก​ข้าม​เครือ​ข่าย​เพื่อ​ถาม​ว่า​แถว​นี้​เป็น​ของ​ใคร

ราคา​ที่​แพง​ที่สุด​คือ​การ​ไม่​ทำ​อะไร เพราะ​มัน​จ่าย​ตอน​ที่​ข้อมูล​กระจาย​ผิด​ไป​แล้ว​หลาย​เดือน

บท​นี้​ปิด​จุด​ที่​สาม และ​รูป​ของ​มัน​ต่าง​จาก​สอง​จุด​แรก เพราะ​ไม่มี​ใคร​ล้น​และ​ไม่มี​ใคร​ผิด

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

จุด​ที่​สาม​คือ​จุด​ที่​บท​นี้​เพิ่ง​วัด และ​มัน​เป็น​จุด​เดียว​ใน​สาม​จุด​แรก​ที่​ไม่มี​การ​ล้น​อยู่​ใน​การ​คำนวณ​เลย ทุก​ค่า​อยู่​ใน​ช่วง​ของ​ชนิด​สบายๆ สิ่ง​ที่​เปลี่ยน​คือ​กฎ​การ​ปัด​ที่​ภาษา​เลือก​ไว้​ให้​ตั้งแต่​ต้น

สอง​จุด​แรก​ยัง​พอ​มี​อะไร​ส่ง​เสียง​ได้​บ้าง คือ​ธง​ของ Rust กับ​ตระกูล Exact ของ Java จุด​ที่​สาม ไม่มี​อะไร​แบบ​นั้น​เลย ทุก​ช่อง​ใน​ตาราง​ของ​บท​นี้​คือ​คำ​ตอบ​ที่​ถูก​ต้อง​ตาม​สเปก​ของ​ภาษา​นั้น

บท​ที่ 5 ย้าย​ไป​ที่​จุด​ถัด​ไป คือ​ตอน​ที่​ค่า​จำนวนเต็ม​เดิน​ผ่าน double แล้ว​ออก​มา​ไม่​เท่า​เดิม คราว​นี้​ค่าที่​เปลี่ยน​ไม่​ได้​มา​จาก​กฎ​ที่​ภาษา​เลือก​ไว้ แต่​มา​จาก​ความ​กว้าง​ของ​ตัว​ที่​รับ​ค่า​ไว้​ระหว่าง​ทาง


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • JLS SE 25 — §15.17.2 Division Operator / และ §15.17.3 Remainder Operator % (เข้า​ถึง 2026-09-13) — §15.17.2 ระบุ​ว่าการ​หาร​จำนวนเต็ม​ปัด​เข้าหา 0 และ​นิยาม​ผล​หาร​ด้วย เงื่อนไข |d ⋅ q| ≤ |n| · §15.17.3 ระบุ​เอกลักษณ์ (a/b)*b+(a%b) ที่​ต้อง​เท่ากับ a แล้ว​สรุป​ต่อว่า​ผล​ของ % ติดลบ​ได้​ก็​ต่อ​เมื่อ​ตัว​ตั้ง​ติดลบ

  • The Python Language Reference — §6.7 Binary arithmetic operations (เข้า​ถึง 2026-09-13) — ระบุ​ว่า // ให้​ผล​ของ​การ​หาร​ทาง​คณิตศาสตร์​ที่​เอา​ฟังก์ชัน floor ไป​ครอบ · ระบุ​ว่า % ให้​ผล​ที่​มี​เครื่องหมาย​เดียว​กับ​ตัว​ดำเนิน​การ​ตัว​ที่​สอง​เสมอ​หรือ​ไม่​ก็​เป็น​ศูนย์ · และ​ผูก​ทั้ง​สอง​ตัว​ไว้​ด้วย​เอกลักษณ์ x == (x//y)*y + (x%y)

  • Remainder (%) — MDN (เข้า​ถึง 2026-09-13) — ระบุ​ว่า % ของ JavaScript รับ​เครื่องหมาย​ของ​ตัว​ตั้ง​เสมอ · อธิบาย ความ​ต่าง​ระหว่าง remainder กับ modulo ว่า​สอง​ค่า​นั้น​ต่าง​กัน​ได้​หนึ่ง​เท่า​ของ​ตัว​หาร​เมื่อ​สอง​ฝั่ง มี​เครื่องหมาย​ต่าง​กัน · และ​เขียน​รูป ((n % d) + d) % d ไว้​ให้​คน​ที่​ต้องการ modulo

  • The Go Programming Language Specification — Integer operators (เข้า​ถึง 2026-09-13) — ผูก q = x / y กับ r = x % y ด้วย x = q*y + r และ |r| < |y| โดยที่ x / y ถูก truncated towards zero ซึ่ง​สเปก​เรียก​ด้วย​ชื่อ truncated division เอง

  • The Rust Reference — Arithmetic and Logical Binary Operators (เข้า​ถึง 2026-09-13) — เชิงอรรถ​ของ​ตาราง​ตัว​ดำเนิน​การ​ระบุ​ว่าการ​หาร​จำนวนเต็ม​ปัด​เข้าหา​ศูนย์ และ​ว่า​เศษ​ของ Rust นิยาม​จาก​การ​หาร​แบบ​ตัด​เข้าหา​ศูนย์ จึง​มี​เครื่องหมาย​เดียว​กับ​ตัว​ตั้ง

  • ตัวเลข​ทุก​ตัว​ใน​บท​นี้​มา​จาก scripts/integer-semantics/probe.py ซึ่ง​วัด​ทั้ง​ชุด​สาม​รอบ ต่อ​การ​เรียก​หนึ่ง​ครั้ง · วัด​บน rustc 1.97.1 go1.27.0 JDK 25.0.4.1 node v24.19.0 และ Python 3.14.6 บน darwin/arm64 เมื่อ 2026-09-12

เช็กความเข้าใจ — บทที่ 4

ข้อ 1 / 3

probe วัด `-7` หารด้วย `2` และเศษของมันในทั้งห้าภาษา ฝั่ง Java กับฝั่ง Python ได้อะไร