All~

A good "close enough" comparison is actually very difficult.  I adapted one
based on

http://adtmag.com/Articles/2000/03/16/Comparing-Floats-How-To-Determine-if-Floating-Quantities-Are-Close-Enough-Once-a-Tolerance-Has-Been.aspx?Page=1

which provides a pretty good example and handles all the cases that I could
find.

    public static boolean compareDoubles(double tolerance, double d1, double
d2) {

        // For a discussion and analysis of this technique, check out:

        //
http://adtmag.com/Articles/2000/03/16/Comparing-Floats-How-To-Determine-if-Floating-Quantities-Are-Close-Enough-Once-a-Tolerance-Has-Been.aspx?Page=1



        // handle 0.0 specifically, as it will screw with our division

        if (d1 == 0.0 || d2 == 0.0) { return Math.abs(d1 - d2) < tolerance;
}



        // catch overflow cases, we will divide by d1 which could increase
the value of d2

        // but use absolute value to handle all the different permutations
of signs

        double d1abs = Math.abs(d1);

        double d2abs = Math.abs(d2);

        if ((d1abs < 1.0) && (d2abs > d1abs*Double.MAX_VALUE)) { return
false; }



        // comparing that they have a ratio near 1.0 handles both very large

        // and very small numbers cleanly with a single tolerance

        double ratio = d2 / d1;

        return ((1-tolerance) < ratio && ratio < (1+tolerance));
    }

I use a default tolerance of 1e-6, which works fairly well.

Enjoy,
Matt

On Sat, Oct 16, 2010 at 7:05 PM, Steven E. Harris <s...@panix.com> wrote:

> cej38 <junkerme...@gmail.com> writes:
>
> > (defn float=
> >   ([x y] (float= x y 0.00001))
> >   ([x y epsilon]
> >      (let [scale (if (or (zero? x) (zero? y)) 1 (Math/abs x))]
> >        (<= (Math/abs (- x y)) (* scale epsilon)))) )
>
> You're scaling epsilon incorrectly here. Epsilon defines the smallest
> value that yields a value greater than one when added to one. If you're
> not using it along with one, you're using it incorrectly.
>
> What you need to do is scale epsilon by having its exponent match the
> value with which you want to use it, while maintaining its mantissa. The
> C library offers functions ldexp()¹ and frexp()² to split and scale the
> exponent of a floating point number, respectively; Common Lisp offers
> DECODE-FLOAT³ and SCALE-FLOAT for the same purpose.
>
> I don't know of any standard functions in Java -- or Clojure -- that
> allow one to destructure a floating point number like this. It's
> possible to use Float#floatToRawIntBits(), an understanding of IEEE 754,
> and tweezers to get there.
>
> If you assume you have a function called `scaled-epsilon' that accepts
> the exemplar value with which you intend to use epsilon,
>
>  (defn scaled-epsilon [n] ...)
>
> you can use the following function `same?' to compare your floating
> point values, assuming they're nonnegative:
>
> ,----
> | (defn same?
> |   [m n]
> |   (if (< n m)
> |       (sufficiently-close? n m)
> |       (sufficiently-close? m n)))
> |
> | (defn- sufficiently-close?
> |   [smaller larger]
> |   (or (zero? larger)
> |       (if (zero? smaller)
> |           (< larger (scaled-epsilon 0.0)))
> |           (zero? (- 1 (/ smaller larger)))))
> `----
>
> Note too that scaling epsilon must take into account whether you intend
> to add it or subtract it from some companion number. It's common to use
> the word "epsilon" to mean some fudge factor without considering what
> the value really means for a floating point number. Indeed, using it
> properly in Java is still too difficult.
>
>
> Footnotes:
> ¹ http://www.dinkumware.com/manuals/?manual=compleat&page=math.html#frexp
> ² http://www.dinkumware.com/manuals/?manual=compleat&page=math.html#ldexp
> ³
> http://www.lispworks.com/documentation/HyperSpec/Body/f_dec_fl.htm#decode-float
>
> http://www.lispworks.com/documentation/HyperSpec/Body/f_dec_fl.htm#scale-float
>
> --
> Steven E. Harris
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com<clojure%2bunsubscr...@googlegroups.com>
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to