Is this a valid float comparison that accounts for a set number of decimal places?

偶尔善良 提交于 2019-12-04 02:44:20

Based on @infact's answer and some comments in both the question and the answer I came up with

public static bool AlmostEquals(this float float1, float float2, int precision = 2) 
{ 
    float epsilon = Math.Pow(10.0, -precision) 
    return (Math.Abs(float1-float2) <= epsilon);   
} 

This has the benfit of accepting any integer, you could check for > 1.0 precision by using precision = -x, where x is the power of 10 to check against.

I would also recommend making the default precision = 3, which would give you accuracy down to a tenth of a penny, by default, if this method were used for financials.

If the user wants to "compare two floats using a set number of decimal points (significant figures)" and this actually means that we have a function

AlmostEquals(14.3XXXXXXXX, 14.3YYYYYYY, 1) == true for all possible XXX and YYY and the last parameter is the decimal place after the decimal point.

there is a simple but unfortunate answer:

It is NOT possible to program this function which will fulfill this contract. It may be possible to program something which will often give the correct result, but you cannot foresee when this will be the case, so the function is effectively worthless.

The given solutions here break already with AlmostEquals(0.06f, 0.14f, 1) = true but 0 != 1.

Why ? The first reason is the extreme sensitivity. For example: 0.0999999999.... and 0.100000...1 have different digits in the first place, but they are almost indistinguishable in the difference, they are almost exactly equal. Whatever the mythical function does, it cannot allow even small differences in calculation.

The second reason is that we want actually calculate with the numbers. I used VC 2008 with C# to print out the correct values of the Math.pow function. The first is the precision parameter, the second the hex value of the resulting float and the third is the exact decimal value.

1 3dcccccd 0.100000001490116119384765625

2 3c23d70a 0.00999999977648258209228515625

3 3a83126f 0.001000000047497451305389404296875

4 38d1b717 0.0000999999974737875163555145263671875

5 3727c5ac 0.00000999999974737875163555145263671875

6 358637bd 9.999999974752427078783512115478515625E-7

As you can see, the sequence 0.1, 0.01, 0.001 etc. produces numbers which are excellent approximations, but are either slightly too small or too big.

What if we enforce that the given place must have the correct digit ? Lets enumerate the 16 binary values for 4 bits

0.0
0.0625
0.125
0.1875
0.25
0.3125
0.375
0.4375
0.5
0.5625
0.625
0.6875
0.75
0.8125
0.875
0.9375

16 different binary numbers should be able to suffice for 10 decimal numbers if we want to calculate only with one place after the decimal point. While 0.5 is exactly equal, enforcing the same decimal digit means that 0.4 needs 0.4375 and 0.9 needs 0.9375, introducing severe errors.

Violating the first condition of extreme sensitivity means that you cannot do anything reasonable with such numbers. If you would know that the decimal place of a number has a certain value, you would not need to calculate in the first place.

The C# documentation even cites an example: http://msdn.microsoft.com/en-us/library/75ks3aby.aspx

Notes to Callers

Because of the loss of precision that can result from representing decimal values as floating-point numbers or performing arithmetic operations on floating-point values, in some cases the Round(Double, Int32) method may not appear to round midpoint values to the nearest even value in the digits decimal position. This is illustrated in the following example, where 2.135 is rounded to 2.13 instead of 2.14. This occurs because internally the method multiplies value by 10digits, and the multiplication operation in this case suffers from a loss of precision.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!