莫名奇妙的异常008:C#中多位小数问题

  • Post author:
  • Post category:其他


分享一个博主碰到的问题。

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



版权声明:本文为yysyangyangyangshan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。