1.3 จำนวนเต็มใหญ่แค่ไหน
กล่อง int มีขนาดตายตัว เก็บเลขได้ถึงค่าหนึ่งเท่านั้น ถ้าผลคำนวณใหญ่เกิน โปรแกรมจะไม่ฟ้องอะไร แต่ได้คำตอบผิดเงียบๆ เรื่องนี้ทำให้คนเสียคะแนนในการแข่งขันมาแล้วนับไม่ถ้วน เพราะโจทย์ชอบให้ตัวเลขใหญ่ถึงหลักพันล้าน หน้านี้จะทำให้มันพังให้เห็นกับตา แล้วแก้ด้วยชนิด long long
int เก็บได้แค่ไหน
ในหน้า 0.1 เราเห็นว่าข้างในคอมพิวเตอร์มีแค่ 0 กับ 1 เลขหนึ่งหลักแบบนี้เรียกว่า bit และ 8 bit รวมกันเรียกว่า byte กล่อง int ใช้ 4 byte หรือ 32 bit
32 bit เรียงได้ 232 แบบ ประมาณ 4.3 พันล้านแบบ แบ่งครึ่งหนึ่งให้เลขติดลบ อีกครึ่งให้ 0 กับเลขบวก ค่าที่แน่นอนดูได้จาก limits.h ซึ่งมีค่าคงที่ INT_MAX กับ INT_MIN ให้
#include <stdio.h>
#include <limits.h>
int main()
{
printf("int max = %d\n", INT_MAX);
printf("int min = %d\n", INT_MIN);
return 0;
}
int max = 2147483647
int min = -2147483648
ไม่ต้องท่องเลขเต็ม จำว่า int เก็บได้ประมาณ 2 × 109 คือสองพันล้าน เป็นเลขสิบหลัก
เกินแล้วเกิดอะไร
ลองเอาค่ามากสุดของ int มาบวกหนึ่ง
#include <stdio.h>
int main()
{
int money;
scanf("%d", &money);
money = money + 1;
printf("%d\n", money);
return 0;
}
2147483647
-2147483648
บวกหนึ่งแล้วกลายเป็นค่าติดลบที่น้อยที่สุด เหมือนเลขไมล์รถที่วิ่งถึง 999999 แล้ววนกลับไปเริ่มใหม่ ต่างกันตรงที่ int วนไปฝั่งติดลบ อาการที่ค่าเกินขอบเขตของชนิดนี้เรียกว่า overflow (ล้น)
ตัวอย่างที่เจอจริงกว่านั้น คือการคูณ ที่ดินกว้าง 50000 เมตร ยาว 50000 เมตร พื้นที่คือ 2500000000 ตารางเมตร เกินสองพันล้านไปแล้ว
#include <stdio.h>
int main()
{
int width, length;
scanf("%d %d", &width, &length);
printf("area = %d\n", width * length);
return 0;
}
50000 50000
area = -1794967296
พื้นที่ติดลบ ไม่มี error ไม่มี warning คอมไพล์ผ่านด้วย -Wall สบายๆ ถ้าส่งโปรแกรมนี้ ชุดทดสอบที่ตัวเลขเล็กจะผ่านหมด และตกเฉพาะชุดที่ตัวเลขใหญ่ ซึ่งหาสาเหตุยากถ้าไม่รู้เรื่องนี้
อย่าพึ่งผลที่วนกลับ
มาตรฐานภาษา C ไม่ได้รับประกันว่า int ที่ล้นจะวนกลับแบบนี้เสมอ เรียกว่าเป็น undefined behavior บนเครื่องเราเห็นวนกลับ แต่ compiler มีสิทธิ์ทำอย่างอื่นได้ สิ่งที่ต้องจำจึงไม่ใช่ว่าล้นแล้วได้เลขอะไร แต่คือ อย่าปล่อยให้ล้นเลย
long long กล่องที่ใหญ่กว่า
long long เป็นจำนวนเต็มเหมือน int แต่ใช้ 8 byte หรือ 64 bit เก็บได้กว้างกว่ามาก
#include <stdio.h>
#include <limits.h>
int main()
{
printf("long long max = %lld\n", LLONG_MAX);
printf("long long min = %lld\n", LLONG_MIN);
return 0;
}
long long max = 9223372036854775807
long long min = -9223372036854775808
ประมาณ 9 × 1018 เป็นเลขสิบเก้าหลัก ใช้ long long ต้องเปลี่ยนสามจุด
- ประกาศเป็น
long long - รับค่าด้วย
%lldแทน%d(มาจาก long long decimal) - พิมพ์ด้วย
%lldแทน%d
#include <stdio.h>
int main()
{
long long width, length;
scanf("%lld %lld", &width, &length);
long long area = width * length;
printf("area = %lld\n", area);
return 0;
}
50000 50000
area = 2500000000
กับดัก int คูณ int
หลายคนแก้แค่ตัวแปรที่เก็บผล แบบนี้
#include <stdio.h>
int main()
{
int width, length;
scanf("%d %d", &width, &length);
long long area = width * length;
printf("area = %lld\n", area);
return 0;
}
50000 50000
area = -1794967296
ยังผิดอยู่ เพราะคอมพิวเตอร์ทำบรรทัดที่ 7 สองจังหวะเหมือนที่เห็นในหน้า 1.1 จังหวะแรกคำนวณ width * length ซึ่งเป็น int คูณ int ผลจึงเป็น int และล้นไปตั้งแต่ตรงนี้ จังหวะที่สองค่อยเอาค่าที่ผิดแล้วไปใส่กล่อง long long กล่องใหญ่แค่ไหนก็ช่วยไม่ได้
วิธีที่ปลอดภัยที่สุดตอนนี้คือ ประกาศตัวแปรที่จะเอามาคูณเป็น long long ตั้งแต่ต้น แบบโปรแกรมก่อนหน้า อีกวิธีคือแปลงชนิดเฉพาะจุด ซึ่งจะได้เรียนในหน้า 1.4
เมื่อไรต้องใช้ long long
โจทย์ทุกข้อบอกขอบเขตของข้อมูลเข้า เช่น 1 ≤ A, B ≤ 1000000000 ก่อนเขียนให้ถามตัวเองว่า ผลที่ใหญ่ที่สุดที่เป็นไปได้คือเท่าไร แล้วเทียบกับ 2 × 109
| โจทย์ | ผลมากสุด | ใช้ |
|---|---|---|
A + B เมื่อ A, B ≤ 109 | 2 × 109 | int พอดีๆ |
A + B + C เมื่อ A, B, C ≤ 109 | 3 × 109 | long long |
A × B เมื่อ A, B ≤ 104 | 108 | int |
A × B เมื่อ A, B ≤ 105 | 1010 | long long |
A × B เมื่อ A, B ≤ 109 | 1018 | long long ยังพอ |
A × B × C เมื่อ A, B, C ≤ 109 | 1027 | ไม่พอทั้งคู่ โจทย์แบบนี้มักให้ตอบเป็นเศษจากการหาร ซึ่งจะเรียนในบทที่ 12 |
ถ้าคำนวณแล้วไม่แน่ใจ ใช้ long long ไปเลย ข้อเสียคือใช้หน่วยความจำมากขึ้นเท่าตัว ซึ่งจะเริ่มมีผลตอนเก็บตัวเลขเป็นล้านตัวในบทที่ 5
ลองทำให้พัง
แบบที่ 1 เปลี่ยนชนิดแล้วลืมเปลี่ยน %d
ในโปรแกรม land ที่ใช้ long long แล้ว แก้บรรทัด printf กลับเป็น %d
land.c:8:21: warning: format '%d' expects argument of type 'int', but argument 2 has type 'long long int' [-Wformat=]
8 | printf("area = %d\n", area);
| ~^ ~~~~
| | |
| int long long int
| %lld
compiler บอกชัดว่า %d รอ int แต่ได้ long long int และแนะให้ใช้ %lld ถ้าดื้อรันต่อ จะได้ area = -1794967296 เหมือนตอนยังไม่แก้ เพราะ printf หยิบไปแค่ครึ่งเดียวของกล่อง
ลืมเปลี่ยน %d ใน scanf ก็ได้คำเตือนคล้ายกัน คือ expects argument of type 'int *', but argument 2 has type 'long long int *' แบบนี้อันตรายกว่า เพราะ scanf เขียนแค่ครึ่งกล่อง อีกครึ่งเป็นค่าขยะ บนเครื่องที่เขียนเอกสารนี้บังเอิญได้คำตอบถูก แต่เปลี่ยนเครื่องหรือเปลี่ยนโปรแกรมนิดเดียวอาจผิดได้ เห็น warning เรื่อง format เมื่อไร แก้ทันที
แบบที่ 2 ใส่เลขใหญ่เกินลงใน int ตรงๆ
#include <stdio.h>
int main()
{
int population = 3000000000;
printf("%d\n", population);
return 0;
}
-1294967296
คอมไพล์ด้วย -Wall แล้วไม่มีคำเตือนเลย ทั้งที่สามพันล้านใส่ใน int ไม่ได้ -Wall ช่วยจับได้หลายอย่าง แต่ไม่ใช่ทุกอย่าง เรื่องขนาดของตัวเลข น้องต้องคิดเองจากขอบเขตในโจทย์
สรุป
intเก็บได้ตั้งแต่ -2147483648 ถึง 2147483647 ประมาณ 2 × 109long longเก็บได้ประมาณ 9 × 1018 รับและพิมพ์ด้วย%lld- เกินขอบเขตเรียกว่า overflow ไม่มี error ไม่มี warning แค่ได้คำตอบผิด
int * intล้นตั้งแต่ตอนคูณ เก็บผลในlong longทีหลังไม่ช่วย- ก่อนเขียน คำนวณผลที่ใหญ่ที่สุดจากขอบเขตในโจทย์ เกิน 2 × 109 ใช้
long long