A task completed using a computer will generally involve at least three models: one for the data that describes the task; one for the user actions that can be carried out on that data; and one that establishes the cultural context in which it is carried out. The data model determines what is physically possible through the selection of parts and their relationships, the same way that different vehicles (car, bicycle or truck) emerge from choices of frame, axles, tires, power train, and power source. The data model determines, to a great extent, the affordances of the resulting vehicle (how much it can carry, how you steer it), and those affordances establish an interface between the mechanism and the user. Finally, the user’s goals and the vehicle’s affordances interact with the cultural context—the rules of the road—to further establish whether and how the task can be accomplished.
We don’t have to be a car designer or a mechanic to drive, but we do need to know where the tires go, how to add fuel, and when to lubricate it; we learn how to steer, brake, use a manual transmission, parallel park, and change a tire, as well as how fast to go and how to signal a turn. We understand the fundamental components and their relationships to each other. For cars, that produces a certain mechanical literacy; for computers it is often called “computational thinking” and is the subject of this chapter. Because many users learn to compute in a largely task-oriented mode, even experienced and capable users may have incomplete understanding of the system they use, so the focus here is on the low-level model, the mechanics of the system.
A computer is a general-purpose machine for processing information, but its heart is something very much like a simple calculator—enter a couple of numbers, pick a math operation, and it’ll tell you a result. To add a list of numbers written down on a pad of paper, you must combine the basic computations with other information (the list) and actions (reading the list, keeping track of your place in the list, knowing when you’re done) in a sequence, forming an algorithm. Expressed in code the computer can understand, an algorithm becomes a computer program.
Some points to note about even this simple example: (1) neither the initial list of numbers, nor the pattern of operations (“enter, add, repeat”) reside in the calculator itself, they reside on the pad of paper or in the user’s memory; (2) the user will need to make decisions along the way (e.g., “What’s the next number?” “Are we done yet?”); and (3) if you need the final total later, you had better copy it down onto your piece of paper at the end! In the example, the calculator functions as the central processing unit (CPU). The pad of paper and the steps carried out by the human operator are also part of the system. In a real computer the role of the pad of paper is played by memory or disk storage and the central processor or CPU includes hardware to interpret and follow the instructions that make up the algorithm, retrieving each data item in turn and adding it to the intermediate result. In fact, another name for the CPU is “arithmetic and logical unit.”
The other important quality this example illustrates is the duality of data and algorithm. Both are needed, and they are interdependent. By combining data and algorithms, computers do amazing things all by themselves, but most of the time they interact with humans (in fact, our smartphones are more about interaction than computation). Their screens can be used to display words, numbers, photographs, and drawings, and their speakers can play sounds. Keyboards and mice let us input information. They are very reliable, fast, and accurate. They don’t get bored or tired. They are great partners but they do not make intuitive leaps or get ideas, though they may juxtapose two pieces of information that cause a human to do so.
The different programs you run on your computer control its information-processing behavior, allowing it to switch from being a web browser or CAD program to being a spreadsheet or video game. This characteristic of acting like or presenting the behavior of something else is referred to as virtuality. It’s as if all of our kitchen appliances (stove, fridge, sink) were replaced with one piece of hardware that delivers each of these functions when requested.
The computer itself consists of sources of input (including mice, keyboards, cameras, disk drives, and network interfaces, as well as cell phone radios, accelerometers and temperature sensors), the CPU for manipulating the information, short-term memory and long-term disk storage for the information, and a means of communicating information to the outside world (including a display screen, speakers, printer, and network interfaces). The CPU does the actual computation, depending on the instructions in the programs. Information (data) and instructions (software) are both stored in memory during use, where they are quickly accessible by the CPU. The main memory is fairly expensive and volatile (it forgets everything if power is lost), so it is supplemented with non-volatile secondary storage that does not require electricity to retain stored information. Until quite recently rotating magnetic surfaces were used for this, using various mechanisms ranging from cheap low-capacity “floppy” disks to fast high-capacity “hard drives.” More recently flash memory thumb drives and solid-state disks made from computer chips have begun to take over this role, though they continue to be referred to using the old vocabulary, and play the same role in the machine.
When the machine is first turned on, main memory is empty except for a simple program that is actually built into a small area of memory called read-only memory (ROM). On power-up the hardware automatically accesses and runs the instructions in ROM. In a process called booting (derived from the phrase “to pull yourself up by your bootstraps”) this program retrieves the operating system software from the secondary storage and loads it into memory for active use. The operating system is the software bureaucracy with which you interact and against which all your other applications/programs execute.
When you start up (launch) a program the operating system copies (loads) its instructions from the secondary storage into an unused area of memory and the machine begins executing those instructions. The program may, in turn, copy a data file into other unused memory (opening and reading the file), connect to another computer over a network in order to retrieve a file, or begin creating a new file. Because it is a relatively slow process, changes are usually not recorded to the hard disk until you explicitly save your work, so program failures (crashes) may obliterate whatever work you have done since the last save. When the program is done it gives control of the machine back to the operating system, which recycles that memory for the next task.
In digital computers, everything is represented using bits, shorthand for “binary digits.” Each bit can take one of two states or values. In the early years (1950s to 1970s), when memory was literally magnetic, bits were actually stored as N or S magnetism by pulsing a current through an iron ring. Similarly, punch cards and paper tape either had a hole or no hole in a particular spot. In modern systems bits take the form of high or low voltages sustained in tiny circuits etched by the millions onto chips. Whatever the physical mechanism, each bit is unambiguously one value or the other. There is no “in between” value. Reflecting this, we usually refer to the individual values with dichotomies like “True/False,” “on/off,” or “1/0.”
As the above list of dichotomies suggests, a bit doesn’t actually mean anything by itself, but can be used to store one of two values. The meaning arises from the context in which they are used. That is, they must be interpreted, a little like a secret code. This explains why you usually need the same program that created a file in order to change the file. The exceptions arise when the code is widely known, allowing different programs to manipulate the data in the file, as is the case with a plain text file.
Since individual bits store so little info, most of the time we use bits in groups. An 8-bit group is called a byte. (Humorous fact: a 4-bit group is called a nibble.) The range of meaning possible in a group depends on the number of bits used together. Figure 4.1 uses groups of four circles to illustrate the 16 unique on/off patterns a nibble may have. If you look at it carefully, you’ll see that the pattern of filled circles also follows a familiar pattern—starting at the right side, count from 0 to 1; when you run out of digits carry over 1 to the next place (bit) to the left. Repeat. This is counting with binary numbers. It works just like counting with decimal numbers, except binary numbers have a 1s place, a 2s place, a 4s place, and an 8s place, each allowed to hold only a 0 or a 1, rather than the more familiar 1s place, 10s place and so on with digits 0 to 9. Arithmetic doesn’t change, just the representation: Look carefully and you’ll find that 2 + 5 = 7 here too.
Unfortunately, while more bits can store more information, programs are usually built with fixed-size assumptions about the most demanding situation they will encounter—the “biggest number,” “number of colors,” “maximum number of rooms,” etc. These numbers reflect the underlying use of bits to store the information (16 values with 4 bits, 256 with 8, 16 million with 24 bits, etc.). The progression includes 210 = 1024, which is so close to 1000 that the pseudo-metric notation of a KB (kilobyte) is often used for measuring file size, transfer speeds, etc. Some hardware limits reveal their binary nature through phrases like “32-bit color” displays, “10-bit analogue to digital conversion,” etc. If the programmer of your social networking site only allocated 8 bits to store your age, you can’t get older than 255! At this time this isn’t a problem, but it is worth noting that Microsoft built the original MS-DOS operating system such that the amount of memory a program could talk to directly was only 640 KB and it became a major headache as hardware grew more capable. Today’s laptops have about 10,000 times this amount of RAM.
Computer memory is usually accessed, and contents moved or updated, in 8-bit bytes or in multiples of 4 or 8 bytes (32 or 64 bits) at a time. These bigger groupings, called words, allow the machine to count to some pretty big numbers, but they’re still limited and literal. Humans might accept the notion that the ratio 1/x approaches something called infinity as x approaches zero, but computers will crash if presented with such information (it’s called overflow, meaning the number is too big to be represented with the bits assigned to it) because the concept of infinity is an abstraction. It doesn’t have a representation in bits.
Unlike human memory, which appears to be associative in nature (your memories of the feel of sand, the shape of beach-balls, and the sound of sea gulls are probably all linked together by connections between your brain’s neurons), all varieties of computer storage are organized as sequential storage locations. However, since program instructions and data are all mixed up in memory, it is designed so that any random location can be accessed in the same amount of time, giving rise to the name random access memory (RAM). Each word in RAM has a unique numeric address that identifies it. Numbers or text stored sequentially in time (as when typing) are often found sequentially in memory. If we know how to find the first character in a sentence, the odds are the second character is stored in the next byte. This implicit sequentiality is consistent with the way text appears on the printed page or with manipulating a list of values. It’s also one of the keys to writing computer viruses (malware) since adjacent areas of memory may be occupied by instructions or data with equal ease and utility.
However, there is no certainty that related information will be stored in adjacent locations. For example, two lines of a CAD drawing that occur in the area we might call the “northwest corner of the kitchen,” maybe even touching each other, might be stored anywhere in a multi-megabyte file.
Computer programming occurs in many forms. In each, it consists of a flexible means of organizing elemental computational actions into sequences or procedures that accomplish information-processing tasks. Because machines take their instructions literally and don’t have the opportunity to develop an understanding of your wishes through conversation, this requires careful reasoning about the task and adherence to some language (textual or graphical) of expression during programming. Aside from the (always frustrating) challenges of syntax mastery, programming tools also require us to construct representations, manipulate them in consistent ways, and develop a workable fit between the user (even if it’s ourselves) and the information available.
Ivan Sutherland’s Sketchpad program (1963) allowed a designer to draw and edit simple graphics. In this sense it was a precursor to today’s CAD systems, but Sketchpad also allowed the designer to create persistent relationships between parts (e.g., end-point matching and equidistant distribution of points around a circle). These features are rarely present in CAD editors today, though modern scripting languages and constraint-based modeling such as that found in some building information modeling (BIM) software begin to reproduce this functionality. When present, the result is an “executable design” that may respond to complex physical, social, aesthetic, or environmental factors and adjust the building footprint, or the façade, or just the ceiling of the lobby, with no added effort.
Though full-featured programing languages can be very complicated, and individual programs can be hundreds of thousands of lines long, the elemental actions used to construct a program are simple and fairly limited. Each program is like a recipe. If you collect the right ingredients (data) and carry out the steps in sequence, you’ll get the desired result. The data we select to store and manipulate will constitute the data model for the program; the affordances we create for acting on that data will become the interaction model.
A more detailed discussion of basic data types is provided in the next chapter, but for now what matters is that different pieces of data are converted to patterns of bits using different encoding rules, each creating a different type of data, with names like Boolean (for true/false information), integer (for counting numbers), floating point (for decimal numbers), and character or string (for text). Because their encoding rules are different, different types are generally handled separately, though there may be ways of converting between them. Combinations of these basic, or primitive, data types can be used to represent more complex data, such as coordinates in space (three floating point numbers), pixels in a photograph (red/green/blue values for each pixel), music samples, etc.
Ultimately, everything gets turned into a number and then a pattern of bits, even text (every character has a numerical equivalent), sounds (volume, by frequency), and colors (intensity of primary colors). This means that complex concepts, especially those involving relationships among parts that cannot be directly represented as numbers, such as beauty or symmetry, are difficult to compute about.
Programmers build more complex structures from these simple elements. For instance, every “album” of music on your computer likely has a title (text), copyright holder (text), year (integer), artist (text), and a list of cuts (each with title (text) and duration (float)). And, of course, there’s the music. These more complex structures of data are sometimes called “objects” or “classes”—a nomenclature that spills over into CAD systems via certain programming languages, as well as the Industry Foundation Classes (IFC) used for standardizing architecture, engineering, and construction (AEC) data exchange.
Computer programs may have both information that varies (variables) and information that remains constant (e.g. the value of pi). If you wish to convert someone’s weight in pounds to their weight in kilograms, start with the weight (that’s a variable), and divide that by 2.20462 pounds/kilogram (that’s a constant). It is common to give variables names like weight_in_pounds so you can refer to them later. Constants can have names too, or you can use numbers directly in a computation.
The following paragraphs will introduce a number of programming concepts. Since most programming is done using text, the examples will be expressed using text, but in order to make them easier to read, they are not written in any actual programming language; they are in pseudo-code.
In our “pounds to kilos” converter we need to perform a computation to create a new value. Let’s call it weight_in_kilos. We compute it this way:
weight_in_kilos = weight_in_pounds / conversion_factor
This is both a statement of truth such as you might encounter in algebra, and a statement of process (“divide the weight_in_pounds by the conversion_factor and store the results in weight_in_kilos”). In programming languages it is the process that matters. It is called an “assignment statement” because it stores or assigns a value to the variable on the left side.
Note that if we had ignored the units in our variable names, we might have written:
weight = weight / conversion_factor
The statement would be correct in most computer languages. As an algebraic statement of truth it makes no sense unless the conversion_factor is 1.0, but as a statement of process it still yields the correct results. You begin with weight (measured in pounds) and you end up with weight (measured in kilograms). They aren’t the same number; the weight in pounds is destroyed when the assignment happens.
If this seems a little confusing, you aren’t alone. To resolve this symbolic confusion some programming languages use separate symbols to distinguish between assignment and the more traditional “is the same as” or “is equal to” algebraic reading. For example, JavaScript, Java, PHP and C all use “=” for assignment and “==” for “is the same value as.”
Computing recipes (algorithms) usually consist of multiple steps. Sequencing is implied by the order in which the steps are listed in the source text file (if it is text) or some sort of dependency graph (in the case of spreadsheets and some visual programming languages). Often, steps involve doing simple arithmetic on variables, but sometimes we need to compute more complex values like the trigonometric sine of an angle, a computation that takes several steps itself. Languages usually have a library of such well-defined operations, or functions, each of which has a name that can be used along with one or more parameters in a statement, like x = sin(angle) where sin is the function and angle is the parameter. You may be more familiar with function names from cooking, such as dice, chop, whip, simmer, and brown. As with programming recipes, chop can be done to onions, carrots, potatoes, etc. The recipe tells us what to chop via the parameter, as in chop(onions).
Functions allow us to condense the text of our program, making it easier to write and keep track of in our heads. In the end we might express a simple soup recipe as
soup = simmer(chop(onions) + chop(carrots) + broth)
Where sequencing is not obvious, most languages use parentheses to imply “do this first.” In this case, you simmer a combination of chopped onions, chopped carrots, and some broth, and call the result soup.
Two of the most powerful affordances of a programming language are the ability to define your own unique data and your own functions. Returning to cooking for a moment, we might note that it isn’t uncommon to both peel and chop. What if your library doesn’t have a function for that? Must we always remember to write down both, in sequence? Couldn’t we just say peel_n_chop(stuff)? In fact, in much the same way that CAD software allows you to define a “block” (of data) by naming a collection of graphic primitives, we can use a “function declaration” to define a new operation. It takes the form of a vocabulary declaration that defines the list of steps needed to accomplish the action, but it doesn’t actually do the task:
define peel_n_chop(stuff)
peeled_stuff = peel(stuff)
chopped_stuff = chop(peeled_stuff)
return(chopped_stuff)
end
Notice how “stuff” is used. In the first line it is part of the definition, indicating that our peel_n_chop function will have a single input parameter, stuff. We then use stuff anywhere in the function definition where that information should go. Whatever information (or vegetable) we eventually insert for “stuff” when we use our function will be passed sequentially through a peeling and a chopping process, and then returned to the spot where peel_n_chop was used. With this definition in place we could write:
gespacho = peel_n_chop(tomatoes) + chop(jalapenos) + peel_n_chop(onions)
By building up and combining a sophisticated vocabulary of operations, acting on a complex data description, our functions can be made very powerful, but we need a few more tools, chief among them being conditional execution.
We don’t always do every step in a recipe. For instance, a recipe for cooking a roast might say: “If the meat is frozen, allow it to thaw in the refrigerator overnight.” Obviously, if it is not frozen, you don’t need to do this, but if it is frozen, maybe there is a way to thaw it faster? So, maybe the recipe could say: “If the meat is frozen, allow it to thaw in the refrigerator for a day or thaw for 10 minutes in a microwave on low power.” Again, if it is not frozen, you skip over all of this; but if it is frozen, the instructions give you two alternatives but no explicit means of selecting between them except the implied “if you don’t have a day to spend thawing the meat.” So there are actually two conditions: (1) thawed or frozen; and (2) with time to thaw in the fridge or not. The second only applies if the answer to the first is “frozen.” This is called a “nested if” condition. We might write it as
If (meat is frozen) then
if (there is enough time) then
thaw it in the fridge
else
thaw it in the microwave
endif
endif
Conditional execution, or if–then, statements generally involve some sort of test (in parentheses in the example above) that produces a true or false result and some way of indicating the steps that get skipped or executed. As shown in the inner if–then–else statement above, they may indicate both what happens when the test is true and what to do when it is false (else). The endif marks the end of the conditional. Regardless of the conditional test, the next step in the recipe is the one after the corresponding endif.
The flip side of conditional execution is repeated execution. What if our language for cooking does not have chop in it but it does have slice? We could define our own chop function as we did above for peel_n_chop, by using slice repeatedly. One way to do this would be:
define chop (stuff)
slices = slice ¼ inch off the end of stuff
slices = slices + slice ¼ inch off the end of stuff
slices = slices + slice ¼ inch off the end of stuff
slices = slices + slice ¼ inch off the end of stuff
slices = slices + slice ¼ inch off the end of stuff
return (slices)
end
This is awkward because it is inefficient to type, hard to update, and worst of all, it is not clear how many slices we need to chop all of stuff! We would like the function to work for both carrots and potatoes. In fact, we can express this more succinctly and more powerfully as:
define chop (stuff)
while(length of stuff > ½ inch)
slices = slices + slice ¼ inch off the end of stuff
endwhile
return (slices)
end
Programming languages usually have multiple ways of saying this (variously referred to using names like do loop, for loop, or while loop). They are similar to a function in that they define a group of one or more actions that will be repeated. Simple loops just repeat an action some number of times, while others include conditional execution tests made at the beginning or the end to decide whether to do the actions again.
The fragments of pseudo-code presented here are meant to convey the linear, sequential character of programming, but aren’t written in any specific language. While the underlying nature of program execution remains the same, there are other ways of writing the instructions. The Logo programming environment included a mechanical “turtle.” The turtle was part robot, part mouse. Users (usually children) could move the turtle through a pattern of turn-and-advance operations that were captured and then replayed in loops. With a pen attached, the turtle could even draw intricate patterns.
Similarly, many applications allow you to record a series of normal manipulations, such as searches or edits, and then replay them as “macros” or “scripts,” a feature which is very similar to the idea of a function described here.
In recent years, visual programming extensions of 3D modeling programs have become popular (e.g., Grasshopper, Generative Components, and Dynamo). In these systems, computational steps are represented by rectangles, with input values on one side and output on the other. Lines connecting the boxes indicate how data flows from one operation to the next, implicitly dictating the sequence of operations. The graphical presentation of such environments has proven very attractive to the visual design community.
Data, combinations of data, sequences of operations, functions, loops, and conditional execution—that’s pretty much the basics of constructing a computer program. The obvious gap between this simple description and the sophistication of most tools we use is indicative of complex logic, thousands of man-hours, hundreds-of-thousands of lines of code, and complex system libraries behind such seemingly simple operations as drawing on the screen or receiving mouse input. Still, all that complexity boils down to these fairly simple operations and representations.
Sequences, functions, loops, and conditionals describe the actions or verbs of computer programs. The nouns are found in the patterns of bits stored in memory. Certain standard representations have emerged over the years and are shared by most computers. Standard representations are essential to the sharing of data between computers produced by different manufacturers and between different programs within a single computer, and they are the fundamental elements out of which more complex representations are built. They make it easier for programmers to move from job to job and for program source code to be ported between differing systems. The representations simply define how to store numbers and text, but there are important subdivisions within those as well.
The existing standards are the product of government and industry experts working together over a period of time. New representations rarely appear as new standards right away. Intellectual property rights and proprietary commercial interests protect them. However, where a single corporate entity dominates a market, such representations often become de facto standards over time as other software developers license or reverse engineer their behavior. Examples include PDF (by Adobe) and DWG (by Autodesk).
The most direct representation possible in memory is that of a counting number, where each bit in memory represents a 0 or 1, and a collection of bits represents a multi-digit number. The resulting binary numbers have a 1s place, 2s place, 4s place, etc. (each time you add a bit you double the number of unique ways you can configure the collection of bits). An 8-bit byte can store 256 patterns (2 raised to the 8th power), such as the counting numbers in the range of 0 to 255. Two bytes (16 bits) get you from 0 to 65,535.
Negative numbers require a slightly different representation. The positive or negative sign of a number is a duality like true and false, so it is easy to see that if we dedicate one bit to remembering the sign of the number we can use the rest of the bits for normal counting. While preserving the number of unique representations, they are divided between the positive and negative portions of the number line. One such scheme in common use is two’s complement encoding under which an 8-bit byte can store a “signed” value between –128 and +127 or an “unsigned” value (always positive) between 0 and 255.
The positive and negative counting numbers are called integers. Such numbers have exact representations in computer memory. The range of values they can have depends on the amount of memory allocated to store each one.
A chicken counting her eggs could use an integer, but an astronomer figuring the distance to the nearest star needs a different representation because it takes too many digits to record astronomical distances even if they are measured in kilometers rather than inches. That’s why scientific or exponential notation was invented! Using this scheme the distance to Alpha Centauri can be written as 4.367 light years, and since a light year is 9.4605284 × 1015 meters, that makes a total of 4.13 × 1016 or 413 × 1014 meters after rounding off a little. Interestingly, that last way of writing it uses two much smaller integers (413 and 14), at the expense of rounding off or approximating the value. Using negative exponents we can also represent very small numbers. Because these kinds of numbers have a decimal point in them, they’re generally called floating point or real numbers. Some of the bits store the exponent and some store the significant digits of the numeric part (called the significand). In a 32-bit single-precision floating point number, 24 of the bits will be used for the significand, and 8 bits will be used for the exponent. That translates into around seven significant digits in a number ranging in value from –1038 to 1038. To do math with real numbers is a bit more complicated than doing math with integers, but dedicated hardware takes care of the details. Note that while they can represent very large or very small values, floating point numbers are necessarily approximate.
There is a difference between a number (e.g., 12) and a measurement (12 meters) that includes a unit of measure, because the standards for computer representation only cover the number. The number can represent a measurement in any unit of measure. The standards guarantee that numbers shared between programs represent the same values, but there is nothing in the standard representations that says what units the values are measured in. Unfortunately, the units of measure are often implicit in the software (CAD programs in the United States default to inches or sometimes feet), or left up to the user (as in a spreadsheet). In 1999 the simple failure to convert a measurement from imperial to metric (SI) units when sharing data within an international design team led to the loss of the multi-million dollar Mars Climate satellite (Hotz 1999).
Because units are usually implicit in CAD software, and exchange formats generally transfer numeric coordinates rather than unit-oriented dimensions, architects sometimes find that drawings exported by a consultant need to be scaled up or down by 12 (to convert between feet and inches), or 1000 (to convert between meters and millimeters), or by 2.54 (to get from inches to centimeters or back).
The important point about the range of a representation is that it cannot capture the full splendor of reality. If you establish a coordinate system for our solar system (centered on the sun) using single-precision real-number coordinates measured in inches, coordinates are precise to about eight digits. For points near the sun, that’s accurate to a tiny fraction of an inch, but for positions 93 million miles (5.9 × 1012 inches) away from the origin, say on the surface of the Earth, that means points 104 inches (833 feet, 40 meters) apart have indistinguishable coordinates. Not a big problem for architects who usually pick an origin closer to their projects, but a challenge for space scientists and molecular engineers. Larger memory allocations (64 bits per number rather than 32) reduce the problem, but it never completely goes away.
Integers are generally faster to compute with, but not useful for things like spatial coordinates, because we often need fractional coordinates, and we may need big coordinates.
The curious thing in this situation is that floating point numbers, which allow us the most precise-appearing representation (e.g., “3.1415926” for pi, or “1.0”) are using an internal representation that is an approximation to the value, while the numbers we associate with approximation (e.g., “37 feet” or “12 meters”) are actually exact representations (if you rounded off the measurement when you wrote it down, that approximation is on you—the internal representation of the number is exact).
By convention, integers generally appear without a decimal point (so “37.0” is a floating point number, while “37” is an integer), but this is not a requirement.
Humans write text in many ways—left-to-right, right-to-left, top-to-bottom—and we use many different symbol systems, some with a handful of unique symbols, like American English, and some with thousands, like Japanese Kanji. This complicates the creation and entry of text in ways that Western Europeans and their American cousins don’t always appreciate, but since American companies dominated computing in the 1980s we started out doing it their way. To simplify things and make it manageable, the early systems ignored all text except left-to-right, top-to-bottom. This reduced the complexity down to just two problems, analogous to typing: picking the individual characters and figuring out when to start a new line of output.
Remember, bytes store patterns of on/off, so they don’t actually store a text character. The link between the pattern and the meaning is one we create. It is arbitrary, but it can be rational. To simplify the problem, early developers ignored much punctuation and many letters. Given a list of acceptable characters, they created mappings from on/off patterns of bits in memory to the selected characters—very much like setting up a secret code. A few of these, from the American Standard Code for Information Interchange (ASCII, pronounced “ass-key”), are shown in Figure 4.1, where you might note that “A” and “a” are not the same character, and that interpreting the bits of “B” as an integer yields a larger number than “A”—a fact that helps with alphabetizing.
The appearance of the letters when printed depended entirely on the printer—there was no such thing as a font. Even worse, different companies (DEC, IBM, CDC) came up with different mappings, so an “!” (in EBCDIC) on an IBM mainframe had the same bit pattern as a “Z” (in ASCII) on a DEC mini-computer. This significantly complicated the process of copying files from one computer to another, but since much data stayed in one place it wasn’t a huge problem. When the PC revolution began and people wanted data on their PC that started out on their company mainframe computer, the incompatibilities between systems became both noticeable and painful.
The rise of the PC saw the demise of all the encoding schemes except ASCII. ASCII rigidly defines 128 characters, including both upper-case and lower-case letters, numerals, and a variety of punctuation, plus some characters like “form-feed” (to start a new page), “line-feed” (move down a line), and “carriage return” (move back to the start of the line).
ASCII uses an 8-bit byte to store each character, so there are 256 possible characters, but the standard only defined the first half of them. The eighth bit was originally set aside to provide a way to validate character transmission between systems, but robust hardware rendered that use unnecessary and vendors began to use the “upper 128” characters for other things. Different vendors (IBM, Microsoft, Apple, etc.) filled in the missing characters but not in the same way, so while ASCII is a standard, it is a flawed standard in practice. The flaws appear most obviously today when you use certain characters in email or blog posts, characters such as curly (or smart) quotes, accented characters (éøü), em-dash (—), etc. The vacuum of undefined space had some positive effects too; in the 1980s those upper 128 characters allowed researchers at the University of Hawai‘i to develop a script system for recording Pacific Northwest Native American languages and stories, helping preserve a language that was rapidly disappearing (Hsu 1985). But ASCII is no good if you want to use a writing system from the Middle East or the Far East. Those users literally could not write things down in a familiar and standardized script on a computer until unicode came along.
Unicode was created to address the shortcomings of ASCII and its various extensions. The goal was to provide a unique character encoding for every writing system in the world (Unicode 2016). Because memory and storage space have been scarce resources, and in order to make the new standard backwards compatible with ASCII, unicode actually comes in three slightly different variations: using 1 byte (utf-8, very similar to ASCII), 2 bytes (utf-16), or 4 bytes per character (utf-32). Individual characters, called code points, are assigned in language-oriented groups.
While unicode resolves the character representation problem, there is another problem with text files. The ASCII standard doesn’t specify how the end of a line is marked in a text file. With the development of word processors and automated word-wrap, this has morphed into a question about how the end of a paragraph is marked too. Unfortunately, three incompatible answers evolved in the industry:
Microsoft Windows uses CRLF. Unix (and thus OS-X) uses LF. The original Apple Macintosh used CR. If you have ever opened a text file to find it unaccountably double-spaced, or with some odd invisible character at the start of every line after the first one, or where each paragraph is one very long line or the entire file turns into one giant block paragraph, you’ve encountered the side-effects of this problem. HTML rather brilliantly dodges the whole mess by lumping all three into the general category of “white space” and treating them the same as a space character.
The name of each letter (e.g., “upper-case A”) is distinct from the graphic mark (or glyph) with which it is printed. A collection of glyphs is called a font. In the early days text was printed by striking a character image against an inked ribbon, making what we now call “plain text”—all characters were the same width (ten characters per inch); 80 characters filled a line; and there were six lines per inch vertically. All upper-case “A” characters looked the same, but their font might vary from terminal to terminal or printer to printer, depending on the manufacturer, not the computer or file. In the 1980s the appearance of raster output devices such as dot-matrix and laser printers changed all that, giving us proportionally spaced text of many sizes and appearances that flows into variably spaced lines and paragraphs.
Even today, font data largely remains separate from the character data, and because fonts can be protected by copyright, not all fonts are available on all computers, which means exchanging files (both text documents and web pages) between operating systems can cause unexpected font substitutions. When an alternative font is used, lines of text usually turn out to have different lengths. This causes the edges of fully justified paragraphs to not line up; shifts text on slides and in notes or dimensions in drawings and illustrations, possibly causing overlaps with other line-work or graphics, etc. Even though fonts may now be attached to web pages and can be embedded in PDF files, intellectual property restrictions on some copyrighted fonts remain a challenge.
The different virtual devices that your computer can become are encoded in the different programs it has stored on the secondary storage system—the hard drive—of your computer. Similarly, the different documents you might compose with your word processor are stored separately so you don’t have to complete one document before starting another. Instead, you save and open them as needed. Each document or application is assigned space in secondary storage called a file, and files are organized into directories or folders.
As a container, each file is standardized and cataloged by the operating system, but the contents of the file, the pattern of text and numbers called the file format, is specific to the program that created it, and those programs probably come from different companies. Thus, your word processor can’t make sense of your CAD data and vice versa. File extensions, suffixes to the file name such as .doc or .html, are commonly used to indicate which program goes with which file.
The one universal file format is plain text (usually ASCII). Most programs can read and write text files, though saving data to plain text may cause important features to be discarded. Chapter 8 explores the question of representation in more depth.
Patterns of bits stored in files represent text and numbers, as well as program instructions. Programming languages, with their data types, variables, branches, functions, and loops, give us the means by which to express and organize sequences of operations to do useful work, creating entire new functions and programs, or scripts to run inside existing programs. Standardized representations for text (ASCII and unicode) and numbers (integers and floating point numbers) provide portability, but only cover fairly simple data types. History and commercial interests have made more sophisticated data exchange more difficult, but network connectivity is encouraging the development of exchange standards. Chapter 5 will explore standards emerging in the AEC industry.
Sutherland, Ivan. 1963. SketchPad: A man–machine graphical communication system. AFIPS Conference Proceedings 23: 323–328.
Hotz, Robert Lee. 1999. Mars probe lost due to simple math error. Los Angeles Times, October 1, 1999.
Hsu, Robert. 1985. Lexware manual. Manoa: University of Hawai‘i Department of Linguistics.
Sutherland, Ivan. 1963. SketchPad: A man–machine graphical communication system. AFIPS Conference Proceedings 23: 323–328.
Unicode. 2016. What is unicode. www.unicode.org/standard/WhatIsUnicode.html