简体   繁体   English

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

[英]Why does this cast change the result when even VS says it is redundant?

------ Please jump to the last update ----------- ------请跳到最后更新-----------

I have found a bug (in my code) and I am struggling to find correct understanding of it. 我发现了一个错误(在我的代码中),我正在努力找到正确的理解。

It all boils down to this specific example taken from the immediate window while debugging: 这一切都归结为在调试时从立即窗口获取的这个特定示例:

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

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

x and dx are both of type float. x和dx都是float类型。 So why is the boolean value different when I do a cast? 那么为什么当我进行演员表时布尔值会有所不同?

In the actual code i had: 在我的实际代码中:

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

but I am now trying with the cast 但我现在正在尝试与演员

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

Visual studio says that the cast is redundant but it DO get a false positve without it as de immediate window also told me when: 视觉工作室说,演员是多余的,但如果没有它,它会得到一个错误的假设,因为立即窗口也告诉我:

x+dx+1f < 1f
true

I am currently running my code to see if I get the bug again with the cast and I will update as soon as I get convinced either way. 我目前正在运行我的代码,看看我是否再次使用演员来获取错误,并且一旦我确信无论如何我都会更新。

In the meanwhile I hope someone can sort out whats going on here? 同时我希望有人可以理清这里发生的事情? Can I expect the Cast to do something? 我能指望演员做点什么吗?

Update - Variables My variables x and dx are components of a Vector2 (Xna/monogame). 更新 - 变量我的变量x和dx是Vector2(Xna / monogame)的组件。 So in the code you should read 所以在代码中你应该阅读

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

I thought this would not matter, but maybe it does. 我认为这没关系,但也许确实如此。

Update 2 - Drop the above example 更新2 - 删除上面的示例

Seeing that the cast did change the outcome I made this simple demonstration 看到演员确实改变了结果我做了这个简单的演示

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();
    }
}

So, why does this cast change the result when even VS says it is redundant? 那么,即使VS说它是多余的,为什么这个演员会改变结果呢?

I don't see how you're declaring your variables, but assigning the static values that posted to variables makes those variables of double type, not float . 我没有看到你如何声明你的变量,但是分配发布到变量的静态值会使这些变量成为double类型,而不是float And as you know, the double type has larger precision than float . 如您所知, double类型的精度比float

Here is a test: 这是一个测试:

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

And of course, when adding two double s and a float , the result is double , so that's why the first condition returns true : 当然,当添加两个double和一个float ,结果是double ,这就是第一个条件返回true的原因:

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

But when you cast it to float , a truncation occurs and the result is no longer correct, which is why your second condition returns false : 但是当你将它转换为float ,会发生截断并且结果不再正确,这就是你的第二个条件返回false

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

UPDATE: When your variables are float , truncation is at play here. 更新:当你的变量是float ,截断在这里起作用。 Remember that the max precision of float is only 7 digits and you're assigning numbers with much more digits, so truncation occurs and results in the inaccurate results that you're witnessing. 请记住, float的最大精度只有7位数,并且您为数字分配了更多的数字,因此会发生截断并导致您目睹的结果不准确。

In your original question, here is how the values are truncated: 在您的原始问题中,以下是值被截断的方式:

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

The reason why the last result is 1 should be obvious. 最后一个结果为1的原因应该是显而易见的。 The result of adding x and dx is -1.196167E-08 ( -0.00000001196167 ) which has 7 digits and can fit in float . 添加xdx的结果是-1.196167E-08-0.00000001196167 ),它有7位数字,可以放入float Now adding 1 makes it 0.99999998803833 which has 14 digits and cannot fit in float so it is truncated and rounded to 1 when stored in a float . 现在添加1使其为0.99999998803833 ,它有14位数,并且不能适合float因此当存储在float时它被截断并舍入为1

