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

Posted 杨友山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了莫名奇妙的异常008:C#中多位小数问题相关的知识,希望对你有一定的参考价值。

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

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("f0", 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("f0", 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("f0", 16));

输出,Console.WriteLine(rd),

这样能勉强保留小数的长度(大于15位)

其实对于超过15位的小数在C#中最好使用decimal,但是在数据传入时最好时string类型,使用比如double类型就会造成精度丢失。

演示代码下载:https://download.csdn.net/download/yysyangyangyangshan/13985465
           

以上是关于莫名奇妙的异常008:C#中多位小数问题的主要内容,如果未能解决你的问题,请参考以下文章

JS操作小数运算,结果莫名其妙出现多位小数问题

莫名奇妙的异常009:mysql row size too large>8126

莫名奇妙的异常009:mysql row size too large>8126

莫名奇妙的异常010:Unexpected server response while retrieving PDF

莫名奇妙的异常010:Unexpected server response while retrieving PDF

linux下tomcat进程莫名奇妙的停止,并且查看catalina.out日志无任何异常信息,怎么回事?