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

ค่าสัมบูรณ์​ที่​ติดลบ และ​ผล​หาร​ของ​สอง​ค่า​ลบ​ที่​ยัง​ติดลบ

ยอด​คืน​เงิน​ขอ​งอ​อเด​อร์​ใบ​หนึ่ง​มา​ถึง service ของ​คุณ​เป็น -2147483648 ซึ่ง​เป็น​ค่าที่​ลบ​ที่สุด ที่ int เก็บ​ได้ ค่า​นี้​มา​ถึง​ได้​จริง เพราะ​การ​ลบ​ที่​ล้น​ใน​บท​ที่ 1 ก็​ผลิต​ค่า​ใน​ช่วง​นี้​ออก​มา​ได้

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

Math.abs(Integer.MIN_VALUE) คืน -2147483648 และ​ไม่​โยน​อะไร​ออก​มา​เลย บัญชี​จึง​ได้ ขนาด​ที่​ติดลบ​ไป​หนึ่ง​แถว โดย​ไม่มี​บรรทัด log สัก​บรรทัด​ที่​บอก​ว่า​มี​อะไร​ผิด

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

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

ฝั่ง​ลบ​ของ 32 bit มี​ค่า​มากกว่า​ฝั่ง​บวก​อยู่​หนึ่ง​ค่า และ​ค่าที่​เกิน​มา​นั้น​คือ​ค่าที่​ลบ​ที่สุด

two’s complementtwo's complementการ​เข้า​รหัส​จำนวนเต็ม​มี​เครื่องหมาย​ที่​ฝั่ง​ลบ​มี​ค่า​มากกว่า​ฝั่ง​บวก​อยู่​หนึ่ง​ค่า · ค่าที่​ลบ​ที่สุด​จึง​ไม่มี​คู่​บวก​ของ​ตัว​เอง และ​นั่น​คือ​เหตุผล​ที่​ค่าสัมบูรณ์​ของ​มัน​ยัง​ติดลบ คือ​การ​เข้า​รหัส​จำนวนเต็ม​มี​เครื่องหมาย​ที่ int ของ Java int32 ของ Go และ i32 ของ Rust ใช้​ร่วม​กัน ความ​ไม่​สมมาตร​ของ​มัน​อธิบาย​ได้​ด้วย​การ​นับ​ช่อง​ล้วนๆ

int 32 bit แบ่ง​รูป​แบบ bit ทั้งหมด​ออก​เป็น​สอง​ฝั่ง และ​ศูนย์​กิน​ไป​หนึ่ง​ช่อง​จาก​ฝั่ง​ที่​ไม่​ติดลบ

  • ฝั่ง​ที่​ไม่​ติดลบ​เหลือ​ช่อง​ให้​ค่า​บวก​เพียง 2147483647 ค่า
  • ฝั่ง​ลบ​ได้​ไป​เต็มๆ 2147483648 ค่า

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

เมื่อ​คู่​บวก​ไม่มี​อยู่ คำถาม​ว่า “ค่าสัมบูรณ์​ของ​มัน​คือ​อะไร” ก็​ไม่มี​คำ​ตอบ​ที่​อยู่​ใน​ชนิด​เดิม สิ่ง​ที่​แต่ละ​ภาษา​ทำ​ต่อ​จาก​ตรง​นั้น​คือ​เนื้อ​ของ​บท​นี้ และ​ทาง​เลือก​มี​อยู่​สี่​แบบ​เท่านั้น

สิบ​สอง​แถว​นี้​มี​สอง​แถว​ที่​หยุด​โปรแกรม​เอง สอง​แถว​ที่​คืน​สัญญาณ​ให้​คน​ที่​ขอ ที่​เหลือ​คืน​ค่า​แล้ว​เดิน​ต่อ

ทุก​แถว​ถาม​คำถาม​เดียวกัน คือ​ให้​ค่าที่​ลบ​ที่สุด​ของ​จำนวนเต็ม 32 bit แล้ว​ขอ​ค่าสัมบูรณ์ ผล​หาร ด้วย -1 หรือ​เศษ​ของ​การ​หาร​นั้น

การ​เรียกผล​ที่​วัด​ได้
Java Math.abs(Integer.MIN_VALUE)-2147483648 ไม่มี exception
Java Integer.MIN_VALUE / -1-2147483648 ไม่มี exception
Java Integer.MIN_VALUE % -10
Java Math.absExact(Integer.MIN_VALUE)โยน ArithmeticException
Go int32 ค่า​ต่ำ​สุด / -1-2147483648
Go int32 ค่า​ต่ำ​สุด % -10
Rust i32::MIN.abs() · rustc absmin.rsรหัส​ออก 101 · (panic)
Rust i32::MIN.abs() · rustc -O absmin.rsรหัส​ออก 0 · -2147483648
Rust i32::MIN.checked_abs()None
Rust i32::MIN.checked_div(-1)None
Rust i32::MIN.wrapping_abs()-2147483648
Python abs(-2**31)2147483648

