class: Float
authorStefan Vogel <sv@exept.de>
Mon, 30 Jun 2014 16:17:28 +0200
changeset 16658 1c653a0a7e4d
parent 16657 a87789caf59d
child 16659 6205a731485a
class: Float class definition added: #epsilon #isAlmostEqualTo:nEpsilon: #nextFloat: changed: #initialize #storeString Fuzzy comparing. Use maximum precision for #storeString
Float.st
--- a/Float.st	Mon Jun 30 16:15:35 2014 +0200
+++ b/Float.st	Mon Jun 30 16:17:28 2014 +0200
@@ -14,7 +14,7 @@
 LimitedPrecisionReal variableByteSubclass:#Float
 	instanceVariableNames:''
 	classVariableNames:'DefaultPrintFormat Pi E Halfpi HalfpiNegative Twopi
-		RadiansPerDegree Ln2 Ln10 Sqrt2'
+		RadiansPerDegree Ln2 Ln10 Sqrt2 Epsilon'
 	poolDictionaries:''
 	category:'Magnitude-Numbers'
 !
@@ -397,6 +397,10 @@
 
 defaultPrintFormat:something
     DefaultPrintFormat := something.
+!
+
+epsilon
+    ^ epsilon
 ! !
 
 
@@ -509,16 +513,17 @@
 
 initialize
     Pi isNil ifTrue:[
-	DefaultPrintFormat := '.15'.  "/ print 15 valid digits
-	Pi := 3.14159265358979323846264338327950288419716939937510582097494459.
-	Halfpi := Pi / 2.0.
-	HalfpiNegative := Halfpi negated.
-	Twopi := Pi * 2.0.
-	E := 2.7182818284590452353602874713526625.
-	Sqrt2 := 1.41421356237309504880168872420969808.
-	RadiansPerDegree := Pi / 180.0.
-	Ln2 := 0.69314718055994530941723212145817657.
-	Ln10 := 10.0 ln.
+        DefaultPrintFormat := '.15'.  "/ print 15 valid digits
+        Pi := 3.14159265358979323846264338327950288419716939937510582097494459.
+        Halfpi := Pi / 2.0.
+        HalfpiNegative := Halfpi negated.
+        Twopi := Pi * 2.0.
+        E := 2.7182818284590452353602874713526625.
+        Sqrt2 := 1.41421356237309504880168872420969808.
+        RadiansPerDegree := Pi / 180.0.
+        Ln2 := 0.69314718055994530941723212145817657.
+        Ln10 := 10.0 ln.
+        Epsilon := self computeEpsilon.
     ].
 
     "
@@ -1376,6 +1381,98 @@
     "
 !
 
+isAlmostEqualTo:aNumber nEpsilon:nE 
+    "return true, if the argument, aNumber represents almost the same numeric value
+     as the receiver, false otherwise.
+
+     nE is the number of minimal float distances, that the numbers may differ and
+     still be considered equal.
+
+     For background information why floats need this 
+     read: http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
+    "
+
+%{  /* NOCONTEXT */
+
+    /*
+     * notice:
+     * the following inline code handles some common cases,
+     * and exists as an optimization, to speed up those cases.
+     *
+     * Conceptionally, (and for most other argument types),
+     * mixed arithmetic is implemented by double dispatching
+     * (see the message send at the bottom)
+     */
+
+#ifndef INT64
+# define INT64 long long int
+#endif
+    INT64 ulpDiff;
+    union {
+        double d;
+        INT64 i;
+    } myself, otherFloat;
+    int nEpsilon;
+    double scaledEpsilon;
+
+
+    if (!__isSmallInteger(nE)) {
+        goto tryHarder;
+    }
+
+    nEpsilon =  __intVal(nE);
+    scaledEpsilon = nEpsilon *__floatVal(@global(Epsilon));
+
+    if (__isSmallInteger(aNumber)) {
+        otherFloat.d = (double)(__intVal(aNumber));
+    } else if (aNumber == nil) {
+        RETURN(false)
+    } else if (__qIsFloatLike(aNumber)) {
+        otherFloat.d = (double)(__floatVal(aNumber));
+    } else if (__qIsShortFloat(aNumber)) {
+        otherFloat.d = (double)(__shortFloatVal(aNumber));
+    } else {
+        goto tryHarder;
+    }
+
+    myself.d = __floatVal(self);
+
+    // Check if the numbers are really close -- needed
+    // when comparing numbers near zero (ULP method below fails for numbers near 0!).
+    if (fabs(myself.d - otherFloat.d) <= scaledEpsilon) {
+        RETURN(true);
+    }
+
+    // if the signs differ, the numbers are different
+    if (signbit(myself.d) != signbit(otherFloat.d)) {
+        RETURN(false);
+    }
+
+    // compute the difference of the 'units in the last place" ULP
+    // (if ulpDiff == 1, two floats are adjecant)
+    ulpDiff = myself.i - otherFloat.i;
+    if (ulpDiff < 0) ulpDiff = -ulpDiff;
+    if (ulpDiff <= nEpsilon) {
+        RETURN(true);
+    } else {
+        RETURN(false)
+    }
+
+tryHarder:;
+%}.
+    ^ aNumber isAlmostEqualToFromFloat:self nEpsilon:nE
+
+    "
+        67329.234 isAlmostEqualTo:67329.23400000001 nEpsilon:1
+        1.0 isAlmostEqualTo:1.0001 nEpsilon:1
+        1.0 isAlmostEqualTo:-1.0 nEpsilon:1
+        1 isAlmostEqualTo:1.000000000000001 nEpsilon:1
+        1 isAlmostEqualTo:1.000000000000001 nEpsilon:10
+        1.0 isAlmostEqualTo:1 nEpsilon:1
+        1.0 isAlmostEqualTo:1 asFraction nEpsilon:1
+    "
+!
+
 ~= aNumber
     "return true, if the arguments value are not equal"
 
@@ -1763,30 +1860,30 @@
 
     __BEGIN_PROTECT_REGISTERS__
 #ifdef SYSV
-    len = snprintf(buffer, sizeof(buffer), "%.15lg", __floatVal(self));
+    len = snprintf(buffer, sizeof(buffer), "%.17lg", __floatVal(self));
 #else
-    len = snprintf(buffer, sizeof(buffer), "%.15G", __floatVal(self));
+    len = snprintf(buffer, sizeof(buffer), "%.17G", __floatVal(self));
 #endif
     __END_PROTECT_REGISTERS__
 
     if (len >= 0 && len < sizeof(buffer)-3) {
-	/*
-	 * kludge to make integral float f prints as "f.0" (not as "f" as printf does)
-	 * (i.e. look if string contains '.' or 'e' and append '.0' if not)
-	 */
-	for (cp = buffer; *cp; cp++) {
-	    if ((*cp == '.') || (*cp == ',') || (*cp == 'E') || (*cp == 'e')) break;
-	}
-	if (!*cp && (cp[-1] >= '0') && (cp[-1] <= '9')) {
-	    *cp++ = '.';
-	    *cp++ = '0';
-	    *cp = '\0';
-	}
-
-	s = __MKSTRING(buffer);
-	if (s != nil) {
-	    RETURN (s);
-	}
+        /*
+         * kludge to make integral float f prints as "f.0" (not as "f" as printf does)
+         * (i.e. look if string contains '.' or 'e' and append '.0' if not)
+         */
+        for (cp = buffer; *cp; cp++) {
+            if ((*cp == '.') || (*cp == ',') || (*cp == 'E') || (*cp == 'e')) break;
+        }
+        if (!*cp && (cp[-1] >= '0') && (cp[-1] <= '9')) {
+            *cp++ = '.';
+            *cp++ = '0';
+            *cp = '\0';
+        }
+
+        s = __MKSTRING(buffer);
+        if (s != nil) {
+            RETURN (s);
+        }
     }
 %}.
     "
@@ -1799,26 +1896,26 @@
     ^ ObjectMemory allocationFailureSignal raise.
 
     "
-	1.0 storeString
-	1.234 storeString
-	1e10 storeString
-	1.2e3 storeString
-	1.2e30 storeString
-	Float pi storeString
-	(1.0 uncheckedDivide:0) storeString
-	(0.0 uncheckedDivide:0) storeString
+        1.0 storeString
+        1.234 storeString
+        1e10 storeString
+        1.2e3 storeString
+        1.2e30 storeString
+        Float pi storeString
+        (1.0 uncheckedDivide:0) storeString
+        (0.0 uncheckedDivide:0) storeString
 
      notice that the storeString is NOT affected by DecimalPointCharacterForPrinting:
 
-	DecimalPointCharacterForPrinting := $,.
-	1.234 storeString.
-	1.0 storeString.
-	1e10 storeString.
-	1.2e3 storeString.
-	1.2e30 storeString.
-	(1.0 uncheckedDivide:0) storeString.
-	(0.0 uncheckedDivide:0) storeString.
-	DecimalPointCharacterForPrinting := $.
+        DecimalPointCharacterForPrinting := $,.
+        1.234 storeString.
+        1.0 storeString.
+        1e10 storeString.
+        1.2e3 storeString.
+        1.2e30 storeString.
+        (1.0 uncheckedDivide:0) storeString.
+        (0.0 uncheckedDivide:0) storeString.
+        DecimalPointCharacterForPrinting := $.
     "
 ! !
 
@@ -1906,20 +2003,18 @@
 	^ self elementBoundsError:value
     ].
     ^ self indexNotIntegerOrOutOfBounds:index
-! !
-
-!Float methodsFor:'queries'!
+!
 
 basicSize
     "return the size in bytes of the float.
 
      Notice:
-	the need to redefine this method here is due to the
-	inability of many machines to store floats in non-double aligned memory.
-	Therefore, on some machines, the first 4 bytes of a float are left unused,
-	and the actual float is stored at index 5 .. 12.
-	To hide this at one place, this method knows about that, and returns
-	values as if this filler wasnt present."
+        the need to redefine this method here is due to the
+        inability of many machines to store floats in non-double aligned memory.
+        Therefore, on some machines, the first 4 bytes of a float are left unused,
+        and the actual float is stored at index 5 .. 12.
+        To hide this at one place, this method knows about that, and returns
+        values as if this filler wasn't present."
 
 %{  /* NOCONTEXT */
 
@@ -1927,6 +2022,39 @@
 %}.
 ! !
 
+!Float methodsFor:'queries'!
+
+nextFloat:count
+    "answer the next float count places after (or before if count is negative) myself"
+
+%{
+#ifndef INT64
+# define INT64 long long int
+#endif
+    union {
+        double d;
+        INT64 i;
+    } this;
+
+    if (__isSmallInteger(count)) {
+        this.d = __floatVal(self);
+        if (isfinite(this.d))
+            this.i += __intVal(count);
+
+        RETURN(__MKFLOAT(this.d));
+    }
+%}.
+    self primitiveFailed:#badArgument
+
+  "
+     (1.0 nextFloat:1) storeString
+     (67329.234 nextFloat:1) storeString
+     (67329.234 asShortFloat nextFloat:1) storeString
+     Float NaN nextFloat:100000
+     Float infinity nextFloat:100000
+  "
+! !
+
 !Float methodsFor:'special access'!
 
 exponent
@@ -2888,11 +3016,11 @@
 !Float class methodsFor:'documentation'!
 
 version
-    ^ '$Header: /cvs/stx/stx/libbasic/Float.st,v 1.198 2014-06-25 12:20:31 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic/Float.st,v 1.199 2014-06-30 14:17:28 stefan Exp $'
 !
 
 version_CVS
-    ^ '$Header: /cvs/stx/stx/libbasic/Float.st,v 1.198 2014-06-25 12:20:31 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic/Float.st,v 1.199 2014-06-30 14:17:28 stefan Exp $'
 ! !