From: Tino Calancha <tino.calancha@gmail.com>
To: Emacs developers <emacs-devel@gnu.org>
Cc: Tino Calancha <tino.calancha@gmail.com>
Subject: New library num-base-converters
Date: Tue, 15 Aug 2017 02:26:18 +0900 (JST) [thread overview]
Message-ID: <alpine.DEB.2.20.1708150225001.30658@calancha-pc> (raw)
Hi,
a simple library providing base converters for integers.
Convert integers between bases 2, 8, 10 and 16 is
a common task in the engineering and science applications.
I've being using for years several bash scripts calling `bc';
having a library written in elisp sounds like a better idea: it
will work regardless on the OS.
I think this library have its place in both, the core and ELPA.
What do you think?
;;; For Emacs master branch:
--8<-----------------------------cut here---------------start------------->8---
commit d00f1fc6a315e65d28f5212731f537ea9fb82050
Author: Tino Calancha <tino.calancha@gmail.com>
Date: Tue Aug 15 02:09:38 2017 +0900
New library 'num-base-converters'
* lisp/num-base-converters.el: New file.
* test/lisp/num-base-converters-tests.el: Add test suite.
* etc/NEWS (New Modes and Packages in Emacs 26.1):
Announce it.
diff --git a/etc/NEWS b/etc/NEWS
index 3f38153048..4ec6671f1f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1093,6 +1093,9 @@ processes on exit.
* New Modes and Packages in Emacs 26.1
+** New library num-base-converters' to convert integers between
+different bases.
+
** New Elisp data-structure library 'radix-tree'.
** New library 'xdg' with utilities for some XDG standards and specs.
diff --git a/lisp/num-base-converters.el b/lisp/num-base-converters.el
new file mode 100644
index 0000000000..dd31d13d2b
--- /dev/null
+++ b/lisp/num-base-converters.el
@@ -0,0 +1,152 @@
+;;; num-base-converters.el --- Convert integers between different numeric bases -*- lexical-binding: t -*-
+
+;; Copyright (C) 2017 Free Software Foundation, Inc.
+
+;; Author: Tino Calancha <tino.calancha@gmail.com>
+;; Keywords: convenience, numbers, converters, tools
+;; Created: Tue Aug 15 02:04:55 JST 2017
+;; Version: 0.1
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library defines the command `nbc-number-base-converter' to
+;; convert a given integer in a numeric base to a different base.
+;;
+;; For instance, 10 in hexadecimal is 'A':
+;; (nbc-number-base-converter "10" 10 16)
+;; => "A"
+;;
+;; In addition, this file adds the following commands to convert
+;; between the most common bases (2, 8, 10, 16):
+;; `nbc-hex2dec', `nbc-hex2oct', `nbc-hex2bin'
+;; `nbc-dec2hex', `nbc-dec2oct', `nbc-dec2bin'
+;; `nbc-oct2hex', `nbc-oct2dec', `nbc-oct2bin'
+;; `nbc-bin2hex', `nbc-bin2dec', `nbc-bin2oct'.
+
+;;; Code:
+
+
+(require 'calc-bin)
+(eval-when-compile (require 'rx))
+
+(defgroup num-base-converters nil
+ "Convert integers between different numeric bases."
+ :group 'nbc)
+
+(defcustom nbc-define-aliases nil
+ "If non-nil, create aliases without prefix 'nbc' for the converters."
+ :type 'boolean
+ :group 'nbc)
+
+
+
+(defun nbc-number-base-converter (num base-in base-out)
+ "Translate NUM, a string representing an integer, to a different base.
+BASE-IN, an integer, is the basis of the input NUM.
+BASE-OUT, an integer, is the basis to display NUM."
+ (interactive
+ (let ((num (read-string "Number: "))
+ (base-in (read-number "Base input: "))
+ (base-out (read-number "Base output: ")))
+ (list num base-in base-out)))
+ (unless (stringp num)
+ (signal 'wrong-type-argument (list 'stringp num)))
+ (unless (and (>= base-in 2) (<= base-in 36) (>= base-out 2) (<= base-out 36))
+ (user-error "Base `b' must satisfy 2 <= b <= 36: base-in `%d' base-out `%d'"
+ base-in base-out))
+ (let* ((case-fold-search nil)
+ (input (progn
+ (pcase num ; Drop base info from NUMB.
+ ((rx (and string-start
+ (let _u (or "b" "0x" "o")
+ (let v (one-or-more not-newline)) string-end)))
+ (setq num v))
+ ((rx (and string-start "#"
+ (let _u (or "b" "x" "o" (and (one-or-more digit) "r")))
+ (let v (one-or-more not-newline)) string-end))
+ (setq num v)))
+ (condition-case nil
+ ;; Translate to canonical syntaxis: #(base)r(number).
+ (read (format "#%dr%s" base-in num))
+ (invalid-read-syntax
+ (user-error "Wrong input: `%s' for base `%s'"
+ num base-in))))))
+ (condition-case nil
+ (let* ((calc-number-radix base-out)
+ (output (math-format-radix input)))
+ (pcase output
+ ((rx (and string-start
+ (let _u (zero-or-one (and (zero-or-more digit) "#"))
+ (let v (and (one-or-more not-newline) string-end)))))
+ (setq output v))) ; Drop base info from OUTPUT.
+ (message "%s base %s = %s base %s" num base-in output base-out)
+ output)
+ (wrong-type-argument
+ (user-error "Wrong input: `%s' for base `%s'" num base-in)))))
+
+
+;;; Add translatros for the most common basis: decimal, hexadecimal,
+;; octal and binary.
+(eval-when-compile
+ (defmacro nbc--create-converters-1 ()
+ (let ((bases (list "hex" "dec" "oct" "bin"))
+ forms)
+ (dolist (base-out bases)
+ (dolist (base-in bases)
+ (if (equal base-out base-in)
+ nil
+ (push `(nbc--create-command ,base-in ,base-out) forms))))
+ `(progn ,@forms)))
+
+ (defmacro nbc--create-command (base-in base-out)
+ (let* ((input-fn
+ (lambda (x)
+ (pcase x
+ (`"hex" (cons "hexadecimal" 16))
+ (`"dec" (cons "decimal" 10))
+ (`"oct" (cons "octal" 8))
+ (`"bin" (cons "binary" 2)))))
+ (in-lst (funcall input-fn base-in))
+ (base-in-name (car in-lst))
+ (out-lst (funcall input-fn base-out))
+ (func-name (format "nbc-%s2%s" base-in
+ (substring (car out-lst) 0 3)))
+ (prefix-len (length "nbc-"))
+ (docstring (format "Translate NUM, a string, from %s to %s."
+ (car in-lst) (car out-lst)))
+ (ispec (format "sNumber in %s: " (car in-lst))))
+ `(progn
+ (when (bound-and-true-p nbc-define-aliases)
+ (defalias (intern ,(substring func-name prefix-len))
+ (intern ,func-name)))
+ (defun ,(intern func-name) ,(list 'num)
+ ,docstring (interactive ,ispec)
+ (let ((res (nbc-number-base-converter
+ num ,(cdr in-lst) ,(cdr out-lst))))
+ (message "%s %s = %s %s"
+ num ,base-in-name res ,(car out-lst))
+ res))))))
+
+(defun nbc--create-converters ()
+ "Create converters between the bases 2, 8, 10 and 16."
+ (nbc--create-converters-1))
+
+(nbc--create-converters)
+
+(provide 'num-base-converters)
+;;; num-base-converters.el ends here
diff --git a/test/lisp/num-base-converters-tests.el b/test/lisp/num-base-converters-tests.el
new file mode 100644
index 0000000000..c6ac2912b9
--- /dev/null
+++ b/test/lisp/num-base-converters-tests.el
@@ -0,0 +1,86 @@
+;;; num-base-converters-tests.el --- Test suite for num-base-converters. -*- lexical-binding: t -*-
+
+;; Copyright (C) 2017 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+(require 'ert)
+(require 'num-base-converters)
+
+
+(ert-deftest nbc-test-converters ()
+ ;; Input is case-insensitive.
+ (dolist (x (list "a" "A"))
+ (should (equal "10" (nbc-hex2dec x)))
+ (should (equal "12" (nbc-hex2oct x)))
+ (should (equal "1010" (nbc-hex2bin x))))
+ ;;
+ (should (equal "A" (nbc-dec2hex "10")))
+ (should (equal "12" (nbc-dec2oct "10")))
+ (should (equal "1010" (nbc-dec2bin "10")))
+ ;;
+ (should (equal "3F" (nbc-oct2hex "77")))
+ (should (equal "63" (nbc-oct2dec "77")))
+ (should (equal "111111" (nbc-oct2bin "77")))
+ ;;
+ (should (equal "A" (nbc-bin2hex "1010")))
+ (should (equal "10" (nbc-bin2dec "1010")))
+ (should (equal "12" (nbc-bin2oct "1010"))))
+
+(ert-deftest nbc-test-nbc-number-base-converter ()
+ ;; Bases `b' must be: 2 <= b <= 36:
+ (should-error (nbc-number-base-converter "10" 37 10))
+ (should-error (nbc-number-base-converter "10" 10 37))
+ (should-error (nbc-number-base-converter "10" 10 1))
+ (should-error (nbc-number-base-converter "10" 1 10))
+ ;; Invalid input:
+ (should-error (nbc-number-base-converter "1a" 8 10))
+ (should-error (nbc-number-base-converter "az" 16 10))
+ (should-error (nbc-number-base-converter "3" 2 10))
+ (should-error (nbc-number-base-converter "z" 10 8))
+ ;;
+ (should (equal "8" (nbc-number-base-converter "10" 8 16)))
+ (should (equal "10" (nbc-number-base-converter "8" 16 8)))
+ (should (equal "12" (nbc-number-base-converter "10" 10 8)))
+ (should (equal "10" (nbc-number-base-converter "12" 8 10)))
+ (should (equal "1000" (nbc-number-base-converter "10" 8 2)))
+ (should (equal "10" (nbc-number-base-converter "1000" 2 8)))
+ ;;
+ (should (equal "10" (nbc-number-base-converter "a" 16 10)))
+ (should (equal "10" (nbc-number-base-converter "A" 16 10)))
+ (should (equal "A" (nbc-number-base-converter "10" 10 16)))
+ (should (equal "1010" (nbc-number-base-converter "a" 16 2)))
+ (should (equal "1010" (nbc-number-base-converter "A" 16 2)))
+ (should (equal "A" (nbc-number-base-converter "1010" 2 16)))
+ ;;
+ (should (equal "1010" (nbc-number-base-converter "10" 10 2)))
+ (should (equal "10" (nbc-number-base-converter "1010" 2 10))))
+
+(ert-deftest nbc-test-nbc-number-base-converter ()
+ (let (nbc-define-aliases)
+ (nbc--create-converters)
+ (should (fboundp 'nbc-oct2dec))
+ (should-not (fboundp 'oct2dec))
+ (setq nbc-define-aliases t)
+ ;; It should create the aliases, i.e. `oct2dec' etc.
+ (nbc--create-converters)
+ (should (fboundp 'oct2dec))))
+
+
+(provide 'num-base-converters-tests)
+
+;; num-base-converters-tests.el ends here
--8<-----------------------------cut here---------------end--------------->8---
In GNU Emacs 26.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.22.11)
of 2017-08-14
Repository revision: 5ba4c7d16b800864fa14b8a981e33f6aa6fa94d6
;;; For Elpa:
--8<-----------------------------cut here---------------start------------->8---
;;; num-base-converters.el --- Convert integers between different numeric bases -*- lexical-binding: t -*-
;; Copyright (C) 2017 Free Software Foundation, Inc.
;; Author: Tino Calancha <tino.calancha@gmail.com>
;; Maintainer: Tino Calancha <tino.calancha@gmail.com>
;; Keywords: convenience, numbers, converters, tools
;; Created: Tue Aug 15 02:04:55 JST 2017
;; Package-Requires: ((emacs "24.4"))
;; Version: 0.1
;; GNU Emacs 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 3 of the License, or
;; (at your option) any later version.
;; This file is part of GNU Emacs.
;; GNU Emacs 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This library defines the command `nbc-number-base-converter' to
;; convert a given integer in a numeric base to a different base.
;;
;; For instance, 10 in hexadecimal is 'A':
;; (nbc-number-base-converter "10" 10 16)
;; => "A"
;;
;; In addition, this file adds the following commands to convert
;; between the most common bases (2, 8, 10, 16):
;; `nbc-hex2dec', `nbc-hex2oct', `nbc-hex2bin'
;; `nbc-dec2hex', `nbc-dec2oct', `nbc-dec2bin'
;; `nbc-oct2hex', `nbc-oct2dec', `nbc-oct2bin'
;; `nbc-bin2hex', `nbc-bin2dec', `nbc-bin2oct'.
;;; Code:
(require 'calc-bin)
(defgroup num-base-converters nil
"Convert integers between different numeric bases."
:group 'nbc)
(defcustom nbc-define-aliases nil
"If non-nil, create aliases without prefix 'nbc' for the converters."
:type 'boolean
:group 'nbc)
(defun nbc-number-base-converter (num base-in base-out)
"Translate NUM, a string representing an integer, to a different base.
BASE-IN, an integer, is the basis of the input NUM.
BASE-OUT, an integer, is the basis to display NUM."
(interactive
(let ((num (read-string "Number: "))
(base-in (read-number "Base input: "))
(base-out (read-number "Base output: ")))
(list num base-in base-out)))
(unless (stringp num)
(signal 'wrong-type-argument (list 'stringp num)))
(unless (and (>= base-in 2) (<= base-in 36) (>= base-out 2) (<= base-out 36))
(error "Base `b' must satisfy 2 <= b <= 36: base-in `%d' base-out `%d'"
base-in base-out))
(let* ((case-fold-search nil)
(input (progn
;; Drop base info from NUMB.
(cond ((string-match "\\`\\(b\\|0x\\|o\\)\\(.+\\)\\'" num)
(setq num (match-string 2 num)))
((string-match "\\`#\\(b\\|x\\|o\\|[0-9]+r\\)\\(.+\\)\\'" num)
(setq num (match-string 2 num))))
(condition-case nil
;; Translate to canonical syntaxis: #(base)r(number).
(read (format "#%dr%s" base-in num))
(invalid-read-syntax
(error "Wrong input: `%s' for base `%s'" num base-in))))))
(condition-case nil
(let* ((regexp "\\`\\([0-9]*#\\)?\\(.+\\'\\)")
(calc-number-radix base-out)
(output (math-format-radix input)))
(when (string-match regexp output) ; Drop base info from OUTPUT.
(setq output (match-string-no-properties 2 output)))
(message "%s base %s = %s base %s" num base-in output base-out)
output)
(wrong-type-argument
(error "Wrong input: `%s' for base `%s'" num base-in)))))
;;; Add translatros for the most common basis: decimal, hexadecimal,
;; octal and binary.
(eval-when-compile
(defmacro nbc--create-converters-1 ()
(let ((bases (list "hex" "dec" "oct" "bin"))
forms)
(dolist (base-out bases)
(dolist (base-in bases)
(if (equal base-out base-in)
nil
(push `(nbc--create-command ,base-in ,base-out) forms))))
`(progn ,@forms)))
(defmacro nbc--create-command (base-in base-out)
(let* ((input-fn
(lambda (x)
(pcase x
(`"hex" (cons "hexadecimal" 16))
(`"dec" (cons "decimal" 10))
(`"oct" (cons "octal" 8))
(`"bin" (cons "binary" 2)))))
(in-lst (funcall input-fn base-in))
(base-in-name (car in-lst))
(out-lst (funcall input-fn base-out))
(func-name (format "nbc-%s2%s" base-in
(substring (car out-lst) 0 3)))
(prefix-len (length "nbc-"))
(docstring (format "Translate NUM, a string, from %s to %s."
(car in-lst) (car out-lst)))
(ispec (format "sNumber in %s: " (car in-lst))))
`(progn
(when (bound-and-true-p nbc-define-aliases)
(defalias (intern ,(substring func-name prefix-len))
(intern ,func-name)))
(defun ,(intern func-name) ,(list 'num)
,docstring (interactive ,ispec)
(let ((res (nbc-number-base-converter
num ,(cdr in-lst) ,(cdr out-lst))))
(message "%s %s = %s %s"
num ,base-in-name res ,(car out-lst))
res))))))
(defun nbc--create-converters ()
"Create converters between the bases 2, 8, 10 and 16."
(nbc--create-converters-1))
(nbc--create-converters)
(provide 'num-base-converters)
;;; num-base-converters.el ends here
--8<-----------------------------cut here---------------end--------------->8---
next reply other threads:[~2017-08-14 17:26 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-08-14 17:26 Tino Calancha [this message]
2017-08-14 17:48 ` New library num-base-converters Ted Zlatanov
2017-08-15 2:11 ` raman
2017-08-15 2:50 ` Tino Calancha
2017-08-15 5:25 ` Drew Adams
2017-08-15 6:35 ` Tino Calancha
2017-08-15 7:55 ` Stefan Monnier
2017-08-15 9:49 ` Tino Calancha
2017-08-15 15:24 ` Stefan Monnier
2017-08-15 13:40 ` Ted Zlatanov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=alpine.DEB.2.20.1708150225001.30658@calancha-pc \
--to=tino.calancha@gmail.com \
--cc=emacs-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).