สาม​แถว​แรก​กับ​สอง​แถว​ของ Go เป็น​ห้า​ช่อง​ที่​เงียบ​สนิท ไม่มี​ช่อง​ไหน​ใน​ห้า​ช่อง​นี้​โยน exception ไม่มี​ช่อง​ไหน​คืน​สัญญาณ และ​ไม่มี​ช่อง​ไหน​เขียน​อะไร​ลง stderr

ค่า​ของ​การ​หาร​ใน Go กับ Rust ต้อง​อยู่​ใน​ตัวแปร

main.go ของ probe ประกาศ var mn int32 = -2147483648 กับ var neg int32 = -1 แล้ว​จึง หาร ส่วน shapes.rs ห่อ​ค่าที่​มัน​วัด​ด้วย std::hint::black_box ก่อน​ใช้

เหตุผล​เดียว​กับ​กับ​ดัก​ของ literal ใน​บท​ที่ 1 คือ​สอง​ภาษา​นี้​ตัดสิน​การ​หาร​ที่​เห็น​ค่า​ทั้ง​คู่​ล่วง​หน้า ตั้งแต่ compile การ​วัด​จึง​ต้อง​พา​ค่า​ข้าม​พรมแดน​ของ runtime มา​ก่อน

กับ​ดัก​นี้​ผูก​กับ​นิพจน์ ไม่ใช่​กับ​ภาษา absmin.rs ใน​หัวข้อ​ถัด​ไป​เขียน i32::MIN ลง​ไป​ตรงๆ แล้ว​ยัง​ได้​รหัส​ออก 101 อยู่ดี

เศษ​ที่​เป็น​ศูนย์ ไม่​ได้​แปล​ว่า​ไม่มี​อะไร​เกิด​ขึ้น

หัวข้อ​ที่​มีชื่อ​ว่า “เศษ​ที่​เป็น​ศูนย์ ไม่​ได้​แปล​ว่า​ไม่มี​อะไร​เกิด​ขึ้น”

Integer.MIN_VALUE % -1 ได้ 0 ซึ่ง​เป็น​คำ​ตอบ​ที่​ไม่​ทำให้​ใคร​สะดุด จน​กระทั่ง​รู้​ว่าการ​หาร ที่​อยู่​คู่​กับ​มัน​นั้น​ล้น

JLS หัวข้อ 15.17.3 ผูก​สอง​ค่า​นี้​เข้า​ด้วย​กัน​ด้วย​เอกลักษณ์ (a/b)*b+(a%b) ที่​ต้อง​เท่ากับ a แล้ว​ระ​บุ​ตรงๆ ว่า​เอกลักษณ์​นี้​ยัง​เป็น​จริง​ใน​กรณี​พิเศษ​นี้ โดยที่​เศษ​เป็น 0

เศษ​ที่​ถูก​จึง​อยู่​ร่วม​กับ​ผล​หาร​ที่​ผิด​ได้​ใน​สมการ​เดียวกัน คน​ที่​ตรวจ​ว่า % -1 ให้ 0 แล้ว​สรุป​ว่า การ​หาร​นั้น​เรียบร้อย กำลัง​อ่าน​ครึ่ง​เดียว​ของ​สิ่ง​ที่​สเปก​เขียน​ไว้

สอง​ที่​ที่​หยุด​เอง และ​ยาม​ที่​ต้อง​เรียก​ชื่อ

หัวข้อ​ที่​มีชื่อ​ว่า “สอง​ที่​ที่​หยุด​เอง และ​ยาม​ที่​ต้อง​เรียก​ชื่อ”

สอง​ที่​แรก​หยุด​โปรแกรม​โดย​ไม่​ต้อง​ขอ · ส่วน​ยาม​ที่​เหลือ​ทำงาน​ก็​ต่อ​เมื่อ​คุณ​เรียก​ชื่อ​มัน

Math.abs กับ Math.absExact รับ​ค่า​เดียวกัน แต่​ตอบ​คนละ​อย่าง ตัว​แรก​คืน -2147483648 ตัว​ที่​สอง​โยน​ออก​มา​ทั้ง​บรรทัด

ข้อความ​ที่ probe บันทึก​จาก Math.absExact(Integer.MIN_VALUE)
java.lang.ArithmeticException: Overflow to represent absolute value of Integer.MIN_VALUE

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

