ฉันจะนับจำนวนหลักทั้งหมดในตัวเลขได้อย่างไร


114

ฉันจะนับจำนวนตัวเลขทั้งหมดใน C # ได้อย่างไร ตัวอย่างเช่นหมายเลข 887979789 มี 9 หลัก


6
ลองใช้. ความยาวหากไม่ได้ผลให้แปลงเป็นสตริงก่อน
Breezer

สมมติว่า x = 887979789; x ToString (). Count (); จะให้สิ่งนั้นแก่คุณ
nPcomp

คำตอบ:


176

โดยไม่ต้องแปลงเป็นสตริงคุณสามารถลอง:

Math.Ceiling(Math.Log10(n));

การแก้ไขตามความคิดเห็นของ ysap:

Math.Floor(Math.Log10(n) + 1);

10
ฉันกลัว ceil (log10 (10)) = ceil (1) = 1 ไม่ใช่ 2 อย่างที่ควรจะเป็นสำหรับคำถามนี้!
ysap

3
ขอบคุณเป็นวิธีที่ดี แม้ว่าจะไม่เร็วไปกว่า int count = 0; ทำ {count ++; } ในขณะที่ ((i / = 10)> = 1); :(
Puterdo Borato

3
หากช่วงจำนวนของคุณมีเชิงลบคุณจะต้องใช้ Math.Floor (Math.Log10 (Math.Abs ​​(n)) + 1);
mrcrowl

1
ดีถ้าnมี0ก็สามารถกลับมา1:) เกินไปค่าลบจับเพียงแทนที่ด้วยn Math.Abs(n)
Umair

3
@Puterdo Borato: การทดสอบประสิทธิภาพของฉันแสดงให้เห็นจริง ๆ ว่าวิธีการของคุณเร็วขึ้นเมื่อจำนวนหลักน้อยกว่า 5 ผ่านนั้น Steve's Math.floor เร็วกว่า
stack247

83

ลองสิ่งนี้:

myint.ToString().Length

ได้ผลหรือไม่


25
ควรชี้ให้เห็นว่าคุณมีแนวโน้มที่จะประสบปัญหากับวิธีนี้หากคุณกำลังจัดการกับตัวเลขติดลบ (และเห็นได้ชัดว่าทศนิยม แต่ตัวอย่างใช้ an intดังนั้นฉันถือว่าไม่ใช่ปัญหา)
Cody Gray

2
การจัดสรรสตริง @Krythic เป็นความนิยมใหม่ในโลก. NET
nawfal

1
ใหม่? แทบจะไม่ ฉันจัดสรรสตริงย้อนหลังไปอย่างมากในปี 2010 ช่างเป็นตัวกำหนดเทรนด์ ฮ่า ๆ. คุณพูดถูกแล้ว นี่มันสกปรก!
Andiih

3
@Krythic ไม่ใช่ปี 1980 คอมพิวเตอร์ของคุณมี RAM เพียงพอที่จะบันทึกสตริงอักขระ 10 ตัวลงในหน่วยความจำในช่วงเวลาหนึ่งของการดำเนินการ
MrLore

2
@MrLore ในแอปพลิเคชั่นธรรมดา ๆ อาจเป็นจริง แต่ในโลกแห่งการพัฒนาเกมมันเป็นสัตว์ร้ายที่แตกต่างไปจากเดิมอย่างสิ้นเชิง
Krythic

48

การแก้ไขปัญหา

วิธีการขยายใด ๆ ต่อไปนี้จะทำงานได้ ทั้งหมดถือว่าเครื่องหมายลบเป็นตัวเลขและทำงานได้อย่างถูกต้องสำหรับค่าอินพุตที่เป็นไปได้ทั้งหมด พวกเขายังทำงานกับ. NET Framework และ. NET Core อย่างไรก็ตามมีความแตกต่างด้านประสิทธิภาพที่เกี่ยวข้อง (อธิบายไว้ด้านล่าง) ทั้งนี้ขึ้นอยู่กับ Platform / Framework ที่คุณเลือก

รุ่น Int32:

public static class Int32Extensions
{
    // IF-CHAIN:
    public static int Digits_IfChain(this int n)
    {
        if (n >= 0)
        {
            if (n < 10) return 1;
            if (n < 100) return 2;
            if (n < 1000) return 3;
            if (n < 10000) return 4;
            if (n < 100000) return 5;
            if (n < 1000000) return 6;
            if (n < 10000000) return 7;
            if (n < 100000000) return 8;
            if (n < 1000000000) return 9;
            return 10;
        }
        else
        {
            if (n > -10) return 2;
            if (n > -100) return 3;
            if (n > -1000) return 4;
            if (n > -10000) return 5;
            if (n > -100000) return 6;
            if (n > -1000000) return 7;
            if (n > -10000000) return 8;
            if (n > -100000000) return 9;
            if (n > -1000000000) return 10;
            return 11;
        }
    }

