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

Convert double number to digits and vice versa?

提问人:123iamking 提问时间:10/2/2021 最后编辑:123iamking 更新时间:10/4/2021 访问量:281

问:

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

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,... ),谁知道是否还有更多我不知道的情况 - 风险太大,我的应用程序不使用字符串来显示数字,它结合了精灵图像来绘制数字。

所以我想试试数学方法:

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. 在,我现在不知道它是否比字符串解决方案更好。这。。。可能会导致浮点精度问题和 Pow 是性能的最佳方式吗?DigitToNumTranslatorRawm_numResult += digit * Math.Pow(10, -m_dotIdx);num /= 10;

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

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

所以我的目的是:

  1. 数学方法:编写并确保它没有错误&浮点准确&比字符串方法更好的性能(因为我不处理CultureInfo.InvariantCulture,1E + 17,...)。DigitToNumTranslatorRawNumToDigitTranslatorRaw

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

C# 数学 双倍 浮点精度

评论

0赞 zaitsman 10/2/2021
是否可以添加示例输入数据和预期输出数据
0赞 123iamking 10/2/2021
@zaitsman:我添加了示例输入、输出数据。
0赞 zaitsman 10/2/2021
你能把它添加为任何实际数组吗?例如 或类似。可以将粘贴器直接复制到 c# 代码中的东西。var input = new [] {"whatever", "here"};var output = new int[] { 42 };
0赞 Klaus Gütter 10/2/2021
您的示例没有精确的双重表示。看这里:给出“-69,697699999999999754436”(另见 stackoverflow.com/questions/588004/...因此,通常,您必须将精度作为参数提供给函数。-69.69777(-69.6977).ToString("N20")
1赞 Caius Jard 10/2/2021
至于数学方法和获取点后的数字。当您反复除以 10 并取模以将数字放在点的左边时,请重新开始,反复乘以 10 并修改以使数字位于右侧

答:

0赞 123iamking 10/4/2021 #1

代码使用示例

数字到数字:

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 源代码中提取代码以数学方式实现它,但我太懒了,所以我只使用 String 解决方案。

字符串解决方案

法典:

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

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