หน้า Math ของ Java 25 API เขียน​ความ​ต่าง​ข้อ​นี้​ไว้​ที่ entry ของ abs เอง โดย​บอก​ว่า​เมื่อ อาร์กิวเมนต์​เท่ากับ Integer.MIN_VALUE ผลลัพธ์​คือ​ค่า​เดิม​นั้น ซึ่ง​ติดลบ และ absExact โยน ArithmeticException ให้​ค่า​เดียวกัน​นี้

Rust — สวิตช์​ตัว​เดิม​ของ​บท​ที่ 1 กับ​นิพจน์​คนละ​ตัว

หัวข้อ​ที่​มีชื่อ​ว่า “Rust — สวิตช์​ตัว​เดิม​ของ​บท​ที่ 1 กับ​นิพจน์​คนละ​ตัว”

absmin.rs มี​บรรทัด​เดียว และ​มัน​คือ​ทรง​เดียว​กับ a.rs ของ​บท​ที่ 1 คือ source ชุด​เดียว compiler ตัว​เดียว ต่าง​กัน​ที่​ธง​ตัว​เดียว

scripts/integer-semantics/runners/absmin.rs
fn main() { println!("{}", i32::MIN.abs()); }
  • rustc absmin.rs ได้​รหัส​ออก 101 และ​ไม่มี​อะไร​ออก​ทาง stdout
  • rustc -O absmin.rs ได้​รหัส​ออก 0 และ stdout เป็น -2147483648

บรรทัด​ข้อความ​ของ panic บอก​กลไก​ไว้​ตรง​ตัว คือ​ขั้น​ที่​ล้น​ไม่ใช่​การ​หา​ค่าสัมบูรณ์ แต่​เป็นการ​ใส่ เครื่องหมาย​ลบ​ให้​ค่าที่​ลบ​ที่สุด

บรรทัด​ข้อความ​ของ panic
attempt to negate with overflow

บรรทัด​ที่​อยู่​เหนือ​มัน​มี​หมายเลข​ของ thread ซึ่ง​เปลี่ยน​ทุก​ครั้ง​ที่​รัน probe จึง​ผูก​ไว้​ที่​บรรทัด ข้อความ​บรรทัด​นี้​บรรทัด​เดียว เหมือน​ที่​บท​ที่ 1 ทำ​กับ attempt to subtract with overflow

เอกสาร​ของ i32::abs เขียน​พฤติกรรม​นี้​ไว้​ใน​หัวข้อ Overflow behavior ของ​มัน​เอง โดย​บอก​ว่า ค่าสัมบูรณ์​ของ i32::MIN แทน​ด้วย i32 ไม่​ได้ การ​คำนวณ​มัน​จึง​ล้น

checked_abs กับ checked_div ตอบ​คำถาม​เดียวกัน​โดย​ไม่​ต้อง​พึ่ง​ธง​เลย ทั้ง​คู่​คืน None ซึ่ง​เป็น​ค่าที่​ผู้​เรียก​ต้อง​แกะ​ออก​มา​ก่อน​ใช้

  • i32::MIN.checked_abs() ได้ None
  • i32::MIN.checked_div(-1) ได้ None
  • i32::MIN.wrapping_abs() ได้ -2147483648 ซึ่ง​เป็นการ​ประกาศ​ว่า​จะ​วน​กลับ

สาม​ทรง​นี้​ต่าง​กัน​ที่​ผู้​เขียน​เป็น​คน​เลือก ไม่ใช่​ธง​ตอน compile เป็น​คน​เลือก​ให้ wrapping_abs ให้​ค่า​เท่ากับ abs ที่​ปิด​การ​ตรวจ​ไว้​พอดี แต่​มัน​เขียน​ความ​ตั้งใจ​นั้น​ลง​ไป​ใน​ชื่อ

ราคา​ของ​ยาม​แต่ละ​แบบ

ยาม​ทั้ง​สาม​แบบ​มี​ราคา​ต่าง​กัน และ​ไม่มี​แบบ​ไหน​ฟรี

  • absExact กับ checked_abs บังคับ​ให้​ผู้​เรียก​จัดการ​กรณี​ที่​ทำ​ไม่​ได้ ซึ่ง​แปล​ว่า​ทุก​จุด​เรียก ยาว​ขึ้น และ path ของ error เพิ่ม​ขึ้น​มา​หนึ่ง​เส้น​ต่อ​หนึ่ง​การ​คำนวณ
  • -C overflow-checks=on ครอบ​ทุก​การ​คำนวณ​ใน​ทั้ง binary จึง​ต้อง​จ่าย​ค่า​ตรวจ​แม้​ใน​จุด​ที่ พิสูจน์​แล้ว​ว่า​ล้น​ไม่​ได้
  • abs เปล่าๆ ไม่มี​ราคา​ตอน​รัน ราคา​ของ​มัน​คือ​แถว​ที่​ติดลบ​ใน​บัญชี ซึ่ง​จ่าย​ทีหลัง​และ​จ่าย​แพง​กว่า

ภาษา​เดียว​ใน​ตาราง​ที่​ตอบ​เป็น​ค่า​บวก เพราะ​มัน​ไม่มี​ขอบ​ให้​ชน

abs(-2**31) ใน Python ได้ 2147483648 ซึ่ง​เป็น​คำ​ตอบ​ทาง​คณิตศาสตร์​ที่​ถูก​ต้อง และ​เป็น ค่าที่​อีก​สาม​ภาษา​ใน​ตาราง​แทน​ใน 32 bit ไม่​ได้​เลย

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

flowchart TD
  A["ขอค่าสัมบูรณ์ของค่าที่ลบที่สุด หรือหารมันด้วย -1"] --> B{"ชนิดปลายทางมีความกว้างไหม"}
  B -->|"ไม่มี · Python"| C["2147483648 · ค่าบวกตามคณิตศาสตร์"]
  B -->|"มี 32 bit"| D{"เลือกการกระทำแบบไหน"}
  D -->|"Math.abs · การหารเปล่าๆ · wrapping_abs · abs ที่ปิดการตรวจ"| E["-2147483648 · เงียบสนิท"]
  D -->|"Math.absExact"| F["โยน ArithmeticException"]
  D -->|"abs ที่เปิดการตรวจ"| G["panic · รหัสออก 101"]
  D -->|"checked_abs · checked_div"| H["None"]

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

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

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

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

ความ​เสีย​หาย​ของ​จุด​นี้​ใน​โด​เม​นอ​อเด​อร์​อ่าน​ได้​สอง​แบบ และ​ทั้ง​สอง​แบบ​ผ่าน validation ที่​ตรวจ เฉพาะ​ชนิด​ได้​สบาย

  • ยอด​คืน​เงิน​ที่​ผ่าน Math.abs แล้ว​ยัง​ติดลบ ถูก​เขียน​ลง​บัญชี​เป็น​ขนาด​ที่​ติดลบ
  • order id ที่​หาร​ด้วย -1 เพื่อ​พลิก​เครื่องหมาย ได้​ค่า​เดิม​กลับ​มา แล้ว​ชี้​ไป​ที่​แถว​เดิม

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


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • Math (Java SE 25 & JDK 25) (เข้า​ถึง 2026-09-13) — entry ของ abs(int) ระบุ​ว่า​เมื่อ​อาร์กิวเมนต์​เท่ากับ Integer.MIN_VALUE ผลลัพธ์​คือ​ค่า​เดิม​ซึ่ง​ติดลบ · entry ของ absExact(int) ระบุ​ว่า​ช่วง ของ two’s complement ไม่​สมมาตร​โดย​มี​ค่า​ลบ​เกิน​มา​หนึ่ง​ค่า จึง​โยน ArithmeticException

  • JLS SE 25 — §15.17.2 Division Operator / และ §15.17.3 Remainder Operator % (เข้า​ถึง 2026-09-13) — §15.17.2 เขียน​กรณี​พิเศษ​ไว้​ว่า​เมื่อ​ตัว​ตั้ง​เป็น​จำนวน​ลบ​ที่​มี​ขนาด​ใหญ่ ที่สุด​ของ​ชนิด​นั้น และ​ตัว​หาร​เป็น -1 การ​ล้น​เกิด​ขึ้น​และ​ผลลัพธ์​เท่ากับ​ตัว​ตั้ง โดย​ไม่มี exception ถูก​โยน​ออก​มา · §15.17.3 ระบุ​ว่า​เอกลักษณ์​ของ​เศษ​ยัง​เป็น​จริง​ใน​กรณี​นั้น และ​เศษ​เป็น 0

  • i32 — Rust (เข้า​ถึง 2026-09-13) — หัวข้อ Overflow behavior ของ abs ระบุ​ว่า​ค่าสัมบูรณ์​ของ i32::MIN แทน​ด้วย i32 ไม่​ได้ · checked_abs ระบุ​ว่า​คืน None เมื่อ self == MIN · checked_div ระบุ​ว่า​คืน None เมื่อ​ตัว​หาร​เป็น 0 หรือ​การ​หาร​นั้น​ล้น

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

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

ข้อ 1 / 3

เรียก `Math.abs(Integer.MIN_VALUE)` ใน Java 25 แล้วได้อะไร