    // USING LOG10:
    public static int Digits_Log10(this int n) =>
        n == 0 ? 1 : (n > 0 ? 1 : 2) + (int)Math.Log10(Math.Abs((double)n));

    // WHILE LOOP:
    public static int Digits_While(this int n)
    {
        int digits = n < 0 ? 2 : 1;
        while ((n /= 10) != 0) ++digits;
        return digits;
    }

    // STRING CONVERSION:
    public static int Digits_String(this int n) =>
        n.ToString().Length;
}

รุ่น Int64:

public static class Int64Extensions
{
    // IF-CHAIN:
    public static int Digits_IfChain(this long n)
    {
        if (n >= 0)
        {
            if (n < 10L) return 1;
            if (n < 100L) return 2;
            if (n < 1000L) return 3;
            if (n < 10000L) return 4;
            if (n < 100000L) return 5;
            if (n < 1000000L) return 6;
            if (n < 10000000L) return 7;
            if (n < 100000000L) return 8;
            if (n < 1000000000L) return 9;
            if (n < 10000000000L) return 10;
            if (n < 100000000000L) return 11;
            if (n < 1000000000000L) return 12;
            if (n < 10000000000000L) return 13;
            if (n < 100000000000000L) return 14;
            if (n < 1000000000000000L) return 15;
            if (n < 10000000000000000L) return 16;
            if (n < 100000000000000000L) return 17;
            if (n < 1000000000000000000L) return 18;
            return 19;
        }
        else
        {
            if (n > -10L) return 2;
            if (n > -100L) return 3;
            if (n > -1000L) return 4;
            if (n > -10000L) return 5;
            if (n > -100000L) return 6;
            if (n > -1000000L) return 7;
            if (n > -10000000L) return 8;
            if (n > -100000000L) return 9;
            if (n > -1000000000L) return 10;
            if (n > -10000000000L) return 11;
            if (n > -100000000000L) return 12;
            if (n > -1000000000000L) return 13;
            if (n > -10000000000000L) return 14;
            if (n > -100000000000000L) return 15;
            if (n > -1000000000000000L) return 16;
            if (n > -10000000000000000L) return 17;
            if (n > -100000000000000000L) return 18;
            if (n > -1000000000000000000L) return 19;
            return 20;
        }
    }

    // USING LOG10:
    public static int Digits_Log10(this long n) =>
        n == 0L ? 1 : (n > 0L ? 1 : 2) + (int)Math.Log10(Math.Abs((double)n));

    // WHILE LOOP:
    public static int Digits_While(this long n)
    {
        int digits = n < 0 ? 2 : 1;
        while ((n /= 10L) != 0L) ++digits;
        return digits;
    }

    // STRING CONVERSION:
    public static int Digits_String(this long n) =>
        n.ToString().Length;
}

อภิปรายผล

คำตอบนี้รวมถึงการทดสอบที่ทำสำหรับทั้งสองInt32และInt64ประเภทโดยใช้อาร์เรย์ของ100.000.000ตัวอย่างint/ longตัวเลขแบบสุ่ม ชุดข้อมูลแบบสุ่มจะถูกประมวลผลล่วงหน้าเป็นอาร์เรย์ก่อนที่จะดำเนินการทดสอบ

การทดสอบความสอดคล้องในหมู่ที่ 4 วิธีการที่แตกต่างกันนอกจากนี้ยังได้ดำเนินการสำหรับMinValueกรณีชายแดนลบ-1, 0, 1กรณีชายแดนบวกMaxValueและยังสำหรับชุดข้อมูลที่สุ่มทั้งหมด ไม่มีการทดสอบความสอดคล้องล้มเหลวสำหรับวิธีการข้างต้นยกเว้นสำหรับวิธี LOG10 (จะกล่าวถึงในภายหลัง)

การทดสอบดำเนินการใน.NET Framework 4.7.2และ.NET Core 2.2; สำหรับx86และx64แพลตฟอร์มบนเครื่องโปรเซสเซอร์ Intel 64 บิตพร้อมWindows 10และด้วยVS2017 v.15.9.17. 4 กรณีต่อไปนี้มีผลเหมือนกันกับผลการดำเนินงาน:

.NET Framework (x86)

  • Platform = x86

  • Platform = AnyCPU, Prefer 32-bitมีการตรวจสอบในการตั้งค่าโครงการ

.NET Framework (x64)

  • Platform = x64

  • Platform = AnyCPU, Prefer 32-bitไม่ได้ถูกเลือกในการตั้งค่าโครงการ

.NET Core (x86)

  • "C:\Program Files (x86)\dotnet\dotnet.exe" bin\Release\netcoreapp2.2\ConsoleApp.dll

  • "C:\Program Files (x86)\dotnet\dotnet.exe" bin\x86\Release\netcoreapp2.2\ConsoleApp.dll