The same thing happens in your update 2. The value -2.98023224E-08f has 9 digits, so it is truncated to -2.980232E-08 ( -0.00000002980232 ). 同样的事情发生在您的更新2.值-2.98023224E-08f有9位数,因此它被截断为-2.980232E-08-0.00000002980232 )。 Again, adding 1 to that makes it 0.99999997019768 which is truncated and rounded to 1 : 再次,添加1使其成为0.99999997019768 ,它被截断并舍入为1

float a = -2.98023224E-08f;
Console.WriteLine(a); //-2.980232E-08 last two digits lost
Console.WriteLine(a + 1f); //1

UPDATE 2 Chris commented about the calculation being done at a higher precision which is absolutely correct, but that doesn't explain the results which should not be affected by that. 更新2克里斯评论计算是以更高的精度完成的,这是绝对正确的,但这并不能解释不应受此影响的结果。 Yes a + 1f calculation is done at a higher precision, but because both operands are float , the result of the calculation is then automatically casted down to float . a + 1f计算以更高的精度完成,但由于两个操作数都是float ,因此计算结果会自动降低到float Manually casting the result to float should be then redundant and shouldn't change the result. 手动将结果转换为float应该是多余的,不应更改结果。 More importantly, it does not force the calculation to be done at float precision. 更重要的是,它不会强制计算以float精度完成。 Yes, we still get these results: 是的,我们仍然得到这些结果:

Console.WriteLine(a + 1f); //1
Console.WriteLine(a + 1f < 1f); //True
Console.WriteLine((float)(a + 1f) < 1f); //False

Thanks to a good debate with Chris and lots of testing on various machines, I think I have a better understanding of what's going on. 感谢与Chris的良好辩论和各种机器上的大量测试,我想我对正在发生的事情有了更好的理解。

When we read: 当我们读到:

Floating-point operations may be performed with higher precision than the result type of the operation 可以以比操作的结果类型更高的精度执行浮点运算

The word operations here is not only the calculations (addition, in our example), but also the comparisons (less than, in our example). 这里的单词操作不仅是计算(在我们的例子中添加),还包括比较(在我们的例子中少于)。 So in the second line above, the entire a + 1f < 1f is done at a higher precision: Adding the value -2.98023224E-08f ( -0.0000000298023224 ) to 1 results in 0.9999999701976776 which is then compared to 1f and obviously return true : 所以在上面的第二行中,整个a + 1f < 1f以更高的精度完成:将值-2.98023224E-08f-0.0000000298023224 )加到1得到0.9999999701976776 ,然后将其与1f进行比较,显然返回true

Console.WriteLine(a + 1f < 1f); //True

At no time there is any casting to float , because the result of the comparison is bool . 在任何时候都没有任何铸造float ,因为比较的结果是bool

In the first line however, we're simply printing the result of the calculation a+1f , and because both operands are float , the result is automatically casted down to float and that causes it to be truncated and rounded to 1 : 然而,在第一行中,我们只是打印计算结果a+1f ,并且因为两个操作数都是float ,结果会自动转换为float并导致它被截断并舍入为1

Console.WriteLine(a + 1f); //1

Now the big question is about the third line. 现在最大的问题是关于第三条线。 What's different this time is that the cast is forcing the result of the calculation to be casted down to float, which truncates and rounds it to 1 and then this is compared to 1f . 这次的不同之处在于,强制转换是强制将计算结果下移到float,将其截断并舍入为1 ,然后将其与1f进行比较。 The comparison is still done at a higher precision, but now it doesn't matter because the casting has already changed the result of the calculation: 比较仍然以更高的精度进行,但现在无关紧要,因为铸件已经改变了计算结果:

Console.WriteLine((float)(a + 1f) < 1f); //False

So the casting here is causing the two operations (addition and comparison) to be done separately. 因此,这里的铸造导致两个操作(添加和比较)分开进行。 Without casting, the steps are: add, compare, print. 没有铸造,步骤是:添加,比较,打印。 With casting, the steps are: add, cast, compare, print. 通过铸造,步骤是:添加,铸造,比较,打印。 Both operations are still done at a higher precision, because casting cannot affect that. 两种操作仍然以更高的精度完成,因为铸造不会影响它。

