;;; fastfuzz.el --- Compensate fast for floating-point roundoff error
;; Copyright (C) 1998 Will Mengarini
;; Author: Will Mengarini
;; URL:
;; Created: Su 15 Mar 98
;; Version: 0.20, Mo 04 May 98
;; Keywords: extensions, float, floating point, fuzz
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; Try this with C-x C-e:
;; (= (+ 0.1 0.2) 0.3)
;; Running on an Intel 486/50 with hardware floating point, GNU 19.34.1
;; returns `nil' because of a general problem with the imprecise
;; representation of floating point numbers. This package implements
;; functions that compensate for that. For a general discussion of the
;; issue, see the Info node elisp|Numbers|Comparison of Numbers.
;; To use this package, first you'll need to copy this file to a directory
;; that appears in your load-path. `load-path' is the name of a variable
;; that contains a list of directories Emacs searches for files to load.
;; To prepend another directory to load-path, put a line like
;; (add-to-list 'load-path "c:/My_Directory") in your .emacs file.
;; Then, put
;; (require 'fastfuzz)
;; in your .emacs file. That will make these functions available:
;; fastfuzzy= (x y)
;; fastfuzzy<= (x y)
;; fastfuzzy/= (x y)
;; fastfuzzy< (x y)
;; fastfuzzy>= (x y)
;; fastfuzzy> (x y)
;; fastfuzzyzerop (x)
;; fastfuzzyplusp (x)
;; fastfuzzyminusp (x)
;; fastfuzzyintegerp (x)
;; fastfuzzywholenump (x)
;; Each has a meaning analogous to the corresponding bare function, except
;; that numbers are considered equal if they're "close enough", which is
;; defined in terms of this package's global variable `fastfuzz'.
;; The difference between this package and fuzz.el is that this package
;; defines equality without using division, so it's faster, but doesn't work
;; at extreme orders of magnitude. For example, on an Intel 486,
;; (let ((fuzz 1e-14)
;; (fastfuzz 1e-14))
;; (and (fastfuzzy= 1e-13 1.1e-13)
;; (not (fuzzy= 1e-13 1.1e-13))
;; (fuzzy= 1e11 (/ (* 1e11 3.3) (+ 1.1 2.2)))
;; (not (fastfuzzy= 1e11 (/ (* 1e11 3.3) (+ 1.1 2.2))))))
;; An example of an appropriate application for fastfuzz.el would be checking
;; whether probabilities sum to 1.0.
;; You might need a different `fastfuzz' for your machine. To find out, try
;; M-x fastfuzz-selftest. If you get an error, try increasing `fastfuzz'
;; with M-x set-variable. When you find a value that works, put a line like
;; (setq fastfuzz 1.0e-4)
;; in your .emacs file.
;; If you need a different fastfuzz for a particular application, you could
;; localize the variable `fastfuzz' in a `let' form, or make it buffer-local.
;; Note that this package doesn't share fuzz.el's property that
;; every dyadic comparison function is defined in terms of
;; `fastfuzzy=' (mutatis mutandi), so you can't just overwrite that
;; one function to modify the package's definition of equality.
;;; Code:
;;;###autoload
(defvar fastfuzz 1.0e-6
"*Two floats with magnitudes closer than this are considered fastfuzzy=.")
;;;###autoload
(defsubst fastfuzzyzerop (x)
"Return t if NUMBER is within `fastfuzz' of zero."
(<= (abs x) fastfuzz))
;;;###autoload
(defsubst fastfuzzyplusp (x)
"Return t if NUMBER is positive and not within `fastfuzz' of zero."
(> x fastfuzz))
;;;###autoload
(defsubst fastfuzzyminusp (x)
"Return t if NUMBER is negative and not within `fastfuzz' of zero."
(< x (- fastfuzz)))
;;;###autoload
(defsubst fastfuzzy= (x y)
"Return t if 2 args are nearly equal; defined using variable `fastfuzz'."
(<= (abs (- x y)) fastfuzz))
;;;###autoload
(defsubst fastfuzzy/= (x y)
"Return t if 2 args aren't nearly equal; defined using variable `fastfuzz'."
(> (abs (- x y)) fastfuzz))
;;;###autoload
(defsubst fastfuzzy> (x y)
"Return t if first arg is greater than second, and they're not nearly equal."
(> (- x y) fastfuzz))
;;;###autoload
(defsubst fastfuzzy<= (x y)
"Return t if first arg is less than second, or they're nearly equal."
(<= (- x y) fastfuzz))
;;;###autoload
(defsubst fastfuzzy< (x y)
"Return t if first arg is less than second, and they're not nearly equal."
(> (- y x) fastfuzz))
;;;###autoload
(defsubst fastfuzzy>= (x y)
"Return t if first arg is greater than second, or they're nearly equal."
(<= (- y x) fastfuzz))
;;;###autoload
(defsubst fastfuzzyintegerp (x)
"Return t if ARG is close enough to an integer to be construed as one."
(fastfuzzy= (fround x) x))
;;;###autoload
(defsubst fastfuzzywholenump (x)
"Return t if ARG is close enough to a whole number to be construed as one."
(and (fastfuzzyintegerp x) (fastfuzzy>= x 0.0)))
;;; Selftest:
;;
;; Use `delete-rectangle', orthodoxily bound to C-x r d, to uncomment this
;; code for automated regression testing. It's commented out only to save
;; space (at RMS's request) in released Emacs; if you're going to hack it you
;; probably want to leave the selftest enabled.
;;
;; (require 'cl)
;;
;; (defun fastfuzz-selftest ()
;; "Test the functions in fastfuzz.el: `fastfuzzy=', etc.
;; Signal an error if a test fails.
;; This selftest is not automatically run when fastfuzz.el is loaded,
;; so if you're on a new machine, or building a new Emacs,
;; you might want to run it by hand with \\[fastfuzz-selftest]."
;; ;; See discussion of naming convention in fuzz.el's fuzz-selftest.
;; (interactive)
;;
;; ;; This test suite is nothing like a comprehensive one; it just
;; ;; tries to catch the most egregious brain farts.
;; ;; Contributions would be welcomed.
;; ;; With C-x C-e, this code
;; ;; (mapcar (lambda (x) (format "%.18g" x)) [.1 .2 .3])
;; ;; (mapcar (lambda (x) (format "%.18g" x)) [.4 .5 .6])
;; ;; (mapcar (lambda (x) (format "%.18g" x)) [.7 .8 .9])
;; ;; may be useful in getting an idea how to construct problem expressions.
;;
;; (assert (fastfuzzyplusp 0.01))
;; (assert (fastfuzzyminusp -0.01))
;;
;; (assert (not (fastfuzzyzerop 0.01)))
;; (assert (not (fastfuzzyzerop -0.01)))
;;
;; (assert (fastfuzzy= (* (/ 10.0 3.0) 3.0) 10.0))
;; (assert (fastfuzzy= (* (/ 01.0 3.0) 3.0) 01.0))
;;
;; (assert (fastfuzzy= (+ 0.1 0.2) 0.3))
;; (assert (fastfuzzy<= (+ 0.1 0.2) 0.3))
;; (assert (fastfuzzy>= (+ 0.1 0.2) 0.3))
;; (assert (not (fastfuzzy/= (+ 0.1 0.2) 0.3)))
;; (assert (not (fastfuzzy> (+ 0.1 0.2) 0.3)))
;; (assert (not (fastfuzzy< (+ 0.1 0.2) 0.3)))
;;
;; (assert (fastfuzzy= 1.1 (/ (/ (* 4.0 1.1) 2.0) 2.0)))
;;
;; (assert (fastfuzzy<= 0.0 0.0))
;; (assert (fastfuzzy<= 0.0 0.1))
;;
;; (assert (not (fastfuzzy> 0.0 0.0)))
;; (assert (not (fastfuzzy> 0.0 0.1)))
;;
;; (assert (fastfuzzy>= 1.0 1.0))
;; (assert (fastfuzzy>= 1.1 1.0))
;;
;; (assert (not (fastfuzzy< 1.0 1.0)))
;; (assert (not (fastfuzzy< 1.1 1.0)))
;;
;; (assert (fastfuzzy< +2.0 +3.0))
;; (assert (fastfuzzy<= +2.0 +3.0))
;; (assert (not (fastfuzzy> +2.0 +3.0)))
;; (assert (not (fastfuzzy>= +2.0 +3.0)))
;;
;; (assert (not (fastfuzzy< +2.0 -3.0)))
;; (assert (not (fastfuzzy<= +2.0 -3.0)))
;; (assert (fastfuzzy> +2.0 -3.0))
;; (assert (fastfuzzy>= +2.0 -3.0))
;;
;; (assert (fastfuzzy< -2.0 +3.0))
;; (assert (fastfuzzy<= -2.0 +3.0))
;; (assert (not (fastfuzzy> -2.0 +3.0)))
;; (assert (not (fastfuzzy>= -2.0 +3.0)))
;;
;; (assert (not (fastfuzzy< -2.0 -3.0)))
;; (assert (not (fastfuzzy<= -2.0 -3.0)))
;; (assert (fastfuzzy> -2.0 -3.0))
;; (assert (fastfuzzy>= -2.0 -3.0))
;;
;; (assert (fastfuzzyzerop fastfuzz))
;; (assert (fastfuzzyintegerp fastfuzz))
;; (assert (fastfuzzywholenump fastfuzz))
;;
;; (assert (fastfuzzyzerop 0.0))
;; (assert (fastfuzzyintegerp 0.0))
;; (assert (fastfuzzywholenump 0.0))
;;
;; (assert (fastfuzzyintegerp 1.0))
;; (assert (fastfuzzywholenump 1.0))
;;
;; (assert (fastfuzzyintegerp -1.0))
;;
;; (message "Fastfuzz selftest successful")
;;
;; ) ;for C-x C-e: (fastfuzz-selftest)
;;
;;; End of selftest
(provide 'fastfuzz)
;;; fastfuzz.el ends here