.NET Core (x64)

  • "C:\Program Files\dotnet\dotnet.exe" bin\Release\netcoreapp2.2\ConsoleApp.dll

  • "C:\Program Files\dotnet\dotnet.exe" bin\x64\Release\netcoreapp2.2\ConsoleApp.dll

ผล

การทดสอบประสิทธิภาพด้านล่างทำให้เกิดการแจกแจงค่าที่สม่ำเสมอระหว่างค่าที่หลากหลายซึ่งจำนวนเต็มสามารถสันนิษฐานได้ ซึ่งหมายความว่ามีโอกาสสูงกว่ามากในการทดสอบค่าด้วยตัวเลขจำนวนมาก ในสถานการณ์จริงค่าส่วนใหญ่อาจน้อยดังนั้น IF-CHAIN ​​ควรทำงานได้ดียิ่งขึ้น นอกจากนี้โปรเซสเซอร์จะแคชและปรับแต่งการตัดสินใจ IF-CHAIN ​​ตามชุดข้อมูลของคุณ

ในฐานะที่เป็น@AlanSingfieldชี้ให้เห็นในส่วนความคิดเห็นวิธี LOG10 จะต้องได้รับการแก้ไขด้วยการหล่อให้doubleภายในMath.Abs()สำหรับกรณีที่เมื่อค่าเข้าเป็นหรือint.MinValuelong.MinValue

เกี่ยวกับการทดสอบประสิทธิภาพในช่วงต้นที่ฉันได้ดำเนินการก่อนที่จะแก้ไขคำถามนี้ (ต้องแก้ไขเป็นล้านครั้งแล้ว) มีกรณีเฉพาะที่ระบุโดย@ GyörgyKőszegซึ่งวิธี IF-CHAIN ​​ทำงานช้ากว่าวิธี LOG10

นี้ยังคงเกิดขึ้นแม้ว่าขนาดของความแตกต่างกลายเป็นที่ต่ำกว่ามากหลังจากการแก้ไขสำหรับปัญหาแหลมออกโดย@AlanSingfield การแก้ไขนี้ (เพิ่มโยนไปdouble) ทำให้เกิดข้อผิดพลาดในการคำนวณเมื่อค่าเข้าเป็นว่า-999999999999999999: วิธี LOG10 ผลตอบแทนแทน20 19นอกจากนี้วิธีการ LOG10 ยังต้องมีตัวifป้องกันสำหรับกรณีที่ค่าอินพุตเป็นศูนย์

วิธีการ LOG10 ค่อนข้างยุ่งยากในการทำงานกับค่าทั้งหมดซึ่งหมายความว่าคุณควรหลีกเลี่ยง หากมีใครพบวิธีทำให้มันทำงานได้อย่างถูกต้องสำหรับการทดสอบความสอดคล้องทั้งหมดด้านล่างนี้โปรดโพสต์ความคิดเห็น!

วิธี WHILE ยังมีเวอร์ชัน refactored ล่าสุดซึ่งเร็วกว่า แต่ก็ยังช้าสำหรับPlatform = x86(ฉันไม่พบสาเหตุว่าทำไมจนถึงตอนนี้)

วิธี STRING ช้าอย่างต่อเนื่อง: มันจัดสรรหน่วยความจำมากเกินไปโดยไม่ได้ตั้งใจ ที่น่าสนใจคือใน. NET Core การจัดสรรสตริงดูเหมือนจะเร็วกว่าใน. NET Framework มาก ดีแล้วที่รู้.

วิธี IF-CHAIN ​​ควรมีประสิทธิภาพดีกว่าวิธีอื่น ๆ ทั้งหมดใน 99.99% ของกรณี และในความเห็นส่วนตัวของฉันเป็นทางเลือกที่ดีที่สุดของคุณ (พิจารณาจากการปรับเปลี่ยนทั้งหมดที่จำเป็นเพื่อให้วิธีการ LOG10 ทำงานได้อย่างถูกต้องและประสิทธิภาพที่ไม่ดีของอีกสองวิธี)

สุดท้ายผลลัพธ์คือ:

ใส่คำอธิบายภาพที่นี่

เนื่องจากผลลัพธ์เหล่านี้ขึ้นอยู่กับฮาร์ดแวร์ฉันขอแนะนำให้ทำการทดสอบประสิทธิภาพด้านล่างบนคอมพิวเตอร์ของคุณเองหากคุณต้องการความแน่ใจ 100% ในกรณีเฉพาะของคุณ

รหัสทดสอบ

ด้านล่างนี้คือรหัสสำหรับการทดสอบประสิทธิภาพและการทดสอบความสอดคล้องด้วย ใช้รหัสเดียวกันสำหรับทั้ง. NET Framework และ. NET Core

using System;
using System.Diagnostics;

namespace NumberOfDigits
{
    // Performance Tests:
    class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("\r\n.NET Core");

