; A small package of programs to help use IEEE Float NaNs and IEEE
;; Infinity from within (Allegro CL) Lisp.  The basic strategy is to
;; allow a programmer access to the encoding of the fraction part of
;; the NaN, and with additional utility programs, make progress toward
;; integrating these special values into other programs. Included are
;; routines showing how to use a NaN fraction for encoding short
;; messages. 3 letters in a single, 7 in a double. This could be
;; used for diagnostic information.

;;We do this because the built-in program in Lisp, decode-float,
;;does not work for inf or nan. At least in ACL.
;; This package defines my-decode-float  which should model
;; decode-float in most ways, except for treatment of NaN and Inf.

;; Thanks to Duane Rettig for suggesting I should just violate the
;; type abstraction (twice) as a relatively simple way of getting
;; access to the encodings, and for helping get the programs to work.

;; First some utility programs, the (only) ones that hack
;; on the representation. These could be written in some other
;; Lisp implementation, or in C.

;; Given a double float, convert to an array of 64 bits.
;; this must be compiled with type checking disabled. And the
;; reverse.

;; The corresponding programs, sfloatbits  and bits2sfloat, for single
;; floats appear later.

(defun dfloat2bits(x &aux 
		     (ans (make-array 64 :element-type '(unsigned-byte 1))))
  (declare(optimize (speed 3)(safety 0))
	  (type (simple-array (unsigned-byte 1)(64)) x))
  (dotimes (i 64 ans) (declare (fixnum i))(setf (aref ans i) (aref x i))))

;; Given a 64bit array, convert to double float
;; this must be compiled with type checking disabled.

(defun bits2dfloat(x)  
  (declare(optimize (speed 3)(safety 0))
	  (double-float x))
  (+ 0.0d0 x))

;;example  (bits2dfloat(dfloat2bits -543.21d0)) returns -543.21d0

;; The numbering of bits in the float is the same as the numbering of
;; bits in this bit array.  It is reversed from the way they are often
;; pictured, however.

(defun b2i (x ans) ;; bits to integer, reverse order
  (let ((L (1-(length x))))
    (dotimes (i (1+ L) ans)
      (setf ans (+ (ash ans 1) (aref x (- L i)))))))

;;pos integer number to bitarray and back
(defun n2b(x)
  (let*
      ((L (integer-length x))
       (ans (make-array L :element-type '(unsigned-byte 1))))
    (dotimes (j L ans)
      (setf (aref ans j) (logand 1 x))
      (setf x (ash x -1)))))

;; reverse direction is (b2i b 0)

(defun dparts(x) ;; separate a double-float into parts.
  (assert (typep x 'double-float)) ; for now.
  (let* ((ba (dfloat2bits x))
	 (sign (aref ba 63))
	 (exp (subseq ba 52 63))
	 (frac(subseq ba 0 52)))
   ;; (format t "~%raw values sign=~s exp=~s frac=~s" sign exp frac)
    (setf exp (- (b2i exp 0) 1023)) ; for normalized, anyway
    (setf frac ;check for zero, inf
      (cond((and (= exp -1023)(every #'zerop frac)) 0) ; zero
	   ((and (= exp 1024) (every #'zerop frac)) 0) ; infinity
	   ;;should check for "denormalized" or subnormal here.
	   ;; else put in hidden normalized bit; put in leading bit
	   (t(b2i frac 1))))
    (values sign exp frac
	    ;; this last value should be
	    ;; the same as (rational x), but it is here for
	    ;; a sanity check.
	    #+ignore
	    (* (expt -1 sign)  ;; this is the float EXACTLY as rational
	       (expt 2 (+ exp -52))
	       frac)
	    )))

;; decode-float is sort of like taking the sign, exp, frac
;; from parts, and returning instead 
;; (* frac #.(expt 2.0d0 -52))
;; (1+ exp)
;; (expt -1.0d0 sign)
#+ignore
(defun my-decode-dfloat(x)
  (assert (typep x 'double-float)) ;; rewrite for single if needed.
  
  (multiple-value-bind
      (sign exp frac)
      (dparts x)
    (values   (* frac #.(expt 2.0d0 -53))
	      (1+ exp)
	      (expt -1.0d0 sign))))

;; note. This version of my-decode-dfloat does this:
;; (my-decode-dfloat #.excl::*nan-double*)
;; returns 0.5000000000000001d0, 1025, 1.0d0
;; which is better for my purposes than "error".

;; This could be the end of the file if all we needed was
;; double float decode for *nan* BUT...
;; if we want to encode "stuff" in a nan, we can do this

(defvar nan-template  (dfloat2bits #.excl::*nan-double*))

;; Our way to set funny nan bits:
;; start with the bit-string for a general NaN
;; and put in some particular fraction.

(defun makenan(c) ;; c is a char string, up to 7 chars. 
 (let ((h (copy-seq nan-template)))
   (setf (subseq h 0 51) (n2b (chars2num c)))
   (bits2dfloat h)))

;; Note that we can pack 7 chars in 7-bit ascii 7X7=49 < 52.
;; Feed this next program a character string. You get a number
;; to put in the NaN fraction.

(defun chars2num(c &aux (ans 0))
  ;; c is probably no more than 7 chars for this application.
  (dotimes (i (length c) ans)
    (setf ans(ash ans 7)) ; only first 128, up to #\~ = 126
    (incf ans (char-code (aref c i)))))

(defun num2chars(n &aux (ans nil)) ;reverse transformation from above
  (declare (special letter-lookup))
  (loop while (> n 31) do
    (push (aref letter-lookup (logand n 127)) ans)
    (setf n (ash n -7)))
  (coerce ans 'string))

;; There used to be int-char for this..
(defparameter letter-lookup 
    (make-array 127 :element-type 'character :initial-element #\space))
(loop for i in 
      (coerce "abcdefghijklmnopqrstuvwxyz 
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-+
,.:[]{}:;\"\\|!@#$%^&*()_+~`<>?/" 'list) 
    do (setf (aref letter-lookup  (char-code i))i))

;; Second version of my-decode-dfloat
(defun my-decode-dfloat(x)
  (assert (typep x 'double-float)) ;; rewrite for single if needed.
  (multiple-value-bind
      (sign exp frac)
      (dparts x)
    ;; not clear what we want to return here for a NaN, but at least for
    ;; testing try this..
    (if (= exp 1024) ;; a nan or inf!!
	(values			;;	(num2chars (ash frac -1))
	 (if (= frac 0) "Inf" (num2chars frac))
		(1+ exp)
		(expt -1.0d0 sign))
      
	;;(format nil "A double-NaN with code = ~a" (num2chars frac))
    (values   (* frac #.(expt 2.0d0 -53))
	      (1+ exp)
	      (expt -1.0d0 sign)))))

(defun my-decode-float(r)
  (typecase r (single-float (my-decode-sfloat r))
	    (double-float (my-decode-dfloat r))
	    (real (my-decode-dfloat (* 1.0d0 r)))
	    (t (error "decode-float called on ~s, not a number" r))))

;; example.  try (my-decode-float  (makenan "HiDuane"))

;; An appendix to decode.lisp,
;; which is small package of programs to help use IEEE Float NaNs and IEEE
;; Infinity from within (Allegro CL) Lisp.  

;; This is supposed to work for SINGLE FLOAT format.
;; convert a single-float to an array of bits.

(defun sfloat2bits(x &aux 
		     (ans (make-array 32 :element-type 'bit :short t)))
  (declare(optimize (speed 3)(safety 0))
	  (type (short-simple-array bit(32)) x))
  (dotimes (i 32 ans) (declare (fixnum i))(setf (aref ans i) (aref x i))))

;; Given a 32bit array, convert to double float
;; this must be compiled with type checking disabled.

(defun bits2sfloat(x)  
  (declare(optimize (speed 3)(safety 0))
	  (single-float x))
  (+ 0.0 x))

;;example  (bits2sfloat(sfloat2bits -543.21))

(defun sparts(x) ;; separate a double-float into parts.
  ;(assert (typep x 'single-float)) ; for now.
  (let* ((ba (sfloat2bits x))
	 (sign (aref ba 31))
	 (exp (subseq ba 23 31))
	 (frac (subseq ba 0 23))
	 )
;;    (format t "~%raw values sign=~s exp=~s frac=~s" sign exp frac)
    ;;(setf exp (- (b2i exp 0) 1023)) ; for normalized, anyway
    (setf exp (- (b2i exp 0) 127))
    (setf frac ;check for zero, inf
      (cond((and (= exp -127)(every #'zerop frac))
	    0) 				; zero
	   ((and (= exp 128) (every #'zerop frac))
	    0)				; infinity
	   ;;should check for denormalized here.
	   ;; else put in hidden normalized bit; put in leading bit
	   (t(b2i frac 1))))
    (values sign 
	    exp
	    ;;(1- exp)
	    ;;(ash frac -1)
	    frac
	    ;; this last value should be
	    ;; the same as (rational x), but it is here for
	    ;; a sanity check.
	    ;;#+ignore
	    (* (expt -1 sign)  ;; this is the float EXACTLY as rational
	       (expt 2 (+ exp -23))
	       frac)
	    )))

;; decode-float is sort of like taking the sign, exp, frac
;; from parts, and returning instead 
;; (* frac #.(expt 2.0d0 -52))
;; (1+ exp)
;; (expt -1.0d0 sign)

(defvar snan-template  (sfloat2bits #.excl::*nan-single*))

;; Our way to set funny nan bits:
;; start with the bit-string for a general NaN
;; and put in some particular fraction.

(defun makesnan(c) ;; c is a char string, up to 7 chars. 
 (let ((h (copy-seq snan-template)))
   (setf (subseq h 0 23) (n2b (chars2num c)))
   (bits2sfloat h)))

;; Note that we can pack 3 chars in 7-bit ascii 3X7=21 < 23.
;; Feed this next program a character string. You get a number
;; to put in the NaN fraction.

(defun my-decode-sfloat(x) ;; single float version
  (multiple-value-bind
      (sign exp frac)
      (sparts x)
    ;; not clear what we want to return here for a NaN, but at least for
    ;; testing try this..
    (if (= exp 128) ;; a nan or inf!!
	(values			;;	(num2chars (ash frac -1))
	 (if (= frac 0) "Inf" (num2chars frac))
		(1+ exp)
		(expt -1.0 sign))
      
	;;(format nil "A single-NaN with code = ~a" (num2chars frac))
    (values   (* frac #.(expt 2.0 -24))
	      (1+ exp)
	      (expt -1.0 sign)))))

;; example.  try (my-decode-sfloat  (makesnan "HiD"))