问题摘要:
对于某些十进制值,当我们将类型从十进制转换为double时,会将一小部分添加到结果中.
更糟糕的是,可能存在两个"相等"的十进制值,这些值在转换时会产生不同的双精度值.
代码示例:
decimal dcm = 8224055000.0000000000m; // dcm = 8224055000
double dbl = Convert.ToDouble(dcm); // dbl = 8224055000.000001
decimal dcm2 = Convert.ToDecimal(dbl); // dcm2 = 8224055000
double dbl2 = Convert.ToDouble(dcm2); // dbl2 = 8224055000.0
decimal deltaDcm = dcm2 - dcm; // deltaDcm = 0
double deltaDbl = dbl2 - dbl; // deltaDbl = -0.00000095367431640625
Run Code Online (Sandbox Code Playgroud)
查看评论中的结果.结果从调试器的手表中复制.产生这种效果的数字的十进制数字远远少于数据类型的限制,所以它不能是溢出(我猜!).
更有趣的是,可以有两个相等的十进制值(在上面的代码示例中,参见"dcm"和"dcm2","deltaDcm"等于零),在转换时产生不同的双精度值.(在代码中,"dbl"和"dbl2",它们具有非零"deltaDbl")
我想它应该是与两种数据类型中数字的按位表示的差异相关的东西,但是无法弄清楚是什么!而且我需要知道如何按照我需要的方式进行转换.(比如dcm2 - > dbl2)
在数学上,考虑这个问题的有理数
8725724278030350 / 2**48
Run Code Online (Sandbox Code Playgroud)
其中**在分母表示取幂,即,分母是2对48次方.(分数不在最低方面,通过2可还原的)此数目是恰好作为表示的System.Double.它的十进制扩展是
31.0000000000000'49'73799150320701301097869873046875 (exact)
Run Code Online (Sandbox Code Playgroud)
撇号不代表缺失的数字,而只是标记圆形到15的分数.将执行17位数字.
请注意以下内容:如果此数字四舍五入为15位,则结果为31(后跟十三0秒),因为下一个数字(49...)以a开头4(意味着向下舍入).但如果数字首先四舍五入为17位,然后四舍五入为15位,结果可能是31.0000000000001.这是因为第一次舍入通过将49...数字增加到50 (terminates)(下一个数字73...)而向上舍入,然后第二次舍入可能再次向上舍入(当中点舍入规则表示"从零开始舍入"时).
(当然,还有更多具有上述特征的数字.)
现在,事实证明.NET的这个数字的标准字符串表示是"31.0000000000001".问题:这不是一个错误吗?通过标准字符串表示,我们指的String是由参数Double.ToString()实例方法产生的,当然,该方法与生成的方法相同ToString("G").
需要注意的一个有趣的事情是,如果你投的上述号码System.Decimal,然后你得到一个decimal是31准确!请参阅此Stack Overflow问题,以讨论将a转换Double为Decimal包含第一个舍入到15位的令人惊讶的事实.这意味着转换为Decimal正确的舍入到15位数,而调用ToSting()是不正确的.
总而言之,我们有一个浮点数,当输出给用户时,是31.0000000000001,但当转换为Decimal …
刚开始学习C#.我计划将它用于繁重的数学模拟,包括数值求解.问题是我在添加和减去double时以及比较时都会出现精度损失.代码及其返回的内容(在评论中)如下:
namespace ex3
{
class Program
{
static void Main(string[] args)
{
double x = 1e-20, foo = 4.0;
Console.WriteLine((x + foo)); // prints 4
Console.WriteLine((x - foo)); // prints -4
Console.WriteLine((x + foo)==foo); // prints True BUT THIS IS FALSE!!!
}
}
}
Run Code Online (Sandbox Code Playgroud)
非常感谢任何帮助和澄清!
令我困惑的是(x + foo)==foo回报True.
我试图将double值9007199254740992.0转换为字符串.
但似乎存在舍入错误(最后2个变为0):
(9007199254740992.0).ToString("#") // Returns "9007199254740990"
(9007199254740992.0).ToString() // Returns"9.00719925474099E+15"
Run Code Online (Sandbox Code Playgroud)
首先,我认为这个数字可能无法表示为双数.但它可以.这可以通过将其转换为long然后将其转换为字符串来看出.
((long)9007199254740991.0).ToString() // Returns "9007199254740991"
((long)9007199254740992.0).ToString() // Returns "9007199254740992"
Run Code Online (Sandbox Code Playgroud)
另外,我发现如果我使用"R"格式,它可以工作.
(9007199254740992.0).ToString("R") // Returns "9007199254740992"
Run Code Online (Sandbox Code Playgroud)
任何人都可以解释为什么ToString("#")不以完整精度返回整数部分的双精度?
例如,十进制数0.1不能以任何有限精度的二进制浮点表示
但是,在C#中
string s = 0.1.ToString(CultureInfo.InvariantCulture);
Console.WriteLine(s);
Run Code Online (Sandbox Code Playgroud)
写道0.1.
我本以期待某事0.099999999999999999.
我正在寻找至少一个双精简版的例子,它不能完全代表双精度.
编辑:
正如其他人所指出的,0.1确实是我一直在寻找的文字,正如下面的经典代码示例所示:
double sum = 0.0;
for (int i = 0; i < 10; i++)
{
sum += 0.1;
}
Console.WriteLine(sum.Equals(1.0)); // false
Run Code Online (Sandbox Code Playgroud)
当双精度转换为其他数据类型时,会发生奇怪的事情.这不仅仅是string因为这个表达式是真的:0.1m.Equals((decimal)0.1)
我一直在努力编写一个 double,例如82.0使用 Utf8JsonWriter。
默认情况下,methodWriteNumberValue方法接受一个 double 并为我格式化它,并且格式(这是标准的“G”格式)省略了“.0”后缀。我找不到控制它的方法。
按照设计,我似乎不能只将原始字符串写入 Utf8JsonWriter,但我找到了一种解决方法:创建一个 JsonElement 并调用 JsonElement.WriteTo`。这会调用Utf8JsonWriter 中的私有方法并将字符串直接写入其中。
有了这个发现,我做出了一个非常笨拙且效率低下的实现。
open System.Text.Json
void writeFloat(Utf8JsonWriter w, double d) {
String floatStr = f.ToString("0.0################")
JsonElement jse = JsonDocument.Parse(floatStr).RootElement
jse.WriteTo(w)
}
Run Code Online (Sandbox Code Playgroud)
无论如何我都需要格式化一个double,这样很好,但是解析它,创建一个jsonDocument和一个JsonElement,只是为了能够找到一种调用受保护方法的方法,似乎真的很浪费。但是,它确实有效(我用 F# 编写并转换为 C#,如果我在语法中犯了错误,请道歉)。
有没有更好的办法?想到的一些潜在解决方案(我是 dotnet 的新手,所以我不确定这里有什么可能):
至于为什么这是必要的:我需要强制写入整数值,.0因为我需要与之交互的非常具体的格式,它区分整数和浮点 JSON 值。(我对指数格式没意见,因为这显然是一个浮点数)。