为什么即使VS说它是多余的,这个演员会改变结果?

时间:2017-07-22 13:39:02

标签: c# visual-studio-2015 casting floating-point

------请跳到最后一次更新-----------

我发现了一个错误(在我的代码中),我正在努力找到对它的正确理解。

这一切都归结为调试时从立即窗口获取的这个特定示例:

x
0.00023569075
dx
-0.000235702712
x+dx+1f < 1f
true

(float) (x+dx+1f) < 1f
false

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.
}

但我现在正在尝试演员

x+=dx;
if( (float)( 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.
}

视觉工作室说,演员是多余的,但如果没有它,它会得到一个错误的假设,因为立即窗口也告诉我什么时候:

x+dx+1f < 1f
true

我目前正在运行我的代码,看看我是否再次使用演员来获取错误,并且一旦我确信无论如何我都会更新。

同时我希望有人可以理清这里发生的事情?我能指望演员做点什么吗?

更新 - 变量 我的变量x和dx是Vector2(Xna / monogame)的组件。 所以在代码中你应该阅读

Vector2 coord; // the x (and y) components are floats.
Vector2 ds;
coord.X // where it says x 
ds.X    // where it says dx

我认为这无所谓,但也许确实如此。

更新2 - 删除上面的示例

看到演员确实改变了结果我做了这个简单的演示

class Program
{
    static void Main(string[] args)
    {
        float a = -2.98023224E-08f; // Just a small negative number i picked...

        Console.WriteLine(((a + 1f) < 1f) ? "true" : "false");          //true

        Console.WriteLine(((float)(a + 1f) < 1f) ? "true":"false");     //false

        // Visual Studio Community 2015 marks the above cast as redundant
        // but its clearly something fishy going on!

        Console.Read();
    }
}

那么,即使VS说它是多余的,为什么这个演员会改变结果呢?

3 个答案:

答案 0 :(得分:3)

我没有看到你如何声明你的变量,但是分配发布到变量的静态值会使这些变量成为double类型,而不是float。如您所知,double类型的精度高于float

这是一个测试:

var x = 0.00023569075;
var dx = -0.000235702712;
Console.WriteLine(x.GetType()); //result: System.Double
Console.WriteLine(dx.GetType()); //result: System.Double

当然,在添加两个double和一个float时,结果为double,这就是第一个条件返回true的原因:

Console.WriteLine(x+dx+1f < 1f); //returns true
Console.WriteLine(x+dx+1f); //returns 0.999999988038

但是当你把它转换为float时,会发生截断并且结果不再正确,这就是你的第二个条件返回false的原因:

Console.WriteLine((float)(x+dx+1f) < 1f); //returns false
Console.WriteLine((float)(x+dx+1f)); //returns 1

更新:当您的变量为float时,此处会发生截断。请记住,float的最大精度仅为7位数,并且您指定的数字位数更多,因此会发生截断并导致您目击的结果不准确。

在您的原始问题中,以下是值被截断的方式:

float x = 0.00023569075f;
float dx = -0.000235702712f;
Console.WriteLine(x); //0.0002356907 last digit lost
Console.WriteLine(dx); //-0.0002357027 last two digits lost
Console.WriteLine((x + dx)); //-1.196167E-08
Console.WriteLine((x + dx + 1f)); //1

最后一个结果为1的原因应该是显而易见的。添加xdx的结果是-1.196167E-08-0.00000001196167),其中有7位数字,可以放入float。现在添加1会使其0.99999998803833有14个数字且无法放入float,因此当存储在1时,它会被截断并舍入为float。< / p>

您的更新2中会发生同样的事情。值-2.98023224E-08f有9位数字,因此会被截断为-2.980232E-08-0.00000002980232)。同样,向1添加0.99999997019768会使其1被截断并舍入为float a = -2.98023224E-08f; Console.WriteLine(a); //-2.980232E-08 last two digits lost Console.WriteLine(a + 1f); //1

a + 1f

更新2 Chris评论了以更高精度完成的计算,这绝对是正确的,但这并不能解释不应受此影响的结果。是float计算是以更高的精度完成的,但由于两个操作数都是float,因此计算结果会自动降低到float。手动将结果转换为float应该是多余的,不应更改结果。更重要的是,它不会强制计算以Console.WriteLine(a + 1f); //1 Console.WriteLine(a + 1f < 1f); //True Console.WriteLine((float)(a + 1f) < 1f); //False 精度完成。是的,我们仍然得到这些结果:

a + 1f < 1f

感谢与Chris的良好辩论以及各种机器上的大量测试,我认为我对正在发生的事情有了更好的了解。

当我们读到:

  

浮点运算的执行精度可能高于运算的结果类型

这里的操作这个词不仅是计算(在我们的例子中加法),还包括比较(在我们的例子中少于)。因此,在上面的第二行中,整个-2.98023224E-08f以更高的精度完成:将值-0.00000002980232241)添加到0.9999999701976776会导致1f然后与true进行比较,显然会返回Console.WriteLine(a + 1f < 1f); //True

float

任何时候都没有bool投射,因为比较的结果是a+1f

然而,在第一行中,我们只是打印计算结果float,并且因为两个操作数均为float,结果会自动转换为1,并且导致它被截断并舍入为Console.WriteLine(a + 1f); //1

1

现在最大的问题是关于第三条线。这次的不同之处在于,强制转换是强制将计算结果下移到浮点数,将其截断并舍入为1f,然后将其与Console.WriteLine((float)(a + 1f) < 1f); //False 进行比较。比较仍然以更高的精度进行,但现在无关紧要,因为铸件已经改变了计算结果:

ethtool -K eth6 tx off rx off

因此,这里的铸造导致两个操作(添加和比较)分开进行。没有铸造,步骤是:添加,比较,打印。通过铸造,步骤是:添加,铸造,比较,打印。两种操作仍然以更高的精度完成,因为铸造不会影响它。

也许Visual Studio说铸造是多余的,因为它没有考虑操作是否会以更高的精度完成。

答案 1 :(得分:2)

我认为c#规范的重要部分是:

  

“可以以比操作的结果类型更高的精度执行浮点运算。例如,某些硬件架构支持”扩展“或”长双“浮点类型,其范围和精度比双倍更大使用这种更高精度类型隐式执行所有浮点运算。只有在性能成本过高的情况下,才能使这种硬件架构以较低的精度执行浮点运算,而不是要求实现放弃性能和精度,C#允许更高精度的类型用于所有浮点运算。“ - https://msdn.microsoft.com/en-us/library/aa691146(v=vs.71).aspx

我们可以通过查看这三行代码来推断这几乎肯定是发生了什么,以稍微不同的方式进行比较:

displayTournaments(){

        const { tournamentList } = this.props;

        tournamentList.map((x,i) => {
                    return <li key={i}>{x.name}</li>
                  })


    }

正如您所看到的,第一次计算的结果(这是我们想知道的)与将中间值强制转换成双重告诉我们编译器正在利用执行计算精度更高。

当然结果不同的原因是因为虽然我们可以看到结果在计算float a = -2.98023224E-08f; Console.WriteLine((a + 1f) < 1f); // True Console.WriteLine((float)(a + 1f) < 1f); //False Console.WriteLine((double)(a + 1f) < 1f); //True 时应该为真,但结果为1,因此比较为假。

只是将上面的a+1f四舍五入存储在指数为-25且分数为0的浮点数中。如果你加1,则-25指数部分太小而不能表示它需要舍入,在这种情况下,舍入将数字保留为1.这是因为存储单精度浮点数的方式,它们只有23位用于前导1之后的部分,因此没有精度来存储分数,并在存储时最终将其四舍五入为1。因此,当我们强制它一直使用浮点计算时,为什么比较返回false。

答案 2 :(得分:1)

因为浮点数存储在BINARY中,所以IEEE浮点标准将数字表示为二进制尾数和二进制指数。 (2的权力)。在此表示中无法准确表示许多十进制数。所以编译器使用可用的最近的可用二进制IEEE浮点数。

因此,由于它不完全正确,无论实际差异有多小,比较都会失败。 计算差异,你会看到它有多小。

use CGI qw(:standard); use File::Basename; my ( $name, $path, $extension) = fileparse ( $productimage, '..*' ); $productimage = $name . $extension; $productimage =~ tr/ /_/; $productimage =~ s/[^$safechars]//g; if ( $productimage =~/^([$safechars]+)$/ ) { $productimage = $1; } else { die "Filename contains invalid characters"; } $fh = upload('image'); $uploaddir = "../../.hidden/images"; open ( UPLOADFILE, ">$uploaddir/$productimage" ) or die "$!"; binmode UPLOADFILE; while (<$fh>) { print UPLOADFILE; } close UPLOADFILE;

如果使用小数,则可能有效。