            RunTests_Int32();
            RunTests_Int64();
        }

        // Int32 Performance Tests:
        private static void RunTests_Int32()
        {
            Console.WriteLine("\r\nInt32");

            const int size = 100000000;
            int[] samples = new int[size];
            Random random = new Random((int)DateTime.Now.Ticks);
            for (int i = 0; i < size; ++i)
                samples[i] = random.Next(int.MinValue, int.MaxValue);

            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_IfChain();
            sw1.Stop();
            Console.WriteLine($"IfChain: {sw1.ElapsedMilliseconds} ms");

            Stopwatch sw2 = new Stopwatch();
            sw2.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_Log10();
            sw2.Stop();
            Console.WriteLine($"Log10: {sw2.ElapsedMilliseconds} ms");

            Stopwatch sw3 = new Stopwatch();
            sw3.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_While();
            sw3.Stop();
            Console.WriteLine($"While: {sw3.ElapsedMilliseconds} ms");

            Stopwatch sw4 = new Stopwatch();
            sw4.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_String();
            sw4.Stop();
            Console.WriteLine($"String: {sw4.ElapsedMilliseconds} ms");


            // Start of consistency tests:
            Console.WriteLine("Running consistency tests...");
            bool isConsistent = true;

            // Consistency test on random set:
            for (int i = 0; i < samples.Length; ++i)
            {
                int s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test of special values:
            samples = new int[]
            {
                0,
                int.MinValue, -1000000000, -999999999, -100000000, -99999999, -10000000, -9999999, -1000000, -999999, -100000, -99999, -10000, -9999, -1000, -999, -100, -99, -10, -9, - 1,
                int.MaxValue, 1000000000, 999999999, 100000000, 99999999, 10000000, 9999999, 1000000, 999999, 100000, 99999, 10000, 9999, 1000, 999, 100, 99, 10, 9,  1,
            };
            for (int i = 0; i < samples.Length; ++i)
            {
                int s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test result:
            if (isConsistent)
                Console.WriteLine("Consistency tests are OK");
        }

        // Int64 Performance Tests:
        private static void RunTests_Int64()
        {
            Console.WriteLine("\r\nInt64");

            const int size = 100000000;
            long[] samples = new long[size];
            Random random = new Random((int)DateTime.Now.Ticks);
            for (int i = 0; i < size; ++i)
                samples[i] = Math.Sign(random.Next(-1, 1)) * (long)(random.NextDouble() * long.MaxValue);

            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_IfChain();
            sw1.Stop();
            Console.WriteLine($"IfChain: {sw1.ElapsedMilliseconds} ms");

            Stopwatch sw2 = new Stopwatch();
            sw2.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_Log10();
            sw2.Stop();
            Console.WriteLine($"Log10: {sw2.ElapsedMilliseconds} ms");

            Stopwatch sw3 = new Stopwatch();
            sw3.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_While();
            sw3.Stop();
            Console.WriteLine($"While: {sw3.ElapsedMilliseconds} ms");

            Stopwatch sw4 = new Stopwatch();
            sw4.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_String();
            sw4.Stop();
            Console.WriteLine($"String: {sw4.ElapsedMilliseconds} ms");

            // Start of consistency tests:
            Console.WriteLine("Running consistency tests...");
            bool isConsistent = true;

            // Consistency test on random set:
            for (int i = 0; i < samples.Length; ++i)
            {
                long s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test of special values:
            samples = new long[] 
            {
                0,
                long.MinValue, -1000000000000000000, -999999999999999999, -100000000000000000, -99999999999999999, -10000000000000000, -9999999999999999, -1000000000000000, -999999999999999, -100000000000000, -99999999999999, -10000000000000, -9999999999999, -1000000000000, -999999999999, -100000000000, -99999999999, -10000000000, -9999999999, -1000000000, -999999999, -100000000, -99999999, -10000000, -9999999, -1000000, -999999, -100000, -99999, -10000, -9999, -1000, -999, -100, -99, -10, -9, - 1,
                long.MaxValue, 1000000000000000000, 999999999999999999, 100000000000000000, 99999999999999999, 10000000000000000, 9999999999999999, 1000000000000000, 999999999999999, 100000000000000, 99999999999999, 10000000000000, 9999999999999, 1000000000000, 999999999999, 100000000000, 99999999999, 10000000000, 9999999999, 1000000000, 999999999, 100000000, 99999999, 10000000, 9999999, 1000000, 999999, 100000, 99999, 10000, 9999, 1000, 999, 100, 99, 10, 9,  1,
            };
            for (int i = 0; i < samples.Length; ++i)
            {
                long s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test result:
            if (isConsistent)
                Console.WriteLine("Consistency tests are OK");
        }
    }
}

4
ฉันชอบวิธีแก้ปัญหานี้มันอ่านง่ายกว่าเทคนิคทางคณิตศาสตร์และความเร็วก็พูดถึงตัวมันเองความรุ่งโรจน์
MrLore

3
เหตุใดจึงไม่ถูกทำเครื่องหมายว่าเป็นโซลูชัน ประสิทธิภาพเป็นเรื่องสำคัญและนี่น่าจะเป็นคำตอบที่ครอบคลุมที่สุด
Martien de Jong

ที่น่าสนใจที่ฉันได้รับผลลัพธ์ที่แตกต่าง สำหรับค่าสุ่ม Log10 และ brute force นั้นเกือบจะเหมือนกัน แต่สำหรับlong.MaxValueLog10 นั้นดีกว่ามาก หรือเป็นเพียงใน. NET Core?
GyörgyKőszeg

@ GyörgyKőszeg: ฉันได้เพิ่มการทดสอบสำหรับ Int64 โปรดทราบว่าการทดสอบInt32และInt64สร้างชุดข้อมูลที่แตกต่างกันซึ่งอาจอธิบายInt64ได้ว่าเหตุใดจึงเร็วกว่าInt32ในบางกรณี แม้ว่าภายในการInt32ทดสอบและภายในการInt64ทดสอบชุดข้อมูลจะไม่เปลี่ยนแปลงเมื่อทดสอบวิธีการคำนวณที่แตกต่างกัน ตอนนี้เกี่ยวกับ. NET Core ฉันสงสัยว่ามีการเพิ่มประสิทธิภาพเวทย์มนตร์ในไลบรารีคณิตศาสตร์ซึ่งจะเปลี่ยนผลลัพธ์เหล่านี้ แต่ฉันชอบที่จะได้ยินเพิ่มเติมเกี่ยวกับเรื่องนั้น (คำตอบของฉันมีขนาดใหญ่อยู่แล้วอาจเป็นหนึ่งใน SO ที่ใหญ่ที่สุด ;-)
sɐunıɔןɐqɐp

@ GyörgyKőszeg: นอกจากนี้การวัดประสิทธิภาพระดับต่ำยังเป็นเรื่องยุ่งยากมาก ฉันมักจะชอบที่จะให้รหัสที่ง่ายที่สุด (ฉันชอบง่ายforลูปมากกว่าenumerationsผมดำเนินการก่อนชุดข้อมูลแบบสุ่มและหลีกเลี่ยงการใช้ยาชื่อสามัญ, งาน, Function<>, Action<>หรือใด ๆ กรอบการวัดสีดำกล่อง) โดยสรุปให้มันง่าย ฉันยังฆ่าแอปพลิเคชั่นที่ไม่จำเป็นทั้งหมด (Skype, Windows Defender, ปิดการใช้งาน Anti-Virus, Chrome, Microsoft Office cache ฯลฯ )
sɐunıɔןɐqɐp

13

ไม่ใช่ C # โดยตรง แต่สูตรคือ: n = floor(log10(x)+1)


2
log10 (0) คือ -infinity
Alex Klaus

2
@Klaus - log10 (0) ไม่ได้กำหนดจริง แต่คุณเข้าใจถูกแล้วว่าเป็นกรณีพิเศษที่ต้องได้รับการทดสอบและปฏิบัติแยกกัน นอกจากนี้ยังเป็นจริงสำหรับจำนวนเต็มที่ไม่ใช่จำนวนเต็มบวก ดูความคิดเห็นต่อคำตอบของสตีฟ
ysap

@ysap: Log10 ค่อนข้างยุ่งยากในการทำงานอย่างถูกต้อง คุณมีแนวคิดเกี่ยวกับวิธีการนำไปใช้อย่างถูกต้องสำหรับค่าอินพุตที่เป็นไปได้ทุกช่วงหรือไม่?
sɐunıɔןɐqɐp

@ sɐunıɔןɐqɐp - log10ในกรณีส่วนใหญ่คือฟังก์ชันห้องสมุด ทำไมคุณถึงต้องการติดตั้งด้วยตัวเองและคุณพบปัญหาอะไร หรือโดยทั่วไปlog10(x) = log2(x) / log2(10) logA(x) = logB(x) / logB(A)
ysap

ฉันไม่ได้ตั้งใจที่จะใช้ Log10 อีกครั้งฉันหมายถึงLog10(0)-infinity ไม่สามารถใช้ Log10 เพื่อคำนวณจำนวนหลักของตัวเลขเชิงลบเว้นแต่คุณMath.Abs()จะใช้ก่อนที่จะส่งค่าไปยัง Log10 แต่จากนั้นก็Math.Abs(int.MinValue)พ่นข้อยกเว้น ( long.MinValueเช่นกันในกรณีของ Int64) หากเราร่ายตัวเลขให้เป็นสองเท่าก่อนที่จะส่งไปยัง Log10 มันจะใช้ได้กับตัวเลขเกือบทั้งหมดยกเว้น-999999999999999999(ในกรณีของ Int64) คุณรู้สูตรคำนวณตัวเลขที่ใช้ log10 และยอมรับค่า int32 หรือ int64 เป็นอินพุตและเอาต์พุตเฉพาะค่าที่ถูกต้องหรือไม่?
sɐunıɔןɐqɐp

9

คำตอบที่นี่ใช้ได้กับจำนวนเต็มที่ไม่ได้ลงนาม แต่ฉันไม่พบวิธีแก้ปัญหาที่ดีในการรับจำนวนหลักจากทศนิยมและคู่

public static int Length(double number)
{
    number = Math.Abs(number);
    int length = 1;
    while ((number /= 10) >= 1)
        length++;
    return length;
}
//number of digits in 0 = 1,
//number of digits in 22.1 = 2,
//number of digits in -23 = 2

คุณสามารถเปลี่ยนประเภทการป้อนข้อมูลจากdoubleเป็นdecimalถ้าความแม่นยำมีความสำคัญ แต่ทศนิยมก็มีขีด จำกัด เช่นกัน


7

คำตอบของSteve นั้นถูกต้องแต่ใช้ไม่ได้กับจำนวนเต็มที่น้อยกว่า 1

นี่คือเวอร์ชันอัปเดตที่ใช้งานได้กับเชิงลบ:

int digits = n == 0 ? 1 : Math.Floor(Math.Log10(Math.Abs(n)) + 1)

คุณขาดการคัดเลือกนักแสดงไป int:digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1);
sɐunıɔןɐqɐp

ฉันทำมันโดยไม่มีคำสั่ง if: digit = (int) Math.Floor (Math.Abs ​​(Math.Log10 (Math.Abs ​​(n))) + 1)
KOLRH

n = int.MinValueนี้พ่นยกเว้นเมื่อ
sɐunıɔןɐqɐp

5

ใช้การเรียกซ้ำ (บางครั้งถามในการสัมภาษณ์)

public int CountDigits(int number)
{
    // In case of negative numbers
    number = Math.Abs(number);

    if (number >= 10)
        return CountDigits(number / 10) + 1;
    return 1;
 }

1
number = int.MinValueนี้พ่นยกเว้นเมื่อ
sɐunıɔןɐqɐp


2

นี่คือการใช้งานโดยใช้การค้นหาแบบไบนารี ดูเหมือนจะเร็วที่สุดใน int32

การใช้งาน Int64 ถูกปล่อยให้เป็นแบบฝึกหัดสำหรับผู้อ่าน (!)

ฉันลองใช้ Array.BinarySearch แทนการเข้ารหัสต้นไม้อย่างหนัก แต่นั่นก็เร็วกว่าครึ่งหนึ่ง

แก้ไข: ตารางการค้นหาเร็วกว่าการค้นหาแบบไบนารีโดยเสียค่าใช้จ่ายในการใช้หน่วยความจำมากขึ้น ในความเป็นจริงฉันอาจใช้การค้นหาแบบไบนารีในการผลิตตารางการค้นหามีความซับซ้อนมากสำหรับการเพิ่มความเร็วที่น่าจะถูกบดบังโดยส่วนอื่น ๆ ของซอฟต์แวร์

Lookup-Table: 439 ms
Binary-Search: 1069 ms
If-Chain: 1409 ms
Log10: 1145 ms
While: 1768 ms
String: 5153 ms

รุ่นตารางการค้นหา:

static byte[] _0000llll = new byte[0x10000];
static byte[] _FFFFllll = new byte[0x10001];
static sbyte[] _hhhhXXXXdigits = new sbyte[0x10000];

// Special cases where the high DWORD is not enough information to find out how
// many digits.
static ushort[] _lowordSplits = new ushort[12];
static sbyte[] _lowordSplitDigitsLT = new sbyte[12];
static sbyte[] _lowordSplitDigitsGE = new sbyte[12];

static Int32Extensions()
{
    // Simple lookup tables for number of digits where value is 
    //    0000xxxx (0 .. 65535)
    // or FFFFxxxx (-1 .. -65536)
    precomputePositiveLo16();
    precomputeNegativeLo16();

    // Hiword is a little more complex
    precomputeHiwordDigits();
}

private static void precomputeHiwordDigits()
{
    int b = 0;

    for(int hhhh = 0; hhhh <= 0xFFFF; hhhh++)
    {
        // For hiword hhhh, calculate integer value for loword of 0000 and FFFF.
        int hhhh0000 = (unchecked(hhhh * 0x10000));  // wrap around on negatives
        int hhhhFFFF = hhhh0000 + 0xFFFF;

        // How many decimal digits for each?
        int digits0000 = hhhh0000.Digits_IfChain();
        int digitsFFFF = hhhhFFFF.Digits_IfChain();

        // If same number of decimal digits, we know that when we see that hiword
        // we don't have to look at the loword to know the right answer.
        if(digits0000 == digitsFFFF)
        {
            _hhhhXXXXdigits[hhhh] = (sbyte)digits0000;
        }
        else
        {
            bool negative = hhhh >= 0x8000;

            // Calculate 10, 100, 1000, 10000 etc
            int tenToThePower = (int)Math.Pow(10, (negative ? digits0000 : digitsFFFF) - 1);

            // Calculate the loword of the 10^n value.
            ushort lowordSplit = unchecked((ushort)tenToThePower);
            if(negative)
                lowordSplit = unchecked((ushort)(2 + (ushort)~lowordSplit));

            // Store the split point and digits into these arrays
            _lowordSplits[b] = lowordSplit;
            _lowordSplitDigitsLT[b] = (sbyte)digits0000;
            _lowordSplitDigitsGE[b] = (sbyte)digitsFFFF;

            // Store the minus of the array index into the digits lookup. We look for
            // minus values and use these to trigger using the split points logic.
            _hhhhXXXXdigits[hhhh] = (sbyte)(-b);
            b++;
        }
    }
}

private static void precomputePositiveLo16()
{
    for(int i = 0; i <= 9; i++)
        _0000llll[i] = 1;

    for(int i = 10; i <= 99; i++)
        _0000llll[i] = 2;

    for(int i = 100; i <= 999; i++)
        _0000llll[i] = 3;

    for(int i = 1000; i <= 9999; i++)
        _0000llll[i] = 4;

    for(int i = 10000; i <= 65535; i++)
        _0000llll[i] = 5;
}

private static void precomputeNegativeLo16()
{
    for(int i = 0; i <= 9; i++)
        _FFFFllll[65536 - i] = 1;

    for(int i = 10; i <= 99; i++)
        _FFFFllll[65536 - i] = 2;

    for(int i = 100; i <= 999; i++)
        _FFFFllll[65536 - i] = 3;

    for(int i = 1000; i <= 9999; i++)
        _FFFFllll[65536 - i] = 4;

    for(int i = 10000; i <= 65535; i++)
        _FFFFllll[65536 - i] = 5;
}



public static int Digits_LookupTable(this int n)
{
    // Split input into low word and high word.
    ushort l = unchecked((ushort)n);
    ushort h = unchecked((ushort)(n >> 16));

    // If the hiword is 0000 or FFFF we have precomputed tables for these.
    if(h == 0x0000)
    {
        return _0000llll[l];
    }
    else if(h == 0xFFFF)
    {
        return _FFFFllll[l];
    }

    // In most cases the hiword will tell us the number of decimal digits.
    sbyte digits = _hhhhXXXXdigits[h];

    // We put a positive number in this lookup table when
    // hhhh0000 .. hhhhFFFF all have the same number of decimal digits.
    if(digits > 0)
        return digits;

    // Where the answer is different for hhhh0000 to hhhhFFFF, we need to
    // look up in a separate array to tell us at what loword the change occurs.
    var splitIndex = (sbyte)(-digits);

    ushort lowordSplit = _lowordSplits[splitIndex];

    // Pick the correct answer from the relevant array, depending whether
    // our loword is lower than the split point or greater/equal. Note that for
    // negative numbers, the loword is LOWER for MORE decimal digits.
    if(l < lowordSplit)
        return _lowordSplitDigitsLT[splitIndex];
    else
        return _lowordSplitDigitsGE[splitIndex];
}

เวอร์ชันการค้นหาแบบไบนารี

        public static int Digits_BinarySearch(this int n)
        {
            if(n >= 0)
            {
                if(n <= 9999) // 0 .. 9999
                {
                    if(n <= 99) // 0 .. 99
                    {
                        return (n <= 9) ? 1 : 2;
                    }
                    else // 100 .. 9999
                    {
                        return (n <= 999) ? 3 : 4;
                    }
                }
                else // 10000 .. int.MaxValue
                {
                    if(n <= 9_999_999) // 10000 .. 9,999,999
                    {
                        if(n <= 99_999)
                            return 5;
                        else if(n <= 999_999)
                            return 6;
                        else
                            return 7;
                    }
                    else // 10,000,000 .. int.MaxValue
                    {
                        if(n <= 99_999_999)
                            return 8;
                        else if(n <= 999_999_999)
                            return 9;
                        else
                            return 10;
                    }
                }
            }
            else
            {
                if(n >= -9999) // -9999 .. -1
                {
                    if(n >= -99) // -99 .. -1
                    {
                        return (n >= -9) ? 1 : 2;
                    }
                    else // -9999 .. -100
                    {
                        return (n >= -999) ? 3 : 4;
                    }
                }
                else // int.MinValue .. -10000
                {
                    if(n >= -9_999_999) // -9,999,999 .. -10000
                    {
                        if(n >= -99_999)
                            return 5;
                        else if(n >= -999_999)
                            return 6;
                        else
                            return 7;
                    }
                    else // int.MinValue .. -10,000,000 
                    {
                        if(n >= -99_999_999)
                            return 8;
                        else if(n >= -999_999_999)
                            return 9;
                        else
                            return 10;
                    }
                }
            }
        }

        Stopwatch sw0 = new Stopwatch();
        sw0.Start();
        for(int i = 0; i < size; ++i) samples[i].Digits_BinarySearch();
        sw0.Stop();
        Console.WriteLine($"Binary-Search: {sw0.ElapsedMilliseconds} ms");

แนวทางที่น่าสนใจมาก มันเร็วกว่าเมธอด "Log10", "string.Length" และ "While" สำหรับค่าจำนวนเต็มที่กระจายสม่ำเสมอ ในสถานการณ์จริงการกระจายของค่าจำนวนเต็มจะต้องได้รับการพิจารณาเสมอบนโซลูชันแบบ if-chain +1
sɐunıɔןɐqɐp

วิธีการ LookUpTable ดูเหมือนจะเร็วมากสำหรับสถานการณ์ที่การเข้าถึงหน่วยความจำไม่ใช่ปัญหาคอขวด ฉันเชื่อเป็นอย่างยิ่งว่าสำหรับสถานการณ์ที่มีการเข้าถึงหน่วยความจำบ่อยๆ LookUpTable จะทำงานช้ากว่าวิธีการแบบ if-chain เช่นเดียวกับ BinSearch ที่คุณแนะนำ อย่างไรก็ตามคุณมีการInt64ติดตั้ง LookUpTable หรือไม่? หรือคุณคิดว่ามันซับซ้อนเกินไปที่จะใช้มัน? ฉันต้องการเรียกใช้การทดสอบประสิทธิภาพในภายหลังในชุดที่สมบูรณ์
sɐunıɔןɐqɐp

เฮ้ไม่ได้ไปไกลถึง 64 บิต หลักการจะต้องแตกต่างกันเล็กน้อยตรงที่คุณต้องการระดับ 4x มากกว่าแค่คำศัพท์สูงและต่ำ ยอมรับอย่างแน่นอนว่าในโลกแห่งความเป็นจริงแคช CPU ของคุณจะมีความต้องการในการแข่งขันอื่น ๆ มากมายสำหรับพื้นที่และมีพื้นที่มากมายสำหรับการปรับปรุงในการลดขนาดของการค้นหา (>> 1 จากนั้นก็คำนึงถึงตัวเลขคู่เท่านั้น) . การค้นหาแบบไบนารีสามารถปรับปรุงได้โดยการให้น้ำหนักไปที่ 9,10,8 หลักแทนที่จะเป็น 1,2,3,4 - เนื่องจากการกระจายของชุดข้อมูลแบบสุ่มของคุณ
Alan Singfield

1

การหารตัวเลขด้วย 10 จะทำให้คุณได้เลขหลักทางซ้ายสุดจากนั้นทำ mod 10 กับตัวเลขให้ตัวเลขที่ไม่มีหลักแรกและทำซ้ำจนกว่าคุณจะมีตัวเลขทั้งหมด



0

สร้างเมธอดที่ส่งคืนตัวเลขทั้งหมดและอีกวิธีหนึ่งที่นับจำนวน:

public static int GetNumberOfDigits(this long value)
{
    return value.GetDigits().Count();
}

public static IEnumerable<int> GetDigits(this long value)
{
    do
    {
        yield return (int)(value % 10);
        value /= 10;
    } while (value != 0);
}

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

ฉันยังพบไฟล์ if -chain ที่เสนอในคำตอบอื่น ๆ ที่ดูน่าเกลียดไปหน่อย

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

โปรดทราบว่าไม่ถือว่าเครื่องหมายลบเป็นตัวเลข


-2

แปลงเป็นสตริงจากนั้นคุณสามารถนับเลขทศนิยมด้วยวิธี. length ชอบ:

String numberString = "855865264".toString();
int NumLen = numberString .Length;

1
การจัดสรรสตริงไม่จำเป็นโดยสิ้นเชิง
Krythic

-2

ขึ้นอยู่กับว่าคุณต้องการทำอะไรกับตัวเลข คุณสามารถวนซ้ำตัวเลขโดยเริ่มจากตัวสุดท้ายไปยังตัวแรกดังนี้:

int tmp = number;
int lastDigit = 0;
do
{
    lastDigit = tmp / 10;
    doSomethingWithDigit(lastDigit);
    tmp %= 10;
} while (tmp != 0);

1
ตรรกะของคุณกลับกัน คุณต้องใช้%เพื่อรับตัวเลขจากนั้นจึง/=จะตัดมันลง
julealgon


-3

สมมติว่าคำถามของคุณอ้างถึง int ผลงานต่อไปนี้เป็นค่าลบ / บวกและศูนย์เช่นกัน:

Math.Floor((decimal) Math.Abs(n)).ToString().Length
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.