;;
;; precision-mode.el
;;
;; "THE BEER-WARE LICENSE":
;; Alberto Ramos wrote this file. As long as you retain this 
;; notice you can do whatever you want with this stuff. If we meet some 
;; day, and you think this stuff is worth it, you can buy me a beer in 
;; return. <alberto.ramos@cern.ch>
;;
;; file:    precision-mode.el
;; created: Fri Jun 29 2018
;;
;; DESCRIPTION:
;;
;; precision-mode provides simple routines to write data with
;; uncertainties in human readable format.
;;
;;
;; USAGE:
;;
;; Add (precision-mode) to your .emacs file
;;
;;
;; Variables:
;;
;; (setq prec-ndigits 3): Set the number of significant digits in
;;   the errors. 
;;
;;
;; Functions:
;;
;; M-x prec-pm-to-paren: Change numbers with uncertainties in the
;;   format val +/- error to the human readable format val(err).
;;
;; M-x prec-paren-to-pm: Change numbers in human readable format 
;;   val(error) to the format val +/- error.
;;
;;
;; TODO
;;
;; At the moment this minor mode is just a simple collection of a few
;; routines to change fotmat of numbers.
;;

(defvar prec-ndigits 2 "Number of significant digits in error")

(defun prec-all-to-paren ()
  "Substitute val err or val +/- err to val(err)"
  (interactive)
  (save-restriction
	(narrow-to-region (region-beginning) (region-end))

	(goto-char (point-min))
	(while (search-forward-regexp "[+-]?[0-9]*\\.[0-9]*\\([eE]?[+-]?[0-9]+\\)?[[:space:]]+\\(+/-\\)?[[:space:]]+[+-]?[0-9]*\\.[0-9]*\\([eE]?[+-]?[0-9]+\\)?" nil t)
	  (save-match-data
	  
	    (setq str (split-string (match-string 0)) )
	    (setq v  (nth 0 str))
	    (setq dv (nth 1 str))
	    (if (= (length str) 3)
		(setq dv (nth 2 str))
	      (setq dv (nth 1 str))
	     )
	    )
	    
	  (replace-match (prec-print-paren (string-to-number v) (string-to-number dv)) )
	 )
	)
  )

(defun prec-paren-to-pm ()
  "Substitute val(err) to val +/- err"
  (interactive)
  (save-restriction
	(narrow-to-region (region-beginning) (region-end))
	(goto-char (point-min))
	(while (search-forward-regexp "[+-]?[0-9]*\\.[0-9]*([0-9]+)" nil t)
	  
	  (save-match-data
	    (setq str (split-string (match-string 0) "("))
	    (setq v  (nth 0 str))
	    (setq dv (substring (nth 1 str) 0 -1))

	    (setq nerr (length dv))
	    (setq ndec (length (nth 1 (split-string v "\\."))))
	    (setq ddv (/ (string-to-number (concat dv ".0")) (expt 10 ndec)))
	    )
	  
	  (replace-match (prec-print-pm (string-to-number v) ddv) )	  
	  )
	)

    )

(defun prec-pm-to-paren ()
  "Substitute val+/-err to val(err)"
  (interactive)
  (save-restriction
	(narrow-to-region (region-beginning) (region-end))

	(goto-char (point-min))
	(while (search-forward-regexp "[+-]?[0-9]*\\.[0-9]*\\([eE]?[+-]?[0-9]+\\)?[[:space:]]*\\(+/-\\)[[:space:]]*[+-]?[0-9]*\\.[0-9]*\\([eE]?[+-]?[0-9]+\\)?" nil t)
	  (save-match-data
	    (setq str (split-string (match-string 0) "+/-") )
	    (setq v  (nth 0 str))
	    (if (= (length str) 3)
		(setq dv (nth 2 str))
	      (setq dv (nth 1 str))
	     )
	    )
	    
	  (replace-match (prec-print-paren (string-to-number v) (string-to-number dv)) )
	 )
	)
  )

(defun prec-error-to-paren ()
  "Substitute val err to val(err)"
  (interactive)
  (save-restriction
	(narrow-to-region (region-beginning) (region-end))

	(goto-char (point-min))
	(while (search-forward-regexp "[+-]?[0-9]*\\.[0-9]*\\([eE]?[+-]?[0-9]+\\)?[[:space:]]+[+-]?[0-9]*\\.[0-9]*\\([eE]?[+-]?[0-9]+\\)?" nil t)
	  (save-match-data
	  
	    (setq str (split-string (match-string 0)) )
	    (setq v  (nth 0 str))
	    (setq dv (nth 1 str))
	    )
	    
	  (replace-match (prec-print-paren (string-to-number v) (string-to-number dv)) )
	 )
	)
  )

(defun prec-print-paren (val err)
  "Print val err with val(err) format"
  (setq prec (truncate (log10 (+ err (/ err 100000.)))))
  (if (<= prec 0)
      (setq ndec (+ (- prec) prec-ndigits))
    (setq ndec 0)
    )
  (if (and (< err 10.0) (>= err 1.0))
      (setq ndec (- ndec 1))
      )
  (setq fmt (concat "\%12." (format "%d" ndec) "f"))
  (setq fmt (concat fmt "(%d)"))
  (format fmt val (round (* err (expt 10 ndec))))
  
  )

(defun prec-print-pm (val err)
  "Print val err with val +/- err format"
  (if (< val 99.999999)
      (format "%.8f +/- %.4e" val err)
    (format "%.8e +/- %.4e" val err)
    )
  
  )

(define-minor-mode precision-mode
  "Toggle Precision mode."
      ;; The initial value.
      nil
      ;; The indicator for the mode line.
      :lighter " Prec"
      ;; The minor mode bindings.
      :keybindings 
      :group 'precision)

(provide 'precision-mode)
