#|
 This file is a part of float-features
 (c) 2018 Shirakumo http://tymoon.eu (shinmera@tymoon.eu)
 Author: Nicolas Hafner <shinmera@tymoon.eu>
|#

(defpackage #:float-features
  (:nicknames #:org.shirakumo.float-features)
  (:use #:cl)
  (:export
   #:short-float-positive-infinity
   #:short-float-negative-infinity
   #:short-float-nan
   #:single-float-positive-infinity
   #:single-float-negative-infinity
   #:single-float-nan
   #:double-float-positive-infinity
   #:double-float-negative-infinity
   #:double-float-nan
   #:long-float-positive-infinity
   #:long-float-negative-infinity
   #:long-float-nan
   #:float-infinity-p
   #:float-nan-p
   #:with-float-traps-masked
   #:short-float-bits
   #:single-float-bits
   #:double-float-bits
   #:long-float-bits
   #:bits-short-float
   #:bits-single-float
   #:bits-double-float
   #:bits-long-float))

(in-package #:org.shirakumo.float-features)

(defconstant short-float-positive-infinity
  #+ccl 1S++0
  #+clasp ext:short-float-positive-infinity
  #+cmucl extensions:short-float-positive-infinity
  #+ecl ext:short-float-positive-infinity
  #+mezzano mezzano.extensions:short-float-positive-infinity
  #+mkcl ext:short-float-positive-infinity
  #+sbcl sb-ext:short-float-positive-infinity
  #+lispworks 1S++0
  #+allegro (coerce excl:*infinity-single* 'short-float)
  #-(or ccl clasp cmucl ecl mezzano mkcl sbcl lispworks allegro)
  most-positive-short-float)

(defconstant short-float-negative-infinity
  #+ccl -1S++0
  #+clasp ext:short-float-negative-infinity
  #+cmucl extensions:short-float-negative-infinity
  #+ecl ext:short-float-negative-infinity
  #+mezzano mezzano.extensions:short-float-negative-infinity
  #+mkcl ext:short-float-negative-infinity
  #+sbcl sb-ext:short-float-negative-infinity
  #+lispworks -1S++0
  #+allegro (coerce excl:*negative-infinity-single* 'short-float)
  #-(or ccl clasp cmucl ecl mezzano mkcl sbcl lispworks allegro)
  most-negative-short-float)

(defconstant single-float-positive-infinity
  #+abcl extensions:single-float-positive-infinity
  #+allegro excl:*infinity-single*
  #+ccl 1F++0
  #+clasp ext:single-float-positive-infinity
  #+cmucl extensions:single-float-positive-infinity
  #+ecl ext:single-float-positive-infinity
  #+mezzano mezzano.extensions:single-float-positive-infinity
  #+mkcl mkcl:single-float-positive-infinity
  #+sbcl sb-ext:single-float-positive-infinity
  #+lispworks 1F++0
  #-(or abcl allegro ccl clasp cmucl ecl mezzano mkcl sbcl lispworks)
  most-positive-single-float)

(defconstant single-float-negative-infinity
  #+abcl extensions:single-float-negative-infinity
  #+allegro excl:*negative-infinity-single*
  #+ccl -1F++0
  #+clasp ext:single-float-negative-infinity
  #+cmucl extensions:single-float-negative-infinity
  #+ecl ext:single-float-negative-infinity
  #+mezzano mezzano.extensions:single-float-negative-infinity
  #+mkcl mkcl:single-float-negative-infinity
  #+sbcl sb-ext:single-float-negative-infinity
  #+lispworks -1F++0
  #-(or abcl allegro ccl clasp cmucl ecl mezzano mkcl sbcl lispworks)
  most-negative-single-float)

(defconstant double-float-positive-infinity
  #+abcl extensions:double-float-positive-infinity
  #+allegro excl:*infinity-double*
  #+ccl 1D++0
  #+clasp ext:double-float-positive-infinity
  #+cmucl extensions:double-float-positive-infinity
  #+ecl ext:double-float-positive-infinity
  #+mezzano mezzano.extensions:double-float-positive-infinity
  #+mkcl mkcl:double-float-positive-infinity
  #+sbcl sb-ext:double-float-positive-infinity
  #+lispworks 1D++0
  #-(or abcl allegro ccl clasp cmucl ecl mezzano mkcl sbcl lispworks)
  most-positive-double-float)

(defconstant double-float-negative-infinity
  #+abcl extensions:double-float-negative-infinity
  #+allegro excl:*negative-infinity-double*
  #+ccl -1D++0
  #+clasp ext:double-float-negative-infinity
  #+cmucl extensions:double-float-negative-infinity
  #+ecl ext:double-float-negative-infinity
  #+mezzano mezzano.extensions:double-float-negative-infinity
  #+mkcl mkcl:double-float-negative-infinity
  #+sbcl sb-ext:double-float-negative-infinity
  #+lispworks -1D++0
  #-(or abcl allegro ccl clasp cmucl ecl mezzano mkcl sbcl lispworks)
  most-negative-double-float)

(defconstant long-float-positive-infinity
  #+ccl 1L++0
  #+clasp ext:long-float-positive-infinity
  #+cmucl extensions:long-float-positive-infinity
  #+ecl ext:long-float-positive-infinity
  #+mezzano mezzano.extensions:long-float-positive-infinity
  #+mkcl ext:long-float-positive-infinity
  #+sbcl sb-ext:long-float-positive-infinity
  #+lispworks 1L++0
  #-(or ccl clasp cmucl ecl mezzano mkcl sbcl lispworks)
  most-positive-long-float)

(defconstant long-float-negative-infinity
  #+ccl -1L++0
  #+clasp ext:long-float-negative-infinity
  #+cmucl extensions:long-float-negative-infinity
  #+ecl ext:long-float-negative-infinity
  #+mezzano mezzano.extensions:long-float-negative-infinity
  #+mkcl ext:long-float-negative-infinity
  #+sbcl sb-ext:long-float-negative-infinity
  #+lispworks -1L++0
  #-(or ccl clasp cmucl ecl mezzano mkcl sbcl lispworks)
  most-negative-long-float)

(declaim (inline float-infinity-p
                 float-nan-p))

(defun float-infinity-p (float)
  #+abcl (system:float-infinity-p float)
  #+allegro (excl:infinityp float)
  #+ccl (ccl::infinity-p float)
  #+clasp (ext:float-infinity-p float)
  #+cmucl (extensions:float-infinity-p float)
  #+ecl (ext:float-infinity-p float)
  #+mezzano (mezzano.extensions:float-infinity-p float)
  #+sbcl (sb-ext:float-infinity-p float)
  #-(or abcl allegro ccl clasp cmucl ecl mezzano sbcl)
  (etypecase float
    (short-float (or (= float short-float-negative-infinity)
                     (= float short-float-positive-infinity)))
    (single-float (or (= float single-float-negative-infinity)
                      (= float single-float-positive-infinity)))
    (double-float (or (= float double-float-negative-infinity)
                      (= float double-float-positive-infinity)))
    (long-float (or (= float long-float-negative-infinity)
                    (= float long-float-positive-infinity)))))

(defun float-nan-p (float)
  #+abcl (system:float-nan-p float)
  #+allegro (excl:nanp float)
  #+ccl (and (ccl::nan-or-infinity-p float)
             (not (ccl::infinity-p float)))
  #+clasp (ext:float-nan-p float)
  #+cmucl (extensions:float-nan-p float)
  #+ecl (ext:float-nan-p float)
  #+mezzano (mezzano.extensions:float-nan-p float)
  #+sbcl (sb-ext:float-nan-p float)
  #+lispworks (sys::nan-p float)
  #-(or abcl allegro ccl clasp cmucl ecl mezzano sbcl lispworks)
  (/= float float))

(defun keep (list &rest keeps)
  (loop for item in list
        when (find item keeps)
        collect item))

(defmacro with-float-traps-masked (traps &body body)
  (let ((traps (etypecase traps
                 ((eql T) '(:underflow :overflow :inexact :invalid :divide-by-zero :denormalized-operand))
                 (list traps))))
    #+abcl
    (let ((previous (gensym "PREVIOUS")))
      `(let ((,previous (extensions:get-floating-point-modes)))
         (unwind-protect
              (progn
                (extensions:set-floating-point-modes
                 :traps ',(keep traps :overflow :underflow))
                NIL ,@body)
           (apply #'extensions:set-floating-point-modes ,previous))))
    #+ccl
    (let ((previous (gensym "PREVIOUS"))
          (traps (loop for thing in traps
                       for trap = (case thing
                                    (:underflow :underflow)
                                    (:overflow :overflow)
                                    (:divide-by-zero :division-by-zero)
                                    (:invalid :invalid)
                                    (:inexact :inexact))
                       when trap collect trap)))
      `(let ((,previous (ccl:get-fpu-mode)))
         (unwind-protect
              (progn
                (ccl:set-fpu-mode
                 ,@(loop for trap in traps
                         collect trap collect NIL))
                NIL ,@body)
           (apply #'ccl:set-fpu-mode ,previous))))
    #+clisp
    (if (find :underflow)
        `(ext:without-floating-point-underflow
           ,@body)
        `(progn
           ,@body))
    #+cmucl
    `(extensions:with-float-traps-masked #+x86 ,traps #-x86 ,(remove :denormalized-operand traps)
       ,@body)
    #+ecl
    (let ((previous (gensym "PREVIOUS")))
      `(let ((,previous (si::trap-fpe :last T)))
         (unwind-protect
              (progn
                ,@(loop for trap in traps
                        for keyword = (case trap
                                        (:underlow :floating-point-underflow)
                                        (:overflow :floating-point-overflow)
                                        (:inexact :floating-point-inexact)
                                        (:invalid :floating-point-invalid)
                                        (:divide-by-zero :division-by-zero))
                        when keyword collect `(si::trap-fpe ,keyword T))
                NIL ,@body)
           (si::trap-fpe ,previous NIL))))
    #+clasp
     `(ext:with-float-traps-masked ,traps
       ,@body)
    #+mezzano
    (let ((previous (gensym "PREVIOUS"))
          (traps (loop for thing in traps
                       for trap = (case thing
                                    (:underflow :underflow)
                                    (:overflow :overflow)
                                    (:divide-by-zero :divide-by-zero)
                                    (:invalid :invalid-operation)
                                    (:inexact :precision)
                                    #+x86-64
                                    (:denormalized-operand :denormal-operand))
                       when trap collect trap)))
      `(let ((,previous (mezzano.runtime::get-fpu-mode)))
         (unwind-protect
              (progn
                (mezzano.runtime::set-fpu-mode
                 ,@(loop for trap in traps
                         collect trap collect T))
                NIL ,@body)
           (apply #'mezzano.runtime::set-fpu-mode ,previous))))
    #+sbcl
    `(sb-int:with-float-traps-masked #+x86 ,traps #-x86 ,(remove :denormalized-operand traps)
       ,@body)
    #-(or abcl ccl clasp clisp cmucl ecl mezzano sbcl)
    (declare (ignore traps))
    #-(or abcl ccl clasp clisp cmucl ecl mezzano sbcl)
    `(progn ,@body)))

(declaim (inline short-float-bits
                 single-float-bits
                 double-float-bits
                 long-float-bits
                 bits-short-float
                 bits-single-float
                 bits-double-float
                 bits-long-float))

(declaim (ftype (function (T) (unsigned-byte 16)) short-float-bits))
(defun short-float-bits (float)
  (declare (ignorable float))
  #+mezzano
  (mezzano.extensions:short-float-to-ieee-binary16 float)
  #+(or ecl sbcl cmucl allegro ccl
        (and 64-bit lispworks))
  (let* ((bits (single-float-bits float))
         (sign (ldb (byte 1 31) bits))
         (exp (- (ldb (byte 8 23) bits) 127))
         (sig (ldb (byte 23 0) bits)))
    (cond
      ((or (eql 0s0 float)
           (< exp -24))
       ;;underflow
       (ash sign 15))
      ((< exp -14)
       ;; encode as denormal if possible
       (logior (ash sign 15)
               0
               (ash (ldb (byte 11 13)
                         (logior (ash 1 23) sig))
                    (+ exp 14))))
      ((< exp 16)
       ;; encode directly
       (logior (ash sign 15)
               (ash (+ exp 15) 10)
               (ash sig -13)))
      ((zerop sig)
       ;; infinity
       (if (zerop sign)
           #b0111110000000000
           #b1111110000000000))
      (t
       ;;NaN
       (logior (ash sign 15)
               (ash #x1f 10)
               (ldb (byte 10 13) sig)))))
  ;; clisp short-float is 1+8+16
  ;; 32bit lispworks 5+ is 1+8+??, lw4 only has double
  ;; not sure about others?
  #- (or mezzano ecl sbcl cmucl allegro ccl (and 64-bit lispworks))
  (progn float (error "Implementation not supported.")))

(declaim (ftype (function (T) (unsigned-byte 32)) single-float-bits))
(defun single-float-bits (float)
  #+abcl
  (ldb (byte 32 0) (system:single-float-bits float))
  #+allegro
  (multiple-value-bind (high low) (excl:single-float-to-shorts float)
    (logior low (ash high 16)))
  #+ccl
  (ccl::single-float-bits float)
  #+clasp
  (ext:single-float-to-bits float)
  #+cmucl
  (ldb (byte 32 0) (kernel:single-float-bits float))
  #+ecl
  (si:single-float-bits float)
  #+lispworks
  (let ((v (sys:make-typed-aref-vector 4)))
    (declare (optimize (speed 3) (float 0) (safety 0)))
    (declare (dynamic-extent v))
    (setf (sys:typed-aref 'single-float v 0) float)
    (sys:typed-aref '(unsigned-byte 32) v 0))
  #+mezzano
  (mezzano.extensions:single-float-to-ieee-binary32 float)
  #+sbcl
  (ldb (byte 32 0) (sb-kernel:single-float-bits float))
  #-(or abcl allegro ccl clasp cmucl ecl lispworks mezzano sbcl)
  (progn float (error "Implementation not supported.")))

(declaim (ftype (function (T) (unsigned-byte 64)) double-float-bits))
(defun double-float-bits (float)
  #+abcl
  (logior (system::double-float-low-bits float)
          (ash (system::double-float-high-bits float) 32))
  #+allegro
  (multiple-value-bind (s3 s2 s1 s0) (excl:double-float-to-shorts float)
    (logior s0 (ash s1 16) (ash s2 32) (ash s3 48)))
  #+ccl
  (multiple-value-bind (high low) (ccl::double-float-bits float)
    (logior low (ash high 32)))
  #+clasp
  (ext:double-float-to-bits float)
  #+cmucl
  (ldb (byte 64 0)
   (logior (kernel:double-float-low-bits float)
           (ash (kernel:double-float-high-bits float) 32)))
  #+ecl
  (si:double-float-bits float)
  #+lispworks
  (let ((v (sys:make-typed-aref-vector 8)))
    (declare (optimize (speed 3) (float 0) (safety 0)))
    (declare (dynamic-extent v))
    (setf (sys:typed-aref 'double-float v 0) float)
        #+x86-64 (sys:typed-aref '(unsigned-byte 64) v 0)
        #-x64-64 (logior (sys:typed-aref '(unsigned-byte 32) v 0)
                         (ash (sys:typed-aref '(unsigned-byte 32) v 4) 32)))
  #+mezzano
  (mezzano.extensions:double-float-to-ieee-binary64 float)
  #+sbcl
  (ldb (byte 64 0)
       (logior (sb-kernel:double-float-low-bits float)
               (ash (sb-kernel:double-float-high-bits float) 32)))
  #-(or abcl allegro ccl clasp cmucl ecl lispworks mezzano sbcl)
  (progn float (error "Implementation not supported.")))

(declaim (ftype (function (T) (unsigned-byte 128)) long-float-bits))
(defun long-float-bits (float)
  (declare (ignore float))
  (error "Implementation not supported."))

(declaim (ftype (function (T) short-float) bits-short-float))
(defun bits-short-float (bits)
  (declare (ignorable bits))
  #+mezzano
  (mezzano.extensions:ieee-binary16-to-short-float bits)
  #+ (or ecl sbcl cmucl allegro ccl
         (and 64-bit lispworks))

  (let ((sign (ldb (byte 1 15) bits))
        (exp (ldb (byte 5 10) bits))
        (sig (ldb (byte 10 0) bits)))
    (if (= exp 31)
        (cond
          ((not (zerop sig))
           ;; NaNs
           (bits-single-float
            (logior (ash sign 31)
                    (ash #xff 23)
                    ;; store in high-bit to preserve quiet/signalling
                    (ash sig 13))))
          ;; infinities
          ((zerop sign)
           single-float-positive-infinity)
          (t
           single-float-negative-infinity))
        (cond
          ((= 0 exp sig)
           ;; +- 0
           (if (zerop sign) 0s0 -0s0))
          ((zerop exp)
           ;; denormals -> single floats
           (let ((d (- 11 (integer-length sig))))
             (setf exp (- -14 d))
             (setf sig (ldb (byte 11 0) (ash sig (1+ d))))
             (bits-single-float
              (logior (ash sign 31)
                      (ash (+ exp 127) 23)
                      (ash sig #.(- 23 11))))))
          (t
           ;; normal numbers
           (bits-single-float
            (logior (ash sign 31)
                    (ash (+ exp #.(+ 127 -15)) 23)
                    (ash sig #.(- 23 10))))))))
    #- (or mezzano ecl sbcl cmucl allegro ccl (and 64-bit lispworks))
  (progn bits (error "Implementation not supported.")))

(declaim (ftype (function (T) single-float) bits-single-float))
(defun bits-single-float (bits)
  #+abcl
  (system:make-single-float bits)
  #+allegro
  (excl:shorts-to-single-float (ldb (byte 16 16) bits) (ldb (byte 16 0) bits))
  #+ccl
  (ccl::host-single-float-from-unsigned-byte-32 bits)
  #+clasp
  (ext:bits-to-single-float bits)
  #+cmucl
  (flet ((s32 (x)
           (logior x (- (mask-field (byte 1 31) x))) ))
    (kernel:make-single-float (s32 bits)))
  #+ecl
  (si:bits-single-float bits)
  #+lispworks
  (let ((v (sys:make-typed-aref-vector 4)))
    (declare (optimize speed (float 0) (safety 0)))
    (declare (dynamic-extent v))
    (setf (sys:typed-aref '(unsigned-byte 32) v 0) bits)
    (sys:typed-aref 'single-float v 0))
  #+mezzano
  (mezzano.extensions:ieee-binary32-to-single-float bits)
  #+sbcl
  (sb-kernel:make-single-float
   (sb-c::mask-signed-field 32 (the (unsigned-byte 32) bits)))
  #-(or abcl allegro ccl clasp cmucl ecl lispworks mezzano sbcl)
  (progn bits (error "Implementation not supported.")))

(declaim (ftype (function (T) double-float) bits-double-float))
(defun bits-double-float (bits)
  #+abcl
  (system:make-double-float bits)
  #+allegro
  (excl:shorts-to-double-float
   (ldb (byte 16 48) bits) (ldb (byte 16 32) bits) (ldb (byte 16 16) bits) (ldb (byte 16 0) bits))
  #+ccl
  (ccl::double-float-from-bits (ldb (byte 32 32) bits) (ldb (byte 32 0) bits))
  #+clasp
  (ext:bits-to-double-float bits)
  #+cmucl
  (flet ((s32 (x)
           (logior x (- (mask-field (byte 1 31) x))) ))
    (kernel:make-double-float (s32 (ldb (byte 32 32) bits))
                              (ldb (byte 32 0) bits)))
  #+ecl
  (si:bits-double-float bits)
  #+lispworks
  (let ((v (sys:make-typed-aref-vector 8)))
    (declare (optimize speed (float 0) (safety 0)))
    (declare (dynamic-extent v))
    #+x86-64 (setf (sys:typed-aref '(unsigned-byte 64) v 0) bits)
    #-x86-64 (setf (sys:typed-aref '(unsigned-byte 32) v 0) (ldb (byte 32 0) bits)
                   (sys:typed-aref '(unsigned-byte 32) v 4) (ldb (byte 32 32) bits))
    (sys:typed-aref 'double-float v 0))
  #+mezzano
  (mezzano.extensions:ieee-binary64-to-double-float bits)
  #+sbcl
  (sb-kernel:make-double-float
   (sb-c::mask-signed-field 32 (ldb (byte 32 32) (the (unsigned-byte 64) bits)))
   (ldb (byte 32 0) bits))
  #-(or abcl allegro ccl clasp cmucl ecl lispworks mezzano sbcl)
  (progn bits (error "Implementation not supported.")))

(declaim (ftype (function (T) long-float) bits-long-float))
(defun bits-long-float (bits)
  (declare (ignore bits))
  (error "Implementation not supported."))