Perhaps Visual Studio is saying that the casting is redundant because it is not taking into account whether the operations will be done at a higher precision or not. 也许Visual Studio说铸造是多余的,因为它没有考虑操作是否会以更高的精度完成。

I think the important part of the c# spec here is this: 我认为c#规范的重要部分是:

"Floating-point operations may be performed with higher precision than the result type of the operation. For example, some hardware architectures support an "extended" or "long double" floating-point type with greater range and precision than the double type, and implicitly perform all floating-point operations using this higher precision type. Only at excessive cost in performance can such hardware architectures be made to perform floating-point operations with less precision, and rather than require an implementation to forfeit both performance and precision, C# allows a higher precision type to be used for all floating-point operations. " - https://msdn.microsoft.com/en-us/library/aa691146(v=vs.71).aspx “可以以比操作的结果类型更高的精度执行浮点运算。例如,某些硬件架构支持”扩展“或”长双“浮点类型,其范围和精度比双类型更大,并且使用这种更高精度的类型隐式执行所有浮点运算。只有在性能成本过高的情况下,才能使这种硬件架构以较低的精度执行浮点运算,而不是要求实现放弃性能和精度,C#允许一种更高精度的类型,用于所有浮点运算。“ - https://msdn.microsoft.com/en-us/library/aa691146(v=vs.71).aspx

We can infer that this is almost certainly what is happening by looking at these three lines of code, doing the comparison in slightly different ways: 我们可以通过查看这三行代码来推断这几乎肯定是发生了什么,以稍微不同的方式进行比较:

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

As you can see, the results of the first calculation (which is what we are wondering about) is the same as if the intermediate value is cast as a double telling us that the compiler is taking advantage of the option of performing the calculations at a higher precision. 正如您所看到的,第一次计算的结果(这是我们想知道的)与将中间值强制转换为双精度相同,告诉我们编译器正在利用在a处执行计算的选项。精度更高。

Of course the reason the results are different is because although we can see that the result should be true when it calculates a+1f the result as a single is 1, hence the comparison being false. 当然结果不同的原因是因为虽然我们可以看到结果在计算a+1f时应该为真,但结果为1是单,因此比较为假。

And just to round this off a in the above is stored in a float with an exponent of -25 and a fraction of 0. If you add 1 to this then the -25 exponent parts are too small to be represented so it needs to round and in this case the rounding leaves the number at 1. This is because the way single precision floating point numbers are stored they only have 23 bits for the part following the leading 1 and therefore doesn't have the precision to store the fraction and it ends up rounding it to exactly 1 when stored. 并且只是将上面的a舍入为a ,将其存储在指数为-25且小数为0的浮点数中。如果向此处加1,则-25指数部分太小而无法表示,因此需要舍入在这种情况下,舍入将数字保留为1.这是因为存储单精度浮点数的方式,它们只有23位用于前导1之后的部分,因此没有精度来存储分数和它存储时最终将其四舍五入为1。 Hence why the comparison returns false when we force it to use float calculations all the way. 因此,当我们强制它一直使用浮点计算时,为什么比较返回false。

Because floats are stored in BINARY, IEEE floating point standard represents numbers as a binary mantissa and a binary exponent. 因为浮点数存储在BINARY中,所以IEEE浮点标准将数字表示为二进制尾数和二进制指数。 (powers of 2). (2的权力)。 many decimal numbers cannot be represented exactly in this representation. 在此表示中无法准确表示许多十进制数。 so the compiler uses the nearest available binary IEEE floating point number that is available. 因此编译器使用可用的最近的可用二进制IEEE浮点数。

So since it is not exactly correct, no matter how small the difference actually is, the comparison fails. 因此,由于它不完全正确,无论实际差异有多小,比较都会失败。 calculate the difference and you will see how small it is. 计算差异,你会看到它有多小。

var diff = (float)(x+dx+1f) - 1f;

If you use decimals, it would probably work. 如果你使用小数,它可能会工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM