;;; fuzz.el --- Compensate 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 'fuzz)
;; in your .emacs file. That will make these functions available:
;; fuzzy= (x y)
;; fuzzy<= (x y)
;; fuzzy/= (x y)
;; fuzzy< (x y)
;; fuzzy>= (x y)
;; fuzzy> (x y)
;; fuzzyzerop (x)
;; fuzzyplusp (x)
;; fuzzyminusp (x)
;; fuzzyintegerp (x)
;; fuzzywholenump (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 `fuzz'.
;; You might need a different `fuzz' for your machine. To find out, try
;; M-x fuzz-selftest. If you get an error, try increasing `fuzz'
;; with M-x set-variable. When you find a value that works, put a line like
;; (setq fuzz 1.0e-9)
;; in your .emacs file.
;; If you need a different fuzz for a particular application, you could
;; localize the variable `fuzz' in a `let' form, or make it buffer-local.
;; The definition of `fuzzy=' is optimized for speed rather than absolute
;; consistency; it's not, for example, strictly commutative, although the
;; difference is unlikely to matter in practice. If you want a more rigorous
;; definition, this one, hacked from Info|elisp|Numbers|Comparison of Numbers
;; (defun fuzzy= (x y)
;; (or (and (fuzzyzerop x) (fuzzyzerop y))
;; (<= (/ (abs (- x y))
;; (max (abs x) (abs y)))
;; fuzz)))
;; might better meet your needs. Because every dyadic comparison function in
;; this package is defined in terms of `fuzzy=', you can simply overwrite the
;; definition of `fuzzy=' with the above definition or your own (by
;; evaluating the defun after evaluating "(require 'fuzz)"), and everything
;; else should just work. (Try M-x fuzz-selftest again to be sure. Note
;; that the unhacked version of `fuzzy=' in Info fails the
;; (assert (fuzzyintegerp fuzz)) test.)
;; In practice, I find fastfuzz.el, which see, useful more often.
;;; Code:
;;;###autoload
(defvar fuzz 1.0e-14
"*Used to calculate whether floats are fuzzy=.")
;; For symbols like 'fuzzyplusp I considered 'fuzzy-plusp, but that uses a
;; different naming style from symbols like 'fuzzy>; it inserts an unexpected
;; hyphen. That'd be likely to confuse developers' memories, so they'd be
;; trying to use symbols like 'fuzzy-> or, for that matter, 'fuzzyplusp.
;; Also, the builtin 'wholenump already uses a wordscrunched style.
;;;###autoload
(defsubst fuzzyzerop (x)
"Return t if NUMBER is within `fuzz' of zero."
(<= (abs x) fuzz))
;;;###autoload
(defsubst fuzzyplusp (x)
"Return t if NUMBER is positive and not within `fuzz' of zero."
(> x fuzz))
;;;###autoload
(defsubst fuzzyminusp (x)
"Return t if NUMBER is negative and not within `fuzz' of zero."
(< x (- fuzz)))
;;;###autoload
(defun fuzzy= (x y)
"Return t if 2 args are nearly equal; defined using the variable `fuzz'."
(or (= x y)
(if (fuzzyzerop x)
(fuzzyzerop y)
(fuzzyzerop (1- (/ y x))))))
;;;###autoload
(defsubst fuzzy/= (x y)
"Return t if 2 args aren't nearly equal; defined using the variable `fuzz'."
(not (fuzzy= x y)))
;;;###autoload
(defsubst fuzzy> (x y)
"Return t if first arg is greater than second, and they're not nearly equal."
(and (> x y) (not (fuzzy= x y))))
;;;###autoload
(defsubst fuzzy<= (x y)
"Return t if first arg is less than second, or they're nearly equal."
(or (< x y) (fuzzy= x y)))
;;;###autoload
(defsubst fuzzy< (x y)
"Return t if first arg is less than second, and they're not nearly equal."
(and (< x y) (not (fuzzy= x y))))
;;;###autoload
(defsubst fuzzy>= (x y)
"Return t if first arg is greater than second, or they're nearly equal."
(or (> x y) (fuzzy= x y)))
;;;###autoload
(defsubst fuzzyintegerp (x)
"Return t if ARG is close enough to an integer to be construed as one."
(fuzzy= (fround x) x))
;;;###autoload
(defsubst fuzzywholenump (x)
"Return t if ARG is close enough to a whole number to be construed as one."
(and (fuzzyintegerp x) (or (plusp x) (fuzzyzerop x))))
;;; 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 fuzz-selftest ()
;; "Test the functions in fuzz.el: `fuzzy=', etc.
;; Signal an error if a test fails.
;; This selftest is not automatically run when fuzz.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 \\[fuzz-selftest]."
;; ;; I considered naming this `fuzzyselftest', but the name doesn't
;; ;; really need to be symmetrical with floating point functions since
;; ;; it's not a floating-point function; instead, I'd rather it be
;; ;; symmetrical with a convention that selftesting packages contain a
;; ;; NAME-selftest function where NAME is the same as the name of the
;; ;; file. That convention in particular is probably superior to one
;; ;; using PREFIX-selftest where PREFIX is the namespace prefix that the
;; ;; package reserves, because some packages reserve multiple prefixes.
;; ;; The NAME-selftest convention would be much more convenient for use
;; ;; by scripts that test groups of packages.
;; (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 (fuzzyplusp 0.01))
;; (assert (fuzzyminusp -0.01))
;;
;; (assert (not (fuzzyzerop 0.01)))
;; (assert (not (fuzzyzerop -0.01)))
;;
;; (assert (fuzzy= (* (/ 10.0 3.0) 3.0) 10.0))
;; (assert (fuzzy= (* (/ 01.0 3.0) 3.0) 01.0))
;;
;; (assert (fuzzy= (+ 0.1 0.2) 0.3))
;; (assert (fuzzy<= (+ 0.1 0.2) 0.3))
;; (assert (fuzzy>= (+ 0.1 0.2) 0.3))
;; (assert (not (fuzzy/= (+ 0.1 0.2) 0.3)))
;; (assert (not (fuzzy> (+ 0.1 0.2) 0.3)))
;; (assert (not (fuzzy< (+ 0.1 0.2) 0.3)))
;;
;; (assert (fuzzy= 1.1 (/ (/ (* 4.0 1.1) 2.0) 2.0)))
;;
;; (assert (fuzzy<= 0.0 0.0))
;; (assert (fuzzy<= 0.0 0.1))
;;
;; (assert (not (fuzzy> 0.0 0.0)))
;; (assert (not (fuzzy> 0.0 0.1)))
;;
;; (assert (fuzzy>= 1.0 1.0))
;; (assert (fuzzy>= 1.1 1.0))
;;
;; (assert (not (fuzzy< 1.0 1.0)))
;; (assert (not (fuzzy< 1.1 1.0)))
;;
;; (assert (fuzzy< +2.0 +3.0))
;; (assert (fuzzy<= +2.0 +3.0))
;; (assert (not (fuzzy> +2.0 +3.0)))
;; (assert (not (fuzzy>= +2.0 +3.0)))
;;
;; (assert (not (fuzzy< +2.0 -3.0)))
;; (assert (not (fuzzy<= +2.0 -3.0)))
;; (assert (fuzzy> +2.0 -3.0))
;; (assert (fuzzy>= +2.0 -3.0))
;;
;; (assert (fuzzy< -2.0 +3.0))
;; (assert (fuzzy<= -2.0 +3.0))
;; (assert (not (fuzzy> -2.0 +3.0)))
;; (assert (not (fuzzy>= -2.0 +3.0)))
;;
;; (assert (not (fuzzy< -2.0 -3.0)))
;; (assert (not (fuzzy<= -2.0 -3.0)))
;; (assert (fuzzy> -2.0 -3.0))
;; (assert (fuzzy>= -2.0 -3.0))
;;
;; (assert (fuzzyzerop fuzz))
;; (assert (fuzzyintegerp fuzz))
;; (assert (fuzzywholenump fuzz))
;;
;; (assert (fuzzyzerop 0.0))
;; (assert (fuzzyintegerp 0.0))
;; (assert (fuzzywholenump 0.0))
;;
;; (assert (fuzzyintegerp 1.0))
;; (assert (fuzzywholenump 1.0))
;;
;; (assert (fuzzyintegerp -1.0))
;;
;; (message "Fuzz selftest successful")
;;
;; ) ;for C-x C-e: (fuzz-selftest)
;;
;;; End of selftest
(provide 'fuzz)
;;; fuzz.el ends here