【问题标题】:Convert double number to digits and vice versa?将双数转换为数字,反之亦然?
【发布时间】:2021-11-23 14:11:25
【问题描述】:

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

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”的问题(当数字太长时)。我不太喜欢 string 方法,因为它可能有意外的错误(例如 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. 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 没有列出我可能遇到的所有问题。

【问题讨论】:

  • 能否添加样本输入数据和预期输出数据
  • @zaitsman:我添加了示例输入、输出数据。
  • 您可以将其添加为任何实际数组。例如var input = new [] {"whatever", "here"}; var output = new int[] { 42 }; 或类似的。可以将 paste 直接复制到 c# 代码中的东西。
  • 您的示例 -69.69777 没有精确的双重表示。看这里:(-69.6977).ToString("N20") 给出“-69,69769999999999754436”(另见stackoverflow.com/questions/588004/…)。所以一般来说,您必须将精度作为参数提供给函数。
  • 至于数学方法和获取点后面的数字..当您反复除以 10 以得到点左侧的数字时,重新开始反复乘以 10 和修改把数字放在右边

标签: c# math numbers double floating-accuracy


【解决方案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 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: 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 能确保不会发生噩梦。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 2011-05-27
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多