我正在嘗試將雙數轉換為數字陣列
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);
}
}
但是我遇到了兩個問題:
在
DigitToNumTranslatorRaw,我現在不知道它是否比字串解決方案更好。的m_numResult = digit * Math.Pow(10, -m_dotIdx);,num /= 10;...可能會導致浮點精度的問題,是戰俘性能的最佳方式?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:
Math method: write
DigitToNumTranslatorRaw&NumToDigitTranslatorRawand make sure it's bug free & floating point accurate & better performance than string method (because I don't deal with CultureInfo.InvariantCulture, 1E 17,...).If the math method is too hard, I'll just use the string method
DigitToNumTranslator&NumToDigitTranslatorand 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.
uj5u.com熱心網友回復:
代碼使用示例:
數字到數字:
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能夠確保噩夢不會發生。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/312890.html
標籤:c# math numbers double floating-accuracy
