Lo que todo programador debería saber sobre aritmética de punto flotante
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Better comparison function (with tests)

+150 -21
+126
content/errors/NearlyEqualsTest.java
··· 1 + import static org.junit.Assert.assertFalse; 2 + import static org.junit.Assert.assertTrue; 3 + 4 + import org.junit.Test; 5 + 6 + /** 7 + * Demonstrates a good method for comparing floating-point values 8 + * using an epsilon. Run via JUnit 4 9 + * 10 + * From http://floating-point-gui.de 11 + * 12 + * @author Michael Borgwardt 13 + */ 14 + public class NearlyEqualsTest 15 + { 16 + public static boolean nearlyEqual(float a, float b) 17 + { 18 + final float epsilon = 0.000001f; 19 + final float absA = Math.abs(a); 20 + final float absB = Math.abs(b); 21 + final float diff = Math.abs(a-b); 22 + 23 + if (a*b==0) { // a or b or both are zero 24 + // relative error is not meaningful here 25 + return diff < Float.MIN_VALUE / epsilon; 26 + } else { // use relative error 27 + return diff / (absA+absB) < epsilon; 28 + } 29 + } 30 + 31 + /** Regular large numbers - generally not problematic */ 32 + @Test public void big() 33 + { 34 + assertTrue(nearlyEqual(1000000f, 1000001f)); 35 + assertTrue(nearlyEqual(1000001f, 1000000f)); 36 + assertFalse(nearlyEqual(10000f, 10001f)); 37 + assertFalse(nearlyEqual(10001f, 10000f)); 38 + } 39 + 40 + /** Negative large numbers */ 41 + @Test public void bigNeg() 42 + { 43 + assertTrue(nearlyEqual(-1000000f, -1000001f)); 44 + assertTrue(nearlyEqual(-1000001f, -1000000f)); 45 + assertFalse(nearlyEqual(-10000f, -10001f)); 46 + assertFalse(nearlyEqual(-10001f, -10000f)); 47 + } 48 + 49 + /** Numbers around 1 */ 50 + @Test public void mid() 51 + { 52 + assertTrue(nearlyEqual(1.0000001f, 1.0000002f)); 53 + assertTrue(nearlyEqual(1.0000002f, 1.0000001f)); 54 + assertFalse(nearlyEqual(1.0002f, 1.0001f)); 55 + assertFalse(nearlyEqual(1.0001f, 1.0002f)); 56 + } 57 + 58 + /** Numbers around -1 */ 59 + @Test public void midNeg() 60 + { 61 + assertTrue(nearlyEqual(-1.000001f, -1.000002f)); 62 + assertTrue(nearlyEqual(-1.000002f, -1.000001f)); 63 + assertFalse(nearlyEqual(-1.0001f, -1.0002f)); 64 + assertFalse(nearlyEqual(-1.0002f, -1.0001f)); 65 + } 66 + 67 + /** Numbers between 1 and 0 */ 68 + @Test public void small() 69 + { 70 + assertTrue(nearlyEqual(0.000000001000001f, 0.000000001000002f)); 71 + assertTrue(nearlyEqual(0.000000001000002f, 0.000000001000001f)); 72 + assertFalse(nearlyEqual(0.000000000001002f, 0.000000000001001f)); 73 + assertFalse(nearlyEqual(0.000000000001001f, 0.000000000001002f)); 74 + } 75 + 76 + /** Numbers between -1 and 0 */ 77 + @Test public void smallNeg() 78 + { 79 + assertTrue(nearlyEqual(-0.000000001000001f, -0.000000001000002f)); 80 + assertTrue(nearlyEqual(-0.000000001000002f, -0.000000001000001f)); 81 + assertFalse(nearlyEqual(-0.000000000001002f, -0.000000000001001f)); 82 + assertFalse(nearlyEqual(-0.000000000001001f, -0.000000000001002f)); 83 + } 84 + 85 + /** Comparisons involving zero */ 86 + @Test public void zero() 87 + { 88 + assertTrue(nearlyEqual(0.0f, 0.0f)); 89 + assertFalse(nearlyEqual(0.00000001f, 0.0f)); 90 + assertFalse(nearlyEqual(0.0f, 0.00000001f)); 91 + } 92 + 93 + /** Comparisons of numbers on opposite sides of 0 */ 94 + @Test public void opposite() 95 + { 96 + assertFalse(nearlyEqual(1.000000001f, -1.0f)); 97 + assertFalse(nearlyEqual(-1.0f, 1.000000001f)); 98 + assertFalse(nearlyEqual(-1.000000001f, 1.0f)); 99 + assertFalse(nearlyEqual(1.0f, -1.000000001f)); 100 + assertTrue(nearlyEqual(10000f*Float.MIN_VALUE, -10000f*Float.MIN_VALUE)); 101 + } 102 + 103 + /** 104 + * The really tricky part - comparisons of numbers 105 + * very close to zero. 106 + */ 107 + @Test public void ulp() 108 + { 109 + assertTrue(nearlyEqual(Float.MIN_VALUE, -Float.MIN_VALUE)); 110 + assertTrue(nearlyEqual(-Float.MIN_VALUE, Float.MIN_VALUE)); 111 + assertTrue(nearlyEqual(Float.MIN_VALUE, 0)); 112 + assertTrue(nearlyEqual(0, Float.MIN_VALUE)); 113 + assertTrue(nearlyEqual(-Float.MIN_VALUE, 0)); 114 + assertTrue(nearlyEqual(0, -Float.MIN_VALUE)); 115 + 116 + assertFalse(nearlyEqual(0.000000001f, -Float.MIN_VALUE)); 117 + assertFalse(nearlyEqual(0.000000001f, Float.MIN_VALUE)); 118 + assertFalse(nearlyEqual(Float.MIN_VALUE, 0.000000001f)); 119 + assertFalse(nearlyEqual(-Float.MIN_VALUE, 0.000000001f)); 120 + 121 + assertFalse(nearlyEqual(1e20f*Float.MIN_VALUE, 0.0f)); 122 + assertFalse(nearlyEqual(0.0f, 1e20f*Float.MIN_VALUE)); 123 + assertFalse(nearlyEqual(1e20f*Float.MIN_VALUE, -1e20f*Float.MIN_VALUE)); 124 + } 125 + 126 + }
+24 -21
content/errors/comparison.html
··· 8 8 to be equal (e.g. when calculating the same result through different correct methods) often differ 9 9 slightly, and a simple equality test fails. For example: 10 10 11 - a = 0.15 + 0.15 12 - b = 0.1 + 0.2 11 + float a = 0.15 + 0.15 12 + float b = 0.1 + 0.2 13 13 if(a == b) // can be false! 14 14 if(a >= b) // can also be false! 15 15 ··· 34 34 35 35 * When both `a` and `b` are zero. `0.0/0.0` is "not a number", which causes an exception on some platforms, or returns false for all comparisons. 36 36 * When only `b` is zero, the division yields "infinity", which may also cause an exception, or is greater than epsilon even when `a` is smaller. 37 + * It returns `false` when both `a` and `b` are very small but on opposite sides of zero, even when they're the smallest possible non-zero numbers. 37 38 38 39 Also, the result is not commutative (`nearlyEquals(a,b)` is not always the same as `nearlyEquals(b,a)`). To fix these problems, the code has to get a lot more complex, so we really need to put it into a function of its own: 39 40 40 - function nearlyEqual(a,b) 41 - { 42 - epsilon = 0.00001; 43 - if (a==0.0){ 44 - return Math.abs(b) < epsilon; 45 - }else if (b==0.0){ 46 - return Math.abs(a) < epsilon; 47 - } else { // ensure commutativity 48 - return Math.abs((a-b)/a) < epsilon && 49 - Math.abs((b-a)/b) < epsilon; 50 - } 51 - } 41 + public static boolean nearlyEqual(float a, float b) 42 + { 43 + float epsilon = 0.000001f; 44 + float absA = Math.abs(a); 45 + float absB = Math.abs(b); 46 + float diff = Math.abs(a-b); 52 47 53 - if(nearlyEqual(a,b)) 48 + if (a*b==0) { // a or b or both are zero 49 + // relative error is not meaningful here 50 + return diff < Float.MIN_VALUE / epsilon; 51 + } else { // use relative error 52 + return diff / (absA+absB) < epsilon; 53 + } 54 + } 54 55 55 - Unfortunately, this is *still* not perfect; there are at least two problems that are not easy to fix: 56 + This method [passes tests](../NearlyEqualsTest.java) for many important special cases, but as you can see, it 57 + uses some quite non-obvious logic. In particular, it has to use a completely different definition of error margin 58 + when `a` or `b` is zero, because the classical definition of relative error becomes meaningless in those cases. 56 59 57 - * It reverts to using epsilon as an absolute error measure when `a` or `b` is zero. 58 - * It returns `false` when both `a` and `b` are very small but on opposite sides of zero, even when they're the smallest possible non-zero numbers. 60 + There probably are some cases where the method above still produces unexpected results, and some of the tests 61 + it was developed to pass probably specify behaviour that is not appropriate for some applications. Before using it, make sure it's appropriate for your application! 59 62 60 - Compare floating-point values as integers 61 - ----------------------------------------- 62 - But the there is an alternative to adding even more conceptual complexity to such an apparently simple task: instead of comparing `a` and `b` as [real numbers](http://en.wikipedia.org/wiki/Real_numbers), we can think about them as discrete steps and define the error margin as the maximum number of possible floating-point values between the two values. 63 + Comparing floating-point values as integers 64 + ------------------------------------------- 65 + There is an alternative to heaping conceptual complexity onto such an apparently simple task: instead of comparing `a` and `b` as [real numbers](http://en.wikipedia.org/wiki/Real_numbers), we can think about them as discrete steps and define the error margin as the maximum number of possible floating-point values between the two values. 63 66 64 67 This is conceptually very clear and easy and has the advantage of implicitly scaling the relative error margin with the magnitude of the values. Technically, it's a bit more complex, but not as much as you might think, because IEEE 754 floats are designed to maintain their order when their bit patterns are interpreted as integers. 65 68