Lab 1

Get the code

Use the curl command to download the code you’ll need for the lab.

$ curl https://www.phy.olemiss.edu/~kbeach/phys540/src/lab1.tgz -O
$ tar xzf lab1.tgz
$ ls lab1
cpp/        julia/      python/     rust/
$ ls lab1/cpp
binomial.cpp  makefile      maxint.cpp
$ cd lab1/cpp

The instructions here focus on C++, but you may carry out the comparable steps in Julia, Python, or Rust. We’ll be exploring all of these languages in this course.

The file ending in .tgz is an archived and compressed directory of files (often called a tarball and sometimes bearing the suffix .tar.gz). The last command above moves you into the cpp directory that is nested inside the lab1 directory.

Hello World!

Start by creating a file hello.cpp that contains a “Hello World!” program. See the C++ code listing below. Which text editor you use is a matter of taste. The editors vim, nano, and emacs -nw are good options that operate within the terminal window. Although I like vim for some tasks, most students will likely prefer a modern editor with a windowing interface, such as Atom, Brackets, Sublime Text, TextMate; a full-fledged IDE, such as XCode or Visual Studio Code; or a cloud-based notebook in the style of IPhython or Jupyter.

lab1/cpp/hello.cpp
#include <iostream>
using std::cout;
using std::endl;
int main()
{
   char a = 'k';
   cout << "He" << ++a << "lo World!" << endl;
   return 0;
}

Compile it with g++ (or clang++) and run the resulting executable. You should be able to reproduce the following terminal session:

$ g++ -o hello hello.cpp
$ ls -F
hello*    hello.cpp
$ ./hello
Hello World!
lab1/julia/hello.jl
a = 'k'
a = a+1
println("He",a,"lo World!")
lab1/python/hello.py
a = 'k'
a = chr(ord(a)+1)
print("He"+a+"lo World!")
lab1/rust/hello.rs
fn main() {
    let mut a = 'k';
    a = ((a as u8) + 1) as char;
    println!("He{}lo World!",a);
}

All four programs produce identical output.

$ cd ../julia
$ julia hello.jl
Hello World!
$ cd ../python
$ python3 hello.py
Hello World!
$ cd ../rust
$ rustc hello.rs
$ ./hello
Hello World!

Look carefully at the program listings and take note of how the languages differ in their treatment of the character type and in their approach to formatting strings/streams for display on the screen. C++ and Julia both treat the character as an integer type and quietly cast between ordinals and characters as needed. Python and Rust both require some extra work to effect the conversion. For Rust, in particular, this is a matter of language philosophy. It is a very strongly typed language, and all type conversions are explicit.

Each of these languages has its own look and feel, but there are family resemblances. C++ and Rust are traditional C-style “brace” languages, with all code blocks enclosed in matching braces and each command terminated by a semicolon. Layout is free-form, and extra white space is ignored. (More precisely, any number of spaces, tabs, carriage returns in sequence is equivalent to a single space.) The instructions to be executed are enclosed in a function named main.

Julia and Python each have the cleaner, more minimalistic feel of a scripting language. Semicolons are not needed as a separator. These languages assume one command per line. And braces aren’t used to organize the code. Instead, Python groups any commands that sit at a common level of indentation; Julia uses keyword … end blocks, in the style of ALGOL and Pascal.

Overflow

Compile and run the program maxint. It generates the first six powers of two, starting from the zeroth.

$ make maxint
g++ -o maxint maxint.cpp -O -ansi -pedantic -Wall
$ ./maxint
2^0 = 1
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 16
2^5 = 32

Extend the program to determine the largest int that can be represented on your machine. Are there differences in behavior between the C++, Julia, Python, and Rust implementations?

Be choosy

Included in the lab1 directory is a program that computes the (“\(\mathsf{m}\) choose \(\mathsf{k}\)”) binomial coefficient

\[\mathsf{{m \choose k} = \frac{n!}{k!(n-k)!}}.\]

The naive implementation

unsigned long int binomial(unsigned long int n, unsigned long int k)
{
   return factorial(n)/factorial(k)/factorial(n-k);
}

seems to work fine for the combinatorics of a small number of items

$ make binomial
$ g++ -o binomial binomial.cpp -O -ansi -pedantic -Wall
$ ./binomial 8
(8 choose 0) = 1
(8 choose 1) = 8
(8 choose 2) = 28
(8 choose 3) = 56
(8 choose 4) = 70
(8 choose 5) = 56
(8 choose 6) = 28
(8 choose 7) = 8
(8 choose 8) = 1

but it fails for larger numbers

$ ./binomial 24 | head -n 10
(24 choose 0) = 1
(24 choose 1) = 1
(24 choose 2) = 0
(24 choose 3) = 0
(24 choose 4) = 0
(24 choose 5) = 0
(24 choose 6) = 2
(24 choose 7) = 5
(24 choose 8) = 12
(24 choose 9) = 22

Convince yourself that this calculational error is caused by integer overflow. Note that Python circumvents this by handling arbitrarily large numbers in software (rather than using fixed-bit-width integers in hardware, trading speed for flexibility).

Write a smart implementation of the binomial coefficient in any or all of C++, Julia, Python, and Rust based on the identity

\[\mathsf{{m \choose k}} = \mathsf{\frac{1\cdot 2\cdots n} {(1\cdot 2 \cdots p)(1\cdot 2 \cdots q)}} = \mathsf{\frac{(p+1)(p+2)\cdots n}{1\cdot 2\cdots q}},\]

where \(\mathsf{p} = \mathsf{\textsf{max}(k,n-k)}\) and \(\mathsf{q} = \mathsf{\textsf{min}(k,n-k)}\). You should be able to reproduce the following terminal session.

$ ./binomial 24 | head -n 10
(24 choose 0) = 1
(24 choose 1) = 24
(24 choose 2) = 276
(24 choose 3) = 2024
(24 choose 4) = 10626
(24 choose 5) = 42504
(24 choose 6) = 134596
(24 choose 7) = 346104
(24 choose 8) = 735471
(24 choose 9) = 1307504

Think about why this works to avoid overflow. Be sure that you can offer a careful and convincing explanation.