我知道浮点数具有精度,精度后的数字不可靠.
但是如果用于计算数字的等式是相同的呢?我可以假设结果也一样吗?
例如,我们有两个浮点数x和y.我们可以假设x/y机器1的结果与机器2的结果完全相同吗?IE ==比较将返回true
我有这么小的代码
double s = -2.6114289999999998;
double s7 = Math.Round(s, 7);
double s5 = Math.Round(s, 5);
double s6 = Math.Round(s, 6);
Run Code Online (Sandbox Code Playgroud)
有了Platform = Any CPU,我明白了
s7: -2.611429
s5: -2.61143
s6: -2.611429
Run Code Online (Sandbox Code Playgroud)
使用Platform = x64,我得到了
s7: -2.6114289999999998
s5: -2.61143
s6: -2.6114289999999998
Run Code Online (Sandbox Code Playgroud)
为什么?(从VS的Locals窗口复制的输出)
整段代码是:
private void btnAlign_Click(object sender, EventArgs e)
{
double s = -2.6114289999999998;
double s7 = Math.Round(s, 7);
double s5 = Math.Round(s, 5);
double s6 = Math.Round(s, 6);
}
Run Code Online (Sandbox Code Playgroud) 我正在为一位同事写一个有启发性的例子,告诉他为什么测试花车的平等往往是个坏主意.我去的例子是添加0.1十次,对1.0(我在我的介绍数值类所示)进行比较.我惊讶地发现两个结果是相同的(代码+输出).
float @float = 0.0f;
for(int @int = 0; @int < 10; @int += 1)
{
@float += 0.1f;
}
Console.WriteLine(@float == 1.0f);
Run Code Online (Sandbox Code Playgroud)
一些调查表明,这个结果不能依赖(很像浮动平等).我发现最令人惊讶的是在其他代码之后添加代码可能会改变计算结果(代码+输出).请注意,此示例具有完全相同的代码和IL,并附加了一行C#.
float @float = 0.0f;
for(int @int = 0; @int < 10; @int += 1)
{
@float += 0.1f;
}
Console.WriteLine(@float == 1.0f);
Console.WriteLine(@float.ToString("G9"));
Run Code Online (Sandbox Code Playgroud)
我知道我不应该在花车上使用平等,因此不应该太在意这一点,但我发现它非常令人惊讶,就像我向大家证明的那样.做的东西后,您进行了计算改变了先前计算的价值?我不认为这是人们通常在脑海中计算的模型.
我不是完全难住了,似乎是安全的假设,有某种优化的改变计算的结果,"平等"的情况下发生的(建筑在调试模式防止"平等"的情况下).显然,当CLR发现稍后需要将浮动框打包时,优化被放弃.
我搜索了一下但找不到这种行为的原因.任何人都能提醒我吗?
谁能告诉我为什么这两个模数计算产生两种不同的结果?我只需责怪别人或其他东西而不是我,因为我找不到这个错误.
public void test1()
{
int stepAmount = 100;
float t = 0.02f;
float remainder = t % (1f / stepAmount);
Debug.Log("Remainder: " + remainder);
// Remainder: 0.01
float fractions = 1f / stepAmount;
remainder = t % fractions;
Debug.Log("Remainder: " + remainder);
// Remainder: 0
}
Run Code Online (Sandbox Code Playgroud)
使用VS-2017 V15.3.5
请考虑以下代码:
double v1 = double.MaxValue;
double r = Math.Sqrt(v1 * v1);
Run Code Online (Sandbox Code Playgroud)
r = double.MaxValue在32位机器上r = 64位机器上的无穷大
我们在32位机器上开发,因此在客户通知之前不会发现问题.为什么会发生这种不一致?如何防止这种情况发生?
我使用.NET 2.0与PlatformTarget x64和x86.我给Math.Exp输入相同的输入数,并且它在任一平台上都返回不同的结果.
MSDN说你不能依赖文字/解析的Double来代表平台之间的相同数字,但我认为我在下面使用Int64BitsToDouble可以避免这个问题,并保证在两个平台上输入相同的Math.Exp.
我的问题是为什么结果不同?我原以为:
我知道我不应该比较15/17位数后的浮点数,但我对这里的不一致感到困惑,看起来在同一硬件上看起来是相同的操作.
任何人都知道引擎盖下发生了什么?
double d = BitConverter.Int64BitsToDouble(-4648784593573222648L); // same as Double.Parse("-0.0068846153846153849") but with no concern about losing digits in conversion
Debug.Assert(d.ToString("G17") == "-0.0068846153846153849"
&& BitConverter.DoubleToInt64Bits(d) == -4648784593573222648L); // true on both 32 & 64 bit
double exp = Math.Exp(d);
Console.WriteLine("{0:G17} = {1}", exp, BitConverter.DoubleToInt64Bits(exp));
// 64-bit: 0.99313902928727449 = 4607120620669726947
// 32-bit: 0.9931390292872746 = 4607120620669726948
Run Code Online (Sandbox Code Playgroud)
在打开或关闭JIT的两个平台上,结果都是一致的.
[编辑]
我对下面的答案并不完全满意,所以这里有一些我搜索的细节.
http://www.manicai.net/comp/debugging/fpudiff/说:
因此32位使用80位FPU寄存器,64位使用128位SSE寄存器.
CLI标准表示,如果硬件支持双精度,则可以用更高的精度表示双精度:
[原理:此设计允许CLI为浮点数选择特定于平台的高性能表示,直到它们被放置在存储位置.例如,它可能能够在硬件寄存器中保留浮点变量,这些变量提供的精度比用户请求的更高.在分区I 69同时,CIL生成器可以通过使用转换指令强制操作遵守特定于语言的表示规则.最终理由]
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf(12.1.3处理浮点数据类型)
我认为这就是这里发生的事情,因为Double的标准15位精度后结果不同.64位Math.Exp结果更精确(它有一个额外的数字)因为内部64位.NET使用的FPU寄存器比32位.NET使用的FPU寄存器更精确.
是否有任何 C# 规范说明了整数类型项(例如,int)到项的隐式转换double应该如何工作?如果是这样,有人可以告诉我算法或指导我吗?
C# 6.0 草案规范在词法结构 -> 语法 -> 词法语法 -> 词法分析 -> 令牌 -> 文字下声明“float 或 double 类型的实数的值是通过使用 IEEE '舍入到最近'模式确定的” -> 真正的文字;但是我无法找到有关隐式转换如何工作的任何信息。
我在同一规范中的转换 -> 隐式转换 -> 隐式数字转换下找到的唯一内容是“从int, uint, long, 或ulongtofloat和 fromlong或ulongto 的转换double可能会导致精度损失,但永远不会导致幅度损失。”
我确实知道隐式转换不遵循与真实文字相同的算法,如下面的程序所示*:
using System;
using System.Diagnostics;
namespace App
{
internal static class Program
{
internal static void Main()
{
Debug.Assert(GetFirstByte(10648738977740919977) != GetFirstByte(10648738977740919977d));
}
private static …Run Code Online (Sandbox Code Playgroud) 在Release模式和debug模式下运行时,我们有一些单元测试失败.如果我在发布模式下附加调试器,则测试通过.有太多的代码要在这里发布,所以我真的只是在寻找调试发布模式问题的最佳实践.我检查过:
解决方案:在这种情况下,这是因为我在比较浮点变量是否相等.如果没有重大的重构,我无法将浮点数更改为十进制,所以我添加了一个扩展方法:
public static class FloatExtension
{
public static bool AlmostEquals(this float f1, float f2, float precision)
{
return (Math.Abs(f1 - f2) <= precision);
}
public static bool AlmostEquals(this float f1, float f2)
{
return AlmostEquals(f1, f2, .00001f);
}
public static bool AlmostEquals(this float? f1, float? f2)
{
if (f1.HasValue && f2.HasValue)
{
return AlmostEquals(f1.Value, f2.Value);
}
else if (f1 == null && f2 == null)
{
return true;
}
return false;
}
}
Run Code Online (Sandbox Code Playgroud) 我注意到C#编译器浮动舍入/截断有趣的行为.也就是说,当浮点字面值超出保证的可表示范围(7个十进制数字)时,则a)显式地将float结果转换为float(语义上不必要的操作)和b)将中间计算结果存储在局部变量中都会改变输出.一个例子:
using System;
class Program
{
static void Main()
{
float f = 2.0499999f;
var a = f * 100f;
var b = (int) (f * 100f);
var c = (int) (float) (f * 100f);
var d = (int) a;
var e = (int) (float) a;
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(d);
Console.WriteLine(e);
}
}
Run Code Online (Sandbox Code Playgroud)
输出是:
205
204
205
205
205
Run Code Online (Sandbox Code Playgroud)
在我的计算机上的JITted调试版本中,b的计算方法如下:
var b = (int) (f * 100f);
0000005a fld dword ptr [ebp-3Ch]
0000005d fmul dword ptr ds:[035E1648h]
00000063 fstp qword …Run Code Online (Sandbox Code Playgroud) ------请跳到最后更新-----------
我发现了一个错误(在我的代码中),我正在努力找到正确的理解.
这一切都归结为在调试时从立即窗口获取的这个特定示例:
x
0.00023569075
dx
-0.000235702712
x+dx+1f < 1f
true
(float) (x+dx+1f) < 1f
false
Run Code Online (Sandbox Code Playgroud)
x和dx都是float类型.那么为什么当我进行演员表时布尔值会有所不同?
在我的实际代码中:
x+=dx
if( x+1f < 1f) // Add a one to truncate really small negative values (originally testing x < 0)
{
// do actions accordingly
// Later doing
x+=1; // Where x<1 has to be true, therefore we have to get rid of really small negatives where x+=1 will give x==1 as true and x<1 false.
}
Run Code Online (Sandbox Code Playgroud)
但我现在正在尝试与演员
x+=dx;
if( (float)( x+1f) …Run Code Online (Sandbox Code Playgroud) 我们有一些代码会在某些机器上产生意外结果.我把它缩小到一个简单的例子.在下面的linqpad片段中,方法GetVal和GetVal2实现基本相同,尽管前者还包括对NaN的检查.但是,每个返回的结果都不同(至少在我的机器上).
void Main()
{
var x = Double.MinValue;
var y = Double.MaxValue;
var diff = y/10 - x/10;
Console.WriteLine(GetVal(x,6,diff));
Console.WriteLine(GetVal2(x,6,diff));
}
public static double GetVal(double start, int numSteps, double step)
{
var res = start + numSteps * step;
if (res == Double.NaN)
throw new InvalidOperationException();
return res;
}
public static double GetVal2(double start, int numSteps, double step)
{
return start + numSteps * step;
}
Run Code Online (Sandbox Code Playgroud)
结果
3.59538626972463E+307
Infinity
Run Code Online (Sandbox Code Playgroud)
为什么会发生这种情况,是否有一种避免它的简单方法?与寄存器有关?
有一件小事让我在c#中拼图:)
这是我的变量和结果:
decimal a1 = 0.2M;
decimal a2 = 1.0M;
a1 - a2 = -0.8
float b1 = 0.2F;
float b2 = 1.0F;
b1 - b2 = -0.8
double c1 = 0.2;
double c2 = 1.0;
c1 - c2 = -0.8
double x1 = 0.2F;
double x2 = 1.0F;
x1 - x2 = -0.799999997019768
Run Code Online (Sandbox Code Playgroud)
十进制 - 结果与我预期的一样,因为它知道它们以基数10表示法工作.
浮动 - 让我感到惊讶,知道它在基础2表示法上工作,它实际上显示结果好像它作为基础10表示法而没有失去精确度.
Double c - 与Float相同.
Double x - 显示我希望Float发生的结果.
问题是Float,Double'c'和'x'群体正在发生什么?为什么Double'x'组失去了它的精度,而Float组实际上以10的基数表示,这样可以说出"预期"的计算结果?想知道为什么将Double …