Home > Enterprise >  Convert double number to digits and vice versa?
Convert double number to digits and vice versa?

Time:10-05

I'm trying to convert a double number into array of digits

Input: 
   double num

Output:
   int[] arrDigit
   int   dotIdx
   bool  isMinus

for example:

Input: 
   double num = -69.69777

Output:
   int[] arrDigit = { 7,7,7,9,6,9,6}
   int   dotIdx = 5
   bool  isMinus = true

And vice versa:

Input: 
   array of input digit commands

Output:
   double num

for example:

Input: 
   Insert digit 6
   Insert digit 9
   Start dot
   Insert digit 6
   Insert digit 9
   Insert digit 7
   Insert digit 7
   Insert digit 7

Output:
   double num=69.69777

The easiest way is using C# string method, I've implemented it:

class DigitToNumTranslator
{
    private bool m_isDot;
    //Minus is handled as operator, not the job for translator

    //Helper
    private StringBuilder m_builder = new StringBuilder();

    public double NumResult
    {
        get
        {
            return double.Parse(m_builder.ToString(), System.Globalization.CultureInfo.InvariantCulture);
        }
    }

    public void Reset()
    {
        m_builder.Clear();
        m_isDot = false;
    }

    public void StartDot()
    {
        if (!m_isDot)
        {
            m_isDot = true;
            m_builder.Append('.');
        }
    }

    public void InsertDigit(int digit)
    {
        m_builder.Append(digit.ToString());
    }
}
class NumToDigitTranslator
{
    private List<int> m_lstDigit;
    private IList<int> m_lstDigitReadOnly;
    private int m_dotIdx;
    private bool m_isMinus;

    public IList<int> LstDigit => m_lstDigitReadOnly;
    public int DotIdx => m_dotIdx;
    public bool IsMinus => m_isMinus;

    public NumToDigitTranslator()
    {
        m_lstDigit = new List<int>();
        m_lstDigitReadOnly = m_lstDigit.AsReadOnly();
    }

    public void Translate(double num)
    {
        m_lstDigit.Clear();
        m_dotIdx = 0;
        m_isMinus = false;

        var szNum = num.ToString(System.Globalization.CultureInfo.InvariantCulture);
        //Won't work if it's 1E 17
        for (var i = 0; i < szNum.Length;   i)
        {
            if (char.IsNumber(szNum[i]))
                m_lstDigit.Add(int.Parse(szNum[i].ToString()));
            else if (szNum[i] == '-')
                m_isMinus = true;
            else if (szNum[i] == '.')
                m_dotIdx = i;
        }

        //Reverse for display
        if (m_dotIdx != 0)
            m_dotIdx = szNum.Length - 1 - m_dotIdx;
        m_lstDigit.Reverse();
    }
}

But the string method is met with the issue "1E 17" (when the number is too long). I don't like the string method very much because it may have unexpected bug (e.g CultureInfo, 1E 17,... ) who knows if there is more case that I don't know - too risky and my application doesn't use string to display number, it combines sprite image to draw the number.

So I'd like to try the math method:

class DigitToNumTranslatorRaw
{
    private double m_numResult;
    private bool m_isDot;
    private int m_dotIdx;

    public double NumResult => m_numResult;

    public void Reset()
    {
        m_numResult = 0;
        m_dotIdx = 1;
        m_isDot = false;
    }

    public void StartDot()
    {
        m_isDot = true;
    }

    public void InsertDigit(int digit)
    {
        if (m_isDot)
        {
            m_numResult  = digit * Math.Pow(10, -m_dotIdx);
              m_dotIdx;
        }
        else
        {
            m_numResult *= 10;
            m_numResult  = digit;
        }
    }
}

class NumToDigitTranslatorRaw
{
    private List<int> m_lstDigit;
    private IList<int> m_lstDigitReadOnly;
    private int m_dotIdx;

    public IList<int> LstDigit => m_lstDigitReadOnly;
    public int DotIdx => m_dotIdx;

    public NumToDigitTranslatorRaw()
    {
        m_lstDigit = new List<int>();
        m_lstDigitReadOnly = m_lstDigit.AsReadOnly();
    }

    public void Translate(double num)
    {
        m_dotIdx = 0;
        m_lstDigit.Clear();

        //WIP (work with int, but not with double, thus failed to get the numbers after dot)
        var intNum = (int)num;
        while (num > 10)
        {
            m_lstDigit.Add((intNum % 10));
            num /= 10;
        }
        if (m_lstDigit.Count > 0)
            m_lstDigit.Reverse();
        else
            m_lstDigit.Add(0);
    }
}

But I meet with 2 problems:

  1. In DigitToNumTranslatorRaw, I don't now if it's better than the string solution. the m_numResult = digit * Math.Pow(10, -m_dotIdx);, num /= 10;,... may cause floating point precision problem and Is Pow the best way for performance?

  2. In NumToDigitTranslatorRaw, I'm still not able to get the number after dot.

I tried to extract the code TryParse of Mircosoft to see how they do it, but it's too complicated I couldn't find where they put the that code.

So my purpose is:

  1. Math method: write DigitToNumTranslatorRaw & NumToDigitTranslatorRaw and make sure it's bug free & floating point accurate & better performance than string method (because I don't deal with CultureInfo.InvariantCulture, 1E 17,...).

  2. If the math method is too hard, I'll just use the string method DigitToNumTranslator & NumToDigitTranslator and deal with each string problem (e.g too long number turn into 1E 17), but the problem is I don't know if I cover all the string problem (e.g the 1E 17 I found out by random testing, the CultureInfo problem I found out by searching on stack overflow), the docs didn't list all the problems I may encounter.

CodePudding user response:

Code usage example:

Digit to number:

private DigitToNumTranslator m_digit = new DigitToNumTranslator();

m_digit.Reset();
var isEnd = false;
//m_lstInputKey is a list of enum E_INPUT_KEY, created earlier by user input
for (; i < m_lstInputKey.Count;   i)
{
    switch (m_lstInputKey[i])
    {
        case E_INPUT_KEY.NUM_0: m_digit.InsertDigit(0); break;
        case E_INPUT_KEY.NUM_1: m_digit.InsertDigit(1); break;
        case E_INPUT_KEY.NUM_2: m_digit.InsertDigit(2); break;
        case E_INPUT_KEY.NUM_3: m_digit.InsertDigit(3); break;
        case E_INPUT_KEY.NUM_4: m_digit.InsertDigit(4); break;
        case E_INPUT_KEY.NUM_5: m_digit.InsertDigit(5); break;
        case E_INPUT_KEY.NUM_6: m_digit.InsertDigit(6); break;
        case E_INPUT_KEY.NUM_7: m_digit.InsertDigit(7); break;
        case E_INPUT_KEY.NUM_8: m_digit.InsertDigit(8); break;
        case E_INPUT_KEY.NUM_9: m_digit.InsertDigit(9); break;
        case E_INPUT_KEY.NUM_DOT: m_digit.StartDot(); break;
        default: isEnd = true; break;
    }
    if (isEnd) break;
}

Console.WriteLine(m_digit.NumResult);

Number to digit:

private NumToDigitTranslator m_numToDigitTranslator = new NumToDigitTranslator();

double dInputNumber = 6969696969696969696996.69696969696969D;
m_numToDigitTranslator.Translate(dInputNumber);

//Draw function is how you draw the information to the screen
DrawListDigit(m_numToDigitTranslator.LstDigit);
DrawMinus(m_numToDigitTranslator.IsMinus);
DrawDot(m_numToDigitTranslator.DotIdx);

Math solution

Code:

#region MATH_WAY
class DigitToNumTranslatorMath
{
    private double m_numResult;
    private bool m_isDot;
    private int m_dotIdx;

    public double NumResult => m_numResult;

    public void Reset()
    {
        m_numResult = 0;
        m_dotIdx = 1;
        m_isDot = false;
    }

    public void StartDot()
    {
        m_isDot = true;
    }

    public void InsertDigit(int digit)
    {
        if (m_isDot)
        {
            m_numResult  = digit * Math.Pow(10, -m_dotIdx);
              m_dotIdx;
        }
        else
        {
            m_numResult *= 10;
            m_numResult  = digit;
        }
    }
}
//Bug: (num - Math.Truncate(num))
//==> floating point problem
//==> 1.9D - Math.Truncate(1.9D) = 0.89999999999999991 (Expected: 0.9)
class NumToDigitTranslatorMath
{
    private List<int> m_lstDigit;
    private IList<int> m_lstDigitReadOnly;
    private int m_dotIdx;
    private bool m_isMinus;

    public IList<int> LstDigit => m_lstDigitReadOnly;
    public int DotIdx => m_dotIdx;
    public bool IsMinus => m_isMinus;

    public NumToDigitTranslatorMath()
    {
        m_lstDigit = new List<int>();
        m_lstDigitReadOnly = m_lstDigit.AsReadOnly();
    }

    public void Translate(double num)
    {
        m_dotIdx = 0;
        m_lstDigit.Clear();

        m_isMinus = num < 0;

        int intDigit;
        double intNum;//Use double type to prevent casting a too big double for int which causes overflow

        //Get the digits on the right of dot
        const int NUM_COUNT_AFTER_DOT = 1000000000;//double has Precision 15-16 digits, but I only need 9 digits
        //Math.Truncate(-1.9)=>-1; Math.Floor(-1.9)=>-2;
        intNum = Math.Truncate((num - Math.Truncate(num)) * NUM_COUNT_AFTER_DOT);//Floating point bug here!!!

        //Remove zeros
        while (intNum > 0)
        {
            intDigit = (int)(intNum % 10);
            if (intDigit != 0)
                break;
            else
                intNum = Math.Truncate(intNum / 10);
        }

        while (intNum > 0)
        {
            intDigit = (int)(intNum % 10);
            intNum = Math.Truncate(intNum / 10);
            m_lstDigit.Add(intDigit);
              m_dotIdx;
        }

        //Get the digits on the left of dot
        intNum = Math.Truncate(num);
        while (intNum > 0)
        {
            intDigit = (int)(intNum % 10);
            intNum = Math.Truncate(intNum / 10);
            m_lstDigit.Add(intDigit);
        }

        if (m_lstDigit.Count == 0)
            m_lstDigit.Add(0);
    }
}
#endregion

Note: There is the floating point problem, for example: 1.9D - Math.Truncate(1.9D) = 0.89999999999999991 (Expected: 0.9).

I was planning to extract the code from .Net source code to implement it the Math way, but I was too lazy so I'll just use the String solution.

String Solution:

Code:

static class CONST_STR_FORMAT
{
    private static System.Globalization.CultureInfo s_ciCommon = System.Globalization.CultureInfo.InvariantCulture;

    public static System.Globalization.CultureInfo CI_COMMON => s_ciCommon;

    //source: https://stackoverflow.com/questions/1546113/double-to-string-conversion-without-scientific-notation
    public const string FORMAT_DOUBLE = "0.###################################################################################################################################################################################################################################################################################################################################################";
}
class DigitToNumTranslator
{
    private bool m_isDot;
    //Minus is handled as operator, not the job for translator

    //Helper
    private StringBuilder m_builder = new StringBuilder();

    public double NumResult
    {
        get
        {
            return double.Parse(m_builder.ToString(), CONST_STR_FORMAT.CI_COMMON);
        }
    }

    public void Reset()
    {
        m_builder.Clear();
        m_isDot = false;
    }

    public void StartDot()
    {
        if (!m_isDot)
        {
            m_isDot = true;
            m_builder.Append('.');
        }
    }

    public void InsertDigit(int digit)
    {
        m_builder.Append(digit);
    }
}

class NumToDigitTranslator
{
    private List<int> m_lstDigit;
    private IList<int> m_lstDigitReadOnly;
    private int m_dotIdx;
    private bool m_isMinus;

    public IList<int> LstDigit => m_lstDigitReadOnly;
    public int DotIdx => m_dotIdx;
    public bool IsMinus => m_isMinus;

    public NumToDigitTranslator()
    {
        m_lstDigit = new List<int>();
        m_lstDigitReadOnly = m_lstDigit.AsReadOnly();
    }

    public void Translate(double num)
    {
        m_lstDigit.Clear();
        m_dotIdx = 0;
        m_isMinus = false;


        var szNum = num.ToString(CONST_STR_FORMAT.FORMAT_DOUBLE, CONST_STR_FORMAT.CI_COMMON);

        for (var i = 0; i < szNum.Length;   i)
        {
            if (char.IsNumber(szNum[i]))
                m_lstDigit.Add(int.Parse(szNum[i].ToString()));
            else if (szNum[i] == '-')
                m_isMinus = true;
            else if (szNum[i] == '.')
                m_dotIdx = i;
        }

        //Reverse for display
        if (m_dotIdx != 0)
            m_dotIdx = szNum.Length - 1 - m_dotIdx;
        m_lstDigit.Reverse();
    }
}

Note: No more headache. What I'm afraid most is bugs by culture (bug that happens on some devices but not on my device), hope the code System.Globalization.CultureInfo.InvariantCulture will make sure that nightmare won't happen.

  • Related