提问人:123iamking 提问时间:10/2/2021 最后编辑:123iamking 更新时间:10/4/2021 访问量:281
将双数转换为数字,反之亦然?
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,... ),谁知道是否还有更多我不知道的情况 - 风险太大,我的应用程序不使用字符串来显示数字,它结合了精灵图像来绘制数字。
所以我想试试数学方法:
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);
}
}
但是我遇到了两个问题:
在,我现在不知道它是否比字符串解决方案更好。这。。。可能会导致浮点精度问题和 Pow 是性能的最佳方式吗?
DigitToNumTranslatorRaw
m_numResult += digit * Math.Pow(10, -m_dotIdx);
num /= 10;
在 中,我仍然无法获取点之后的数字。
NumToDigitTranslatorRaw
我试图提取 Mircosoft 的代码 TryParse 看看他们是如何做到的,但它太复杂了,我找不到他们把代码放在哪里。
所以我的目的是:
数学方法:编写并确保它没有错误&浮点准确&比字符串方法更好的性能(因为我不处理CultureInfo.InvariantCulture,1E + 17,...)。
DigitToNumTranslatorRaw
NumToDigitTranslatorRaw
如果数学方法太难,我会只使用字符串方法和处理每个字符串问题(例如,太长的数字变成 1E+17),但问题是我不知道我是否涵盖了所有的字符串问题(例如,我通过随机测试发现的 1E+17,我通过搜索堆栈溢出发现的 CultureInfo 问题), 文档没有列出我可能遇到的所有问题。
DigitToNumTranslator
NumToDigitTranslator
答:
代码使用示例:
数字到数字:
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
评论
var input = new [] {"whatever", "here"};
var output = new int[] { 42 };
-69.69777
(-69.6977).ToString("N20")