Paranoia is a complex program with a simple structure – a single sequence of computations with results displayed at each step. ParaPy breaks the in-line form of ParaBas into dozens of independent functions, but the flow of execution is the same.
The structure of ParaPy and the clean form of Python make this code as readable as any pseudocode, but this section presents a small number of Python's features you might find helpful. The focus is on what matters for Paranoia, not the juicy details of Python exploited to its fullest.
If you want to learn more about Python, Dive Into Python 3 offers a wonderful tour of the language. Or try the official tutorial for a more thorough treatment.
Python is untyped. Variables arise from use rather than
by declaration. Python supports integers of arbitrary
size, but ParaPy uses floating
point values for its tests. If you're using any conventional
computer (or cloud-based server) from the past four decades,
you're likely to have IEEE Standard 754 64-bit
double
values. You can find
intimate details of IEEE 754 arithmetic
in the
Arithmetic room.
Python supports the boolean values True
and False
, and an intuitive definition
of truthiness as anything remotely nonzero.
Strings in ParaPy usually
appear in "double quotes"
, but may
also be written with 'single quotes'
.
The hash sign #
not embedded in a string
introduces a comment
that runs to the end of the line.
Python also supports docstrings, triple-quoted comments you'll find as the first line(s) of most functions.
def print_if_err_cnt_positive():
'''Print an error message from a global count.'''
A function can take any number of parameters and return and number of values. This is another example from ParaPy, including the one-line docstring.
def compute_H_and_inverse():
"""Return min(1/B, 1/2) and reciprocal, noting H is a fraction."""
The return mechanism is coming right up.
Python's most remarkable feature is its approach to punctuation.
Indented lines of code are blocks, usually introduced with a
:
character. Here is the body of
compute_H_and_inverse()
from the last section.
It is indented one level in (typically 4 spaces) from the def
.
if B < TWO:
h_inv = TWO
else:
h_inv = B
Locally, the indentation is very readable. When the plot thickens and you find yourself several levels deep in the logic, it takes a steady eye, or a plumb line, to keep oriented. ParaPy reaches its deepest nesting in the examination of underflow, which may not surprise readers who have been down this path before.
Part of Python's expressiveness is its
ability to manipulate multiple values at a time.
The final line of
compute_H_and_inverse()
is the return
of H
and its reciprocal:
return ONE / h_inv, h_inv
You can guess that the invocation assigns two results.
H, ONE_OVER_H = compute_H_and_inverse()
Python tuples are defined as values grouped by parentheses, but the Pythonic way is to relax the requirement for the punctuation when the context is obvious. Tuples are likewise useful in loops, where two or more values are updated at each pass.
Basic on the original IBM Personal Computer was limited by
its flat structure and its limited namespace for variables.
All variables were global, of the form
A
or X3
, making the glossary
at the end of ParaBas essential reading.
Reuse of some variables in different sections of the code
could complicate things for the reader.
ParaPy uses Python global variables sparingly, always in a way parallel to ParaBas. One class of globals, the flags and counters, are defined and initialized at the beginning of the code.
The other globals are constants, distinguished by their all-caps
names. They are assigned once, then used as needed. Two exceptions,
radix B
and PRECISION
, are double-checked
and are subject to update.
When a global variable is assigned within a function it must
be declared global
, otherwise a local variable
is created.
For example, the pause()
function accesses two
globals.
def pause():
"""Wait until the user hits RETURN."""
global milestone, page_num
ParaPy generally follows the
Google style guide.
As just noted, constant variables like
ONE_HALF
are upper case.
All other variables and all functions are lower case
with underscore dividers, like
underflow_threshold
.
The Pythonic approach is to support
one way, not four, to accomplish a task.
Python supports while
loops, whose condition is tested
at the top of the loop. ParaBas employs the
alternative
do...while
structure heavily, in which the condition is
tested at the end of the loop.
ParaPy effects this structure with a mild
deviation from the style guide.
Bringing the loop break
statement up alongside the
if
test improves readability.
y = ONE
while True:
b = big + y
y = y + y
b = b - big
if b != ZERO: break
ParaPy has a simple structure
that fits in a single file (module), so there's no need
for elaboration. At the top of the code, it imports
the math
library, giving access to handy
functions like math.sqrt
and
math.pow
.
The curious line
from __future__ import division # use // for integer division
is a bit of Python custom around the /
operator
as it applies to integers. We'll do nothing subtle with integer
division.
A Python list is an ordered sequence of values. In ParaPy, we sometimes iterate over a list, as in this case of small odd primes:
for z in [3, 5, 7, 11, 13, 17, 19, 23]:
A Python dictionary is a set of key-value pairs. ParaPy has just one dictionary, a global set of test results. This is the start of its defiinition.
flags = {
"mult_guard_digit": False,
"div_guard_digit": False,
The construct flags["mult_guard_digit"]
accesses
the dictionary element.
If you have formatted floating point numbers in another language, Python will come as no surprise. This is a typical usage.
print("\tdiffers from z * 1 = {:0.17e}".format(r1))
Format requests in {...}
are fulfilled in
sequence from the arguments to format
.
As in C, \t
skips to the next
tab stop, and \n
skips to the next line.
The .17e
requests 17 significant digits
in the exponential format.