
Java Math.clamp: บีบค่าให้อยู่ในช่วงในบรรทัดเดียว และแปลง long เป็น int แบบไม่ล้น
แอปที่รับค่า pageSize=999999 จากลิงก์ แล้วส่งตรงเข้า query ฐานข้อมูล คือวิธีทำให้เซิร์ฟเวอร์หอบได้ง่ายที่สุดวิธีหนึ่ง ทางกันพื้นฐานคือ “บีบค่า” ให้อยู่ในช่วงที่ยอมรับได้ เช่น 1 ถึง 100 ตั้งแต่ Java 21 มีเมธอด Math.clamp ทำเรื่องนี้ในบรรทัดเดียวครับ งานนี้ถูกเพิ่มผ่าน JDK-8301226
WHY: ทำไมไม่เขียน max/min ซ้อนกันเหมือนเดิม
สูตรเดิมคือ Math.max(min, Math.min(max, x)) ใช้ได้ แต่มีจุดพลาดที่เจอบ่อย:
- สลับ
maxกับminแล้วได้ผลผิดแบบเงียบๆ ไม่มี error - ถ้าช่วงเพี้ยน (
min > max) เพราะ config ผิด สูตรเดิมก็ยังคืนค่าออกมา ไม่บอกใคร - อ่านแล้วต้องหยุดคิดว่าตัวไหนครอบตัวไหน
Math.clamp(value, min, max) เขียนชื่อสิ่งที่ตั้งใจทำตรงๆ และตามเอกสาร ถ้า min > max จะโยน IllegalArgumentException ทันที เหมือนรั้วกั้นที่ติดตั้งผิดด้านแล้วส่งเสียงเตือน แทนที่จะปล่อยรถผ่าน
HOW: 4 แบบที่มีให้ใช้
ตามเอกสาร Java 21 มี 4 overload:
| รูปแบบ | ใช้เมื่อ |
|---|---|
int clamp(long value, int min, int max) | แปลง long เป็น int แบบไม่ล้น (saturation) |
long clamp(long value, long min, long max) | ค่าขนาดใหญ่ เช่น byte, millisecond |
double clamp(double value, double min, double max) | เปอร์เซ็นต์ ความทึบ ระดับเสียง |
float clamp(float value, float min, float max) | กราฟิก เกม |
แบบแรกน่าสนใจที่สุด เอกสารบอกว่าเพราะขอบเขตเป็น int ผลลัพธ์จึงอยู่ในช่วง int เสมอ ใช้แปลง long เป็น int อย่างปลอดภัยได้ ต่างจากการ cast (int) ที่ถ้าค่าเกินจะวนไปเป็นเลขแปลกๆ
จุดที่ต้องรู้กับ double
- ถ้า
valueเป็นNaNผลก็เป็นNaN(ไม่ถูกบีบ) — ต้องเช็ก NaN เองถ้าค่ามาจากผู้ใช้ - ถ้า
minหรือmaxเป็นNaNจะโยนIllegalArgumentException - เมธอดนี้ถือว่า
-0.0เล็กกว่า0.0จริงๆ เช่นclamp(-0.0, 0.0, 1.0)คืน0.0
โค้ดพร้อม assertions
public class Main {
static int safePageSize(long requested) {
// ลิงก์อาจส่งเลขอะไรก็ได้ บีบให้อยู่ 1..100 และได้ int เสมอ
return Math.clamp(requested, 1, 100);
}
static double volume(double v) {
if (Double.isNaN(v)) return 0.5; // clamp ไม่จัดการ NaN ให้
return Math.clamp(v, 0.0, 1.0);
}
public static void main(String[] args) {
assert safePageSize(999_999L) == 100;
assert safePageSize(0L) == 1;
assert safePageSize(25L) == 25;
assert safePageSize(Long.MAX_VALUE) == 100;
long big = 5_000_000_000L;
assert (int) big != Integer.MAX_VALUE; // cast ตรงๆ ได้เลขเพี้ยน
assert Math.clamp(big, Integer.MIN_VALUE, Integer.MAX_VALUE) == Integer.MAX_VALUE;
assert volume(1.7) == 1.0;
assert volume(-3.0) == 0.0;
assert volume(Double.NaN) == 0.5;
assert Double.isNaN(Math.clamp(Double.NaN, 0.0, 1.0));
boolean threw = false;
try {
Math.clamp(5L, 10L, 1L); // min > max: config ผิด
} catch (IllegalArgumentException e) {
threw = true;
}
assert threw;
System.out.println("ok");
}
}
ตอนตรวจบทความ โค้ดนี้คอมไพล์ด้วย javac และรันด้วย java -ea (เปิด assertions) บน JDK 21 ของเครื่องนี้
ใช้ตรงไหนในงานจริง
| งาน | ตัวอย่าง |
|---|---|
| API pagination | pageSize จาก query string บีบ 1..100 |
| ตั้งค่าจากไฟล์ config | timeout, retry count ไม่ให้ต่ำหรือสูงเกิน |
| UI/เกม | ความทึบ 0.0..1.0, ตำแหน่งตัวละครไม่ให้หลุดจอ |
| แปลงขนาดไฟล์/เวลา | long → int แบบไม่ล้น |
ข้อควรระวัง: clamp คือ “ปรับให้อยู่ในช่วง” ไม่ใช่ “ตรวจว่าถูกต้อง” ถ้าค่าที่ผิดควรถูกปฏิเสธพร้อมบอกผู้ใช้ (เช่น จำนวนเงินโอน) อย่า clamp เงียบๆ ให้ตอบ error แทน
Recap
- Java 21 ขึ้นไป ใช้
Math.clamp(value, min, max)แทน max/min ซ้อนกัน - ใช้
Math.clamp(longValue, intMin, intMax)เพื่อแปลง long เป็น int แบบไม่ล้น - จำไว้ว่า
min > maxจะโยนIllegalArgumentException— ดีต่อการจับ config ผิด - ค่า double ที่เป็น NaN ไม่ถูกบีบ ต้องเช็กเอง
- งานเงินหรือค่าที่ผิดแล้วต้องแจ้งผู้ใช้ อย่า clamp เงียบๆ
- รันโค้ดด้านบนด้วย
java -eaเพื่อเปิด assertions