分享一个博主碰到的问题。
1、double类型保留小数问题
当时有一段代码来处理小数位数,例如这样,
object objValue = "9.1234567890123456";
Double.TryParse(objValue.ToString(), out var dValue);
string result = Math.Round(dValue, 15).ToString();
对于输入的objValue,做一个小数点位数的保留。
比如这里的输出是这样,Console.WriteLine(result);
0-15位小数都没问题。
但是问题来了,当我想保留大于15位小数时,Math.Round方法不行了,
上面代码,如果
string result = Math.Round(dValue, 16).ToString();
会报这个错:
System.ArgumentOutOfRangeException:“舍入位数必须在 0 和 15 之间(包括 0 和 15)。
2、decimal处理大于15位的小数
double类型只能保留15位小数,于是准备用decimal解决。
string stringValue = "9.12345678901234567890";
string result = decimal.Round(decimal.Parse(stringValue), 18).ToString();
输出Console.WriteLine(result),
如果大家碰到了double解决不了的小数问题,可以使用decimal来解决。
3、小数转string精度丢失问题
事情到此结束了吗?
对于博主来说才刚刚开始,因为,对于数据的处理只是其中一个功能的一小段代码,前面提过我的源数据是object类型的,并不是string类型。
于是转换一下就好了,
object objValue = 9.1234567890123456789;
string stringValue = objValue.ToString();
输出后Console.WriteLine(“object类型9.1234567890123456小数转string后:” + stringValue),
我尝试了Convert,对objValue加字符串操作,StringBuilder产生字符串,都不行,精度就是会丢失。究其原因,因为object objValue = 9.1234567890123456789赋值时编译器就把这个值当作double处理了,对于double超过15位的小数值double是不认识的。
4、取整数处理多位小数转换时精度丢失问题
于是,对于object类型的超过15位小数如何正常转换过来呢,
我准备将小数转为整数,处理代码如下,
//源数据
int dnumber = 16;
object abs = 2.1234567890123456;
//处理
long dec = (long)(Math.Pow(10, dnumber) * (double)abs);
string dbstr = dec.ToString();
char[] cstr = dbstr.ToCharArray();
string formatValue = string.Empty;
string formatInt = string.Empty;
string formatDouble = string.Empty;
int length = cstr.Length;
string intNumber = Convert.ToInt32(abs).ToString();
int intLength = intNumber.Length;
for (int i = 0; i < length; i++)
{
if (i < intLength)
{
formatInt += cstr[i];
}
else
{
formatDouble += cstr[i];
}
}
if (!string.IsNullOrEmpty(formatInt) && !string.IsNullOrEmpty(formatDouble))
{
formatValue = formatInt + "." + formatDouble;
}
decimal d = decimal.Round(decimal.Parse(formatValue), dnumber);
string strDec = d.ToString(string.Format("f{0}", dnumber));
输出,Console.WriteLine(strDec),
这样,将object小数无损转为decimal类型。
但是这里有一个弊端,long类型毕竟长度有限,如果源小数太大,就会因为long的限制,程序报错。
这里我也尝试了使用如下这个方法处理小数和整数部分,但是,取整数时不能无损取出
private static string FormatDoubleNumber(object value, int number)
{
string result = value.ToString();
try
{
//取小数的整数部分
int intValue = Convert.ToInt32(value);
double decValue = (double)value % 1;
long longValue = (long)(Math.Pow(10, number) * (double)decValue);
string dbstr = longValue.ToString();
char[] cstr = dbstr.ToCharArray();
string formatValue = string.Empty;
string formatInt = intValue.ToString();
string formatDouble = string.Empty;
int length = cstr.Length;
for (int i = 0; i < length; i++)
{
formatDouble += cstr[i];
}
if (!string.IsNullOrEmpty(formatInt) && !string.IsNullOrEmpty(formatDouble))
{
formatValue = formatInt + "." + formatDouble;
}
decimal decimalValue = decimal.Round(decimal.Parse(formatValue), number);
result = decValue.ToString(string.Format("f{0}", number));
}
catch { }
return result;
}
这个方法中,(double)value % 1就已经丢失精度了。
5、最终并没有实际解决的解决办法
受限于传入的类型是object,所以无法无损精度的转换,只能牺牲一些精度,但是可以保持小数长度。
使用System.ComponentModel下的DoubleConverter来处理object类型的小数,
object dd = 2.1234567890123456;
DoubleConverter dcb = new DoubleConverter();
string strdc = dcb.ConvertToInvariantString(dd);
decimal decimalValue = decimal.Round(decimal.Parse(strdc), 16);
string rd = decimalValue.ToString(string.Format("f{0}", 16));
输出,Console.WriteLine(rd),
这样能勉强保留小数的长度(大于15位)
其实对于超过15位的小数在C#中最好使用decimal,但是在数据传入时最好时string类型,使用比如double类型就会造成精度丢失。
演示代码下载:
https://download.csdn.net/download/yysyangyangyangshan/13985465