N! มีกี่หลักกันหนอ

มีปัญหาของการเขียนโปรแกรมเพื่อหาจำนวนหลักของ $N!$ อยู่ โดยที่ $N$ นั้นมีค่าเป็นจำนวนมาก จะต้องเขียนโปรแกรมอย่างไรดี? วิธีการทั่วไปก็คือหาค่าของ $N!$ แล้วก็มานับจำนวนหลัก ด้วยการหาร 10 ไปเรื่อยๆแล้วดูว่ามีทั้งหมดกี่หลัก ตามโค้ดด้านล่างนี้

typedef unsigned long long uint64;

uint64 factorial(uint64 n) {
    if(n==0) return 1;
    else return n * factorial(n-1);    
}
uint64 numberOfDigits(uint64 n) {
    uint64 count = 1;
    while(n > 10) {
        count++;
        n /= 10;    
    }
    return count;
}

ฟังก์ชันทั้งสองข้างบน อันแรกคือหาค่าของ $N!$ ด้วยการเขียนแบบ recursive ส่วนฟังก์ชันที่สองก็เป็นการหาจำนวนหลัก จะหาจำนวนหลักของ $N!$ ก็ทำได้โดยการเรียกฟังก์ชันแรกก่อน แล้วนำค่าที่ได้มาส่งให้ฟังก์ชันที่สอง แต่ว่าวิธีการนี้ก็ยังมีข้อจำกัดในเรื่องขนาดของ $N$ อยู่ดีครับ โปรแกรมตัวอย่างผมใช้ unsigned long long (uint64) สำหรับเก็บค่า $N!$ ซึ่งจะเก็บได้ประมาณ 18-19 หลักเท่านั้น ดังนั้นแค่ $N$ มีค่า 20 กว่าๆโปรแกรมก็น็อคแล้ว

วิธีแก้ก็มีหลายแบบครับ ง่ายๆก็เปลี่ยนเป็นภาษาอื่นที่เก็บข้อมูลได้มากขึ้น หรือถ้าใช้ Java ก็อาจจะใช้ BigInteger ก็ว่ากันไป แต่ว่าจริงๆแล้วเราสามารถแก้ปัญหานี้ได้โดยที่ไม่ต้องหาค่าของ $N!$ ก่อนเลยครับ เราสามารถหาจำนวนหลักของ $N!$ ได้ทันทีจากวิธีที่จะเสนอต่อไปนี้

ขั้นแรกคือเราสามารถหาจำนวนหลักของเลขจำนวนเต็มใดๆได้จาก การหา log ฐาน 10 ของมัน ลองดูตัวอย่างครับ

  • $log_{10}( 3 ) = 0.477$,  $log_{10}( 5 ) = 0.699$
  • $log_{10}( 13 ) = 1.114$,  $log_{10}( 78 ) = 1.892$
  • $log_{10}( 345 ) = 2.538$,  $log_{10}( 889 ) = 2.949$
  • $log_{10}( 62950 ) = 4.799$,  $log_{10}( 11100 ) = 4.045$

จะมองเห็นความสัมพันธ์ของจำนวนหลักกับค่าของ log 10 คือ จำนวนหลักจะเท่ากับ $\lfloor log_{10}(N) \rfloor + 1$ เนื่องจากว่าที่จริงแล้ว log10 ก็คือการหาว่าเลขตัวนั้นจะประกอบขึ้นจากการคูณด้วย 10 กี่ตัวดังนั้นจึงสามารถใช้บอกถึงจำนวนหลักได้เช่นกัน งั้นคราวนี้เรามาเขียนฟังก์ชันหาจำนวนหลักกันใหม่ด้วย

uint64 numberOfDigits2(uint64 n) {
    if(n==0) return 1;
    return floor(log10(n)) + 1;
}

ทีนี้ถ้าเราจะหาจำนวนหลักของ N! ก็เพียงแต่หาจาก $\lfloor log_{10}(N!) \rfloor + 1$ อ่าว! แล้วจะหา $N!$ ได้ยังไงล่ะในเมื่อถ้าค่า $N$ มากเราก็ยังคำนวน $N!$ ไม่ได้อยู่ดี โอเคครับเราจะไม่คำนวนหา $N!$ เพียงแต่เราจะใช้กฎของ log ข้อนึงที่ว่า $$log(xy) = log(x) + log(y)$$ ดังนั้นแทนที่เราจะหา $log_{10}(N!)$ เราจะหามันจาก $log_{10}(N)$ + $log_{10}(N-1)$ + $log_{10}(N-2)$ + $...$ + $log_{10}(1)$ เราก็จะสามารถหาจำนวนหลักของ $N!$ ได้แล้ว ตามโค้ด(ภาษา C++) ด้านล่างนี้

uint64 factorialDigit(uint64 n) {
    if(n==0) return 1;
    
    long double logSum = 0;
    for(uint64 i = 1; i <= n; i++)
        logSum += log10(i);
    return floor(logSum) + 1;
}

Taxonomy upgrade extras: