## Seven-Segment Devices

### February 27, 2018

We have today a simple exercise from Jon Bentley’s book Programming Pearls, Chapter 3, Problem 8:

[S. C. Johnson] Seven-segment devices provide an inexpensive display of the ten decimal digits:

``` -----           -----   -----           -----   -----   -----   -----   -----
|     |       |       |       | |     | |       |             | |     | |     |
|     |       |       |       | |     | |       |             | |     | |     |
|     |       |       |       | |     | |       |             | |     | |     |
-----   -----   -----   -----   -----           -----   -----
|     |       | |             |       |       | |     |       | |     |       |
|     |       | |             |       |       | |     |       | |     |       |
|     |       | |             |       |       | |     |       | |     |       |
-----           -----   -----           -----   -----           -----   -----```

The seven segments are usually numbered as:

``` --2--
|     |
3     4
|     |
--1--
|     |
5     6
|     |
--0--```

Write a program that displays a 16-bit positive integer in five seven-segment digits. The output is an array of five bytes; bit i of byte j is one if and only if the ith segment of digit j should be on.

It was harder to type those digits than it looks.

Your task is to write a program to display numbers using seven-segment digits, as Bentley directs. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Pages: 1 2

### 10 Responses to “Seven-Segment Devices”

1. Simple perl one liner… first part converts the digits into a byte string {using tr}. The second just dumps it so it’s readable”

```perl -e '(\$_=shift @ARGV)=~tr/0123456789/}P7WZOoT\x7f_/;print qq(@{[unpack q(c*)]}\n);' 3141592654
```
2. ```
;; Let's have some fun with the format specfiers!

(defparameter *line-0* "~0@*~:[       ~; ----- ~]  ")
(defparameter *line-1* "~1@*~:[       ~; ----- ~]  ")
(defparameter *line-2* "~2@*~:[       ~; ----- ~]  ")
(defparameter *line-3* "~3@*~:[ ~;|~]     ")
(defparameter *line-4* "~4@*~:[ ~;|~]  ")
(defparameter *line-5* "~5@*~:[ ~;|~]     ")
(defparameter *line-6* "~6@*~:[ ~;|~]  ")

(defun format-digit-line (digit-count &rest segment-formats)
(let ((*print-circle* nil))
(format nil "~~~A:{~{~A~}~~:}~~%" digit-count segment-formats)))

(defconstant +digit-count+ 5)

(defparameter *segment-display* (concatenate 'string
"~@*" (format-digit-line +digit-count+ *line-2*)
"~@*" (format-digit-line +digit-count+ *line-3* *line-4*)
"~@*" (format-digit-line +digit-count+ *line-3* *line-4*)
"~@*" (format-digit-line +digit-count+ *line-3* *line-4*)
"~@*" (format-digit-line +digit-count+ *line-1*)
"~@*" (format-digit-line +digit-count+ *line-5* *line-6*)
"~@*" (format-digit-line +digit-count+ *line-5* *line-6*)
"~@*" (format-digit-line +digit-count+ *line-5* *line-6*)
"~@*" (format-digit-line +digit-count+ *line-0*)))

(defun test/digits ()
(let ((digit-count 9))
(loop :for format-spec :across (vector
(format-digit-line digit-count *line-2*)
(format-digit-line digit-count *line-3* *line-4*)
(format-digit-line digit-count *line-1*)
(format-digit-line digit-count *line-5* *line-6*)
(format-digit-line digit-count *line-0*))
:do (format t format-spec
'((nil nil nil nil nil nil nil)
(t   nil nil nil nil nil nil)
(nil t   nil nil nil nil nil)
(nil nil t   nil nil nil nil)
(nil nil nil t   nil nil nil)
(nil nil nil nil t   nil nil)
(nil nil nil nil nil t   nil)
(nil nil nil nil nil nil t  )
(t   t   t   t   t   t   t  ))))))

(defun segments (digit)
(case digit
(0   '(t nil t      t t      t t))
(1   '(nil nil nil  nil t    nil t))
(2   '(t t t        nil t    t nil))
(3   '(t t t        nil t    nil t))
(4   '(nil t nil    t t      nil t))
(5   '(t t t        t nil    nil t))
(6   '(t t t        t nil    t t))
(7   '(nil nil t    nil t    nil t))
(8   '(t t t        t t      t t))
(9   '(t t t        t t      nil t))
(#xa '(nil t t      t t      t t))
(#xb '(t t nil      t nil    t t))
(#xc '(t t nil      nil nil  t nil))
(#xd '(t t nil      nil t    t t))
(#xe '(t t t        t nil    t nil))
(#xf '(nil t t      t nil    t nil))))

(defun print-segments (n &key (base 10.))
(check-type base (integer 2 16))
(assert (<= n (1- (expt base +digit-count+)))
(n) "n must be between 0 and ~A" (1- (expt base +digit-count+)))
(let ((segments (nreverse (loop :repeat +digit-count+
:for (rest digit) := (multiple-value-list (truncate n base))
:then (multiple-value-list (truncate rest base))
:collect (segments digit)))))
(format t *segment-display* segments) (terpri)))

#|

cl-user> (print-segments 45067 :base 10)
-----    -----    -----    -----
|     |  |        |     |  |              |
|     |  |        |     |  |              |
|     |  |        |     |  |              |
-----    -----             -----
|        |  |     |  |     |        |
|        |  |     |  |     |        |
|        |  |     |  |     |        |
-----    -----    -----

nil
cl-user> (print-segments #xb00b5 :base 16)
-----    -----             -----
|        |     |  |     |  |        |
|        |     |  |     |  |        |
|        |     |  |     |  |        |
-----                      -----    -----
|     |  |     |  |     |  |     |        |
|     |  |     |  |     |  |     |        |
|     |  |     |  |     |  |     |        |
-----    -----    -----    -----    -----

nil
cl-user>

|#

```
3. matthew said

C++ solution:

```#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int scanline(int i, int n) {
if (i == 0) return (n >> 1) & 2;
else if (i == 1) return ((n >> 3) & 1) | (n & 2) | ((n >> 2) & 4);
else return ((n >> 5) & 1) | ((n << 1) & 2) | ((n >> 4) & 4);
}

int main(int argc, char *argv[]) {
if (argc <= 1) abort();
char buff;
sprintf(buff, "%lu", strtoul(argv,0,0));
int s[] = { 125,80,55,87,90,79,111,84,127,95 };
for (int i = 0; i < 3; i++) {
for (char *p = buff; *p; p++) {
int n = scanline(i, s[*p - '0']);
const char *c = "|_|";
for (int i = 0; i < (int)strlen(c); i++) {
printf("%c", (n & (1 << i)) ? c[i] : ' ');
}
}
printf("\n");
}
}
```
```\$ ./segment 1234567890
_  _     _  _  _  _  _  _
| _| _||_||_ |_   ||_||_|| |
||_  _|  | _||_|  ||_| _||_|
```
4. matthew said

Let’s try again with that sample output (at least in my browser, the last row of segments is missing):

```\$ ./segment 1234567890
_  _     _  _  _  _  _  _
| _| _||_||_ |_   ||_||_|| |
||_  _|  | _||_|  ||_| _||_|

```
5. In Python

```import argparse

def print_digit(digit):
# From digit 0-9 to bits
TRANSLATE = {
0: '1011111',
1: '0000101',
2: '1110110',
3: '1110101',
4: '0101101',
5: '1111001',
6: '1111011',
7: '0010101',
8: '1111111',
9: '1111101',
}

#  --2--
# |     |
# 3     4
# |     |
#  --1--      0  -
# |     |     3 | |
# 5     6     6  -
# |     |     9 | |
#  --0--     12  -

# Draw each of the segments
TEMPLATE = {
0: (12 + 1, '━'),
1: (6 + 1, '━'),
2: (0 + 1, '━'),
3: (3 + 0, '┃'),
4: (3 + 2, '┃'),
5: (9 + 0, '┃'),
6: (9 + 2, '┃'),
}

bits = TRANSLATE[digit]

print_digit = 15 * [' ']
segment_list = [bits[i] == '1' for i in range(7)]
for index, segment in enumerate(segment_list):
if segment:
position, character = TEMPLATE[index]
print_digit[position] = character

ROW_SIZE = 3
print_digit = [print_digit[i: i + ROW_SIZE] + [' ']
for i in range(0, len(print_digit), ROW_SIZE)]
return print_digit

def print_sequence(numbers):
digits = [print_digit(number) for number in numbers]

# Compose the proper lines
ROWS = 5
lines = [[] for _ in range(ROWS)]

for digit in digits:
for line in range(ROWS):
lines[line] += digit[line]

total = '\n'.join(''.join(line) for line in lines)
print(total)

def print_number(number):
numbers = [int(num) for num in str(number)]
print_sequence(numbers)

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Print a number')
help='A number to be printed')
args = parser.parse_args()

for number in args.numbers:
print_number(number)
```
```\$ python3 seven_segments.py 129485676 45
━   ━       ━   ━   ━   ━   ━
┃   ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃   ┃     ┃ ┃
━   ━   ━   ━   ━   ━       ━
┃ ┃     ┃   ┃ ┃ ┃   ┃ ┃ ┃   ┃ ┃ ┃
━   ━       ━   ━   ━       ━
━
┃ ┃ ┃
━   ━
┃   ┃
━
```
6. Daniel said

Here’s a solution in C. calc_segments returns a 5-byte array with segment encodings. print_segments prints the segment encodings.

```/* segments.c */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_DIGITS 5

#define FAIL_WITH_USAGE()                          \
do {                                             \
fprintf(stderr, "Usage: segments <UINT16>\n"); \
return EXIT_FAILURE;                           \
} while (0)

// Maps each digit to a number encoding segments comprising
// the digit. The rightmost bit in the 7-bit encoding corresponds
// to segment 0.
static uint8_t segment_lookup[] = {
0b1111101, // 0: 0,2,3,4,5,6
0b1010000, // 1: 4,6
0b0110111, // 2: 0,1,2,4,5
0b1010111, // 3: 0,1,2,4,6
0b1011010, // 4: 1,3,4,6
0b1001111, // 5: 0,1,2,3,6
0b1101111, // 6: 0,1,2,3,5,6
0b1010100, // 7: 2,4,6
0b1111111, // 8: 0,1,2,3,4,5,6
0b1011111, // 9: 0,1,2,3,4,6
};

void calc_segments(uint16_t n, uint8_t* segments) {
memset(segments, 0, NUM_DIGITS);
for (size_t i = 0; i < NUM_DIGITS; ++i) {
uint8_t digit = n % 10;
segments[NUM_DIGITS - i - 1] = segment_lookup[digit];
n /= 10;
}
}

void print_segments(uint8_t* segments) {
uint8_t positions = {
{8,2,8},
{3,1,4},
{5,0,6}
};
for (size_t row = 0; row < 3; ++row) {
for (size_t i = 0; i < NUM_DIGITS; ++i) {
uint8_t digit_segments = segments[i];
for (size_t col = 0; col < 3; ++col) {
uint8_t segment = positions[row][col];
char c = ' ';
if ((digit_segments >> segment) & 1) {
c = segment > 2 ? '|' : '_';
}
putchar(c);
}
}
putchar('\n');
}
}

int main(int argc, char* argv[]) {
if (argc != 2) FAIL_WITH_USAGE();
char* n_str = argv;
size_t len = strlen(n_str);
if (len == 0 || len > NUM_DIGITS) FAIL_WITH_USAGE();
char* endptr = NULL;
long n_long = strtol(n_str, &endptr, 10);
if (*endptr != '\0') FAIL_WITH_USAGE();
if (n_long < 0 || n_long > UINT16_MAX) FAIL_WITH_USAGE();
uint16_t n = (uint16_t)n_long;
uint8_t segments[NUM_DIGITS];
calc_segments(n, segments);
print_segments(segments);
return EXIT_SUCCESS;
}
```

Example Usage:

```\$ ./segments 12345
_  _     _
| _| _||_||_
||_  _|  | _|
\$ ./segments 6789
_  _  _  _  _
| ||_   ||_||_|
|_||_|  ||_| _|
```
7. Globules said

Goofing around with Unicode operators in a Haskell solution.

```{-# OPTIONS_GHC -fno-warn-type-defaults #-}

import Data.Bits (setBit, zeroBits)
import Data.Bool (bool)
import Data.List (foldl', unfoldr, union)
import Data.Tuple (swap)
import Data.Word (Word8)

-- Each of the following functions specifies one or more bits to set.  Except
-- for 0, each digit in the list of digits (further down) is defined by the
-- application of three of these functions to the empty list.  The functions are
-- comprised of three Unicode box drawing symbols.  If you align them vertically
-- the three functions will look like the digit being specified.
--
-- Zero is a bit unfortunate in that it's defined by just a pair of
-- functions. To define its middle row we would have had to have used "┃ ┃", but
-- this would be two binary operators without an intervening value, which is a
-- syntax error.

(╺━┓), (╺━┛), (╺━┫), (┃),   (┏━╸),
(┏━┓), (┏━┛), (┗━╸), (┗━┓), (┗━┛),
(┗━┫), (┣━┓), (┣━┫), (╹),   (╻)    :: (Num a, Eq a) => [a] -> [a] -> [a]
(╺━┓) = u [      2,    4      ]
(╺━┛) = u [0,                6]
(╺━┫) = u [   1,       4,    6]
(┃)   = u [            4,    6]
(┏━╸) = u [      2, 3         ]
(┏━┓) = u [      2, 3, 4      ]
(┏━┛) = u [   1,       4, 5   ]
(┗━╸) = u [0,             5   ]
(┗━┓) = u [   1,    3,       6]
(┗━┛) = u [0,             5, 6]
(┗━┫) = u [   1,    3, 4,    6]
(┣━┓) = u [   1,    3,    5, 6]
(┣━┫) = u [   1,    3, 4, 5, 6]
(╹)   = u [                  6]
(╻)   = u [         3         ]

u :: Eq a => [a] -> [a] -> [a] -> [a]
u xs ys zs = xs `union` ys `union` zs

-- A visually lighter weight synonym for the empty list.
o :: [a]
o = []

digits :: [[Int]]
digits = [o  ┏━┓
o  ┗━┛  o,
o    ┃
o    ┃  o,
o  ╺━┓
o  ┏━┛
o  ┗━╸  o,
o  ╺━┓
o  ╺━┫
o  ╺━┛  o,
o  ╻
o  ┗━┫
o    ╹  o,
o  ┏━╸
o  ┗━┓
o  ╺━┛  o,
o  ┏━╸
o  ┣━┓
o  ┗━┛  o,
o  ╺━┓
o    ┃
o    ╹  o,
o  ┏━┓
o  ┣━┫
o  ┗━┛  o,
o  ┏━┓
o  ┗━┫
o  ╺━┛  o]

-- A series of 8-bit words, each of which corresponds to one digit of the
-- argument.  Within each word the i'th bit is set if and only if the i'th
-- segment of a 7-segment display would be set.
bentley :: Integral a => a -> [Word8]
bentley = map oneDigit . reverse . base10
where oneDigit = foldl' setBit zeroBits . (digits !!) . fromIntegral

-- The list of a number's base-10 digits, from least to most significant.
base10 :: Integral a => a -> [a]
base10 0 = 
base10 n = unfoldr (\i -> bool Nothing (Just . swap \$ i `quotRem` 10) (i /= 0)) n

main :: IO ()
main = do
print \$ bentley 3141592654
print \$ bentley 78
```
```\$ ./sevenseg
[87,80,90,80,79,95,55,111,79,90]
[84,127]
```
8. Globules said

Ah well, it looks like character widths are a bit different in the browser, compared to emacs and the terminal. You’ll have to pretend that the vertical sequence of digits in the code lines up nicely…

9. Rahul said

num=’0123456789′;
if index==0:
layer_1.append(sym)
elif index in range(1,4):
layer_2.append(sym)
else:
layer_3.append(sym)
layer_1=[]
layer_2=[]
layer_3=[]
NUMBERS = {0:’1101111′, 1:’0001001′,2:’1011110′,3:’1011011′, 4:’0111001′, 5:’1110011′,6:’1110111′,7:’1001001′,8:’1111111′, 9:’1111011′}
ONE={0:’‘, 1:’|’,2:’‘,3:’|’, 4:’|’,5:’_’,6:’|’}
[add_symbols(i,” “) if a==’0′ else add_symbols(i,ONE.get(i)) for number in num for i, a in enumerate(NUMBERS.get(int(number))) ]
print(“”.join([ ” “+i+” ” for i in layer_1]) ,end=”” )
print(”)
print(“”.join([i for index,i in enumerate(layer_2)]) ,end=”” )
print(”)
print(“”.join([ i for i in layer_3]),end=””)

_ _ _ _ _ _ _ _
| | | | _||||_ |_ |||||
|| || | | _||| ||_| _|

10. Kevin said

Here’s a solution in racket:

```;; given a number 0 <= n <= 65535, turn it into a list of 5 digits (pad w/ 0 as needed)

(define (number->digits num)
(map (lambda (c) (- (char->integer c) 48))
(string->list (~a num #:width 5 #:align 'right #:pad-string "0"))))

;; given a number 0 <= n <= 65535, generate a list of 5 bytes w/ bits set for segment activation

(define BYTES '(1111101 1010000 0110111 1010111 1011010
1001111 1101111 1010100 1111111 1011111))

(define (7seg-bytes num)
(let loop ((dgts (number->digits num)))
(if (empty? dgts)
null
(cons (list-ref BYTES (car dgts)) (loop (cdr dgts))))))

;; given a number 0 <= n <= 65535, show the number in a 5-digit 7-segment display

(define PTNS '("       "
"|      "
"      |"
"|     |"
" ----- "))

(define SEGS '((4 3 3 3 0 3 3 3 4)
(0 2 2 2 0 2 2 2 0)
(4 2 2 2 4 1 1 1 4)
(4 2 2 2 4 2 2 2 4)
(0 3 3 3 4 2 2 2 0)
(4 1 1 1 4 2 2 2 4)
(4 1 1 1 4 3 3 3 4)
(4 2 2 2 0 2 2 2 0)
(4 3 3 3 4 3 3 3 4)
(4 3 3 3 4 2 2 2 4)))

(define (7seg-display num)
(let ((digits (number->digits num)))
(let loop ((segments "") (i 0) (j 0))
(if (> i 8)
(display segments)
(let* ((pattern (list-ref PTNS (list-ref (list-ref SEGS (list-ref digits j)) i)))
(closer (if (< j 4) #\space #\newline))
(update (format "~a~a~a" segments pattern closer)))
(if (= j 4)

;; tests

;(number->digits 0) ==> '(0 0 0 0 0)
;(number->digits 28) ==> '(0 0 0 2 8)
;(number->digits 65535) ==> '(6 5 5 3 5)

;(7seg-bytes 8863) ==> '(1111101 1111111 1111111 1101111 1010111)

;(7seg-display 8863) ==>
-----   -----   -----   -----   -----
|     | |     | |     | |             |
|     | |     | |     | |             |
|     | |     | |     | |             |
-----   -----   -----   -----
|     | |     | |     | |     |       |
|     | |     | |     | |     |       |
|     | |     | |     | |     |       |
-----   -----   -----   -----   -----
```