将双数转换为数字,反之亦然?

Convert double number to digits and vice versa?

我正在尝试将双精度数转换为数字数组

Input: 
   double num

Output:
   int[] arrDigit
   int   dotIdx
   bool  isMinus

例如:

Input: 
   double num = -69.69777

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

反之亦然:

Input: 
   array of input digit commands

Output:
   double num

例如:

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

最简单的方法是使用C#字符串方法,我已经实现了:

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();
    }
}

但是字符串方法遇到了问题“1E+17”(当数字太长时)。我不太喜欢字符串方法,因为它可能有意想不到的错误(例如 CultureInfo,1E+17,...)谁知道是否还有更多我不知道的情况 - 风险太大而且我的应用程序没有使用字符串显示数字,结合sprite图像绘制数字。

所以我想试试数学方法:

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);
    }
}

但是我遇到了两个问题:

  1. DigitToNumTranslatorRaw 中,我现在不知道它是否比字符串解决方案更好。 m_numResult += digit * Math.Pow(10, -m_dotIdx);, num /= 10;,... 可能会导致浮点精度问题,而 Pow 是提高性能的最佳方式吗?

  2. NumToDigitTranslatorRaw中,我仍然无法获取点后的数字。

我试图提取 code TryParse of Mircosoft 看看他们是怎么做的,但是它太复杂了,我找不到他们把代码放在哪里。

所以我的目的是:

  1. 数学方法:编写 DigitToNumTranslatorRaw & NumToDigitTranslatorRaw 并确保它没有错误且浮点数准确且性能优于字符串方法(因为我不处理 CultureInfo.InvariantCulture, 1E+17,...).

  2. 如果数学方法太难,我就用字符串方法DigitToNumTranslator & NumToDigitTranslator来处理每个字符串问题(例如太长的数字变成1E+17), 但问题是我不知道我是否覆盖了所有字符串问题(例如我随机测试发现的 1E+17 ,我通过堆栈溢出搜索发现的 CultureInfo 问题),the docs 没有列出我可能遇到的所有问题。

代码使用示例:

数字转数字:

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);

数字到数字:

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);

数学解法

代码:

#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

注:存在浮点数问题,例如:1.9D - Math.Truncate(1.9D) = 0.89999999999999991 (预期:0.9).

我本来打算从 .Net source 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: 
    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();
    }
}

注意:不再头痛。我最担心的是文化错误(错误发生在某些设备上但我的设备上没有),希望代码 System.Globalization.CultureInfo.InvariantCulture 将确保噩梦不会发生。