将对象转换为十进制数的C#方法

16

我试图将值为0.39999999999999997的对象转换为十进制变量,同时不失去精度。

object d = 0.39999999999999997;

我已经尝试了以下方法。

decimal val1 = Convert.ToDecimal(d); // val1 = 0.4
object val2 = Convert.ChangeType(d, Type.GetType("System.Decimal")); // val2 = 0.4
decimal val3 = decimal.Parse(d.ToString()); // val3 = 0.4
decimal val4 = (Decimal) d; // val4 = 0.4

我知道这不是 decimal 数据类型无法存储此值的问题,如下所示。

decimal val5 = 0.39999999999999997m; // val5 = 0.39999999999999997;

如何将此对象转换为十进制而不失精度?

我正在使用 .NET Framework 3.5,如果有关系的话。


我不确定您能否保证装箱/拆箱和不同浮点类型转换之间的持久精度。 - Tigran
8个回答

13

我认为这是你所寻找的代码:

object d = 0.39999999999999997;
//Unbox value
double doubleVal = (double)d;

//Convert to string. R format specifier gives a string that can round-trip to an identical number.  
//Without R ToString() result would be doubleAsString = "0.4"
string doubleAsString = doubleVal.ToString("R"); 

//Now that you have doubleAsString = "0.39999999999999997" parse it!
decimal decimalVal = decimal.Parse(doubleAsString);

太好了。虽然我担心使用双精度会影响数据的准确性,但这似乎是目前最好的选择。谢谢。 - Chathura W
在这种情况下,我同意V4Vendettas的评论:“恐怕这是不可能的,因为对象保存了对值的引用,而在您的情况中,除非引用是小数,否则您实际上无法获得那种精度。” - Renatas M.

6
为了使其正常工作,您需要类似地指定它。
object d = 0.39999999999999997M;

如果不进行强制,对象无法保持精度。(如果这不是实际代码,您需要展示它如何被分配)

只有这样才能使类似以下代码工作:decimal dec = Convert.ToDecimal(d);


由于这个变量的初始化方式(实际值存储在数据库中),我无法知道它何时会成为一个十进制值。 - Chathura W
如果你无论如何都要使用十进制,为什么不获取数据库的值并将其转换为十进制呢?这有什么问题吗? - V4Vendetta
让我澄清一下。这是一个接受对象的通用方法。而且我不能改变方法签名。 - Chathura W
3
恐怕不可能,因为对象持有对值的引用,在你的情况下,除非引用是十进制数,否则你无法获得那个精度,它永远无法将其解包为十进制数,因为它从未被封装为十进制数。 - V4Vendetta

1

由于您正在从数据库中读取数据(正如您在评论中所指出的那样,我认为将其添加到问题中是个糟糕的主意),因此我认为允许在从数据库中读取时转换为double并再次转换回去是一个可怕的想法,因为您将失去精度[可能存储为定点或能够表示小数的数字系统]。我认为您必须花费一些努力直接读取存储的值作为十进制数(编辑您的模式或类似操作),或者如果不可能,则将它们读取为字符串,并使用Decimal.Parse()获取实际值。

实际上,您的数字0.39999999999999997有17个小数位,因此不能安全地存储为double。

P.S. 这里有Jon Skeet写的关于.net Double和四舍五入的优秀文章


我知道将数据转换为double类型再转回来是个不好的想法。但很遗憾,由于应用程序架构的限制,这是我现在唯一的选择。未来,我打算采用读取字符串值然后将其转换为Decimal类型的方式。感谢您提供的文章,阅读非常有收获。 :) - Chathura W

0

Decimal d = new Decimal(d);

如果d是一个double类型的变量,那么根据MSDN文档,这个构造函数应该会保留精度。

这个构造函数会将数值四舍五入到最接近的15位有效数字。即使这个数值有超过15位有效数字并且不重要的数字为零,也会进行四舍五入。


无法工作。无法将对象传递到Decimal()构造函数中。 - Chathura W

0

对象 d = 0.399999999999999999999999997M; 会起作用。


实际值是从数据库中检索的,无法进行选项操作。 - Chathura W

0
string val = "0.39999999999999997");
decimal d = decimal.Parse(val,
    System.Globalization.NumberStyles.AllowDecimalPoint);//0.39999999999999997

object d = 0.39999999999999997; double decim = double.Parse(d.ToString(), System.Globalization.NumberStyles.AllowDecimalPoint); //decim = 0.4 - Chathura W
@Chathura:这是一个打字错误。我的意思是decimal.Parse。我已经测试过了,结果如预期。 - Jalal Said
嘿,在哪里出现了这个字符串? - V4Vendetta
糟糕,似乎我太心急了。'd.ToString()'将其转换回0.4。 - Chathura W
@Chathura:我现在测试了一下,它没有返回到0.4。这是我的测试代码:string val = "0.39999999999999997"; decimal d = decimal.Parse(val, System.Globalization.NumberStyles.AllowDecimalPoint);/*0.39999999999999997*/ string s = d.ToString();/*0.39999999999999997*/ - Jalal Said
是的,但问题在于初始变量不是字符串而是一个对象。当使用 object.ToString() 时,它会转换为 0.4 - Chathura W

0
在这个页面http://msdn.microsoft.com/en-us/library/364x0z75(v=vs.80).aspx上写着“没有后缀m,该数字将被视为double类型”
而这段代码
object o = 0.39999999999999997;
Console.WriteLine(o.GetType());

显示出 System.Double

而这个则

object o = 0.39999999999999997m;
Console.WriteLine(o.GetType());

显示出 System.Decimal
所以如果没有后缀 m,你只是失去了精度。


Decimal类型是必要的,因为Double类型存储值的方式,它不会存储我输入的精确值。例如,12.03将被存储为12.029999999999999等。 - Chathura W

0

说真的,你只需要做像这样的事情...

object d = 0.39999999999999997; 
decimal result;
decimal.TryParse(d.ToString(), out result);
return result;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接