Assignment 1

416 Distributed Systems: Assignment 1 [BigUInt]

Due: January 17 at 6pm PST

Winter 2020

In this assignment you will get started with programming in the Go language. To solve this assignment you will need to install Go, figure out how to compile, run, and debug a Go program, and implement a few key pieces in a partial BigUInt codebase that we will give to you.

Overview

Go (or golang, as it helps to search for it) is an imperative programming language generally aimed at the development of distributed systems. In some ways, it is related to systems languages like C and Rust, in that programs are built using structs and functions (and like Rust, while it supports "methods", they are basically just special functions). In other ways it is more similar to managed languages like C# and Java, in that, while it does not have a VM, it has a lot of "managed" features: it has a garbage collector, full runtime type information, and the design strives to have essentially no undefined behaviour.

This assignment's objective in particular is to help you get to grips with Go's basic features. You will learn:

A BigUInt is a simplified version of a BigInt, just like the one that ships in Go's standard library, or the default int implementation in Python, or the equivalent data structure available in most programming languages. At a high level, the goal of a Big(U)Int is to store unbounded-precision integers. In many programming languages, like C, C++, Java, and Go, the built-in integer types can only hold number represented by their bit width, which will often be 32 or 64. While this may be good enough for many situations, sometimes a truly unbounded number is needed, or, in Python's case, an unbounded number is provided by default for usability's sake.

This is the BigInt: it represents a number a variable-length sequence of machine ints, requiring any int-supported operations to be implemented as array manipulations. BigUInt is, like the uint type in Go or unsigned int in C, an unsigned variant of BigInt that does not need to support negative numbers, making implementation a little simpler.

BigUInt

This assignment's specific variant of BigUInt is already pre-defined for you by us: it is a struct containing a reference to a slice of unsigned bytes. This means that your task is to implement a pair of unsigned integer operations on slices of unsigned bytes: addition, subtraction, and deep copying.

In addition to Go's extensive documentation, of which we recommend the tour for language features, and How to Write Go Code for the package and testing concepts, the provided assignment code aims to be an example of how to lay out a Go project.

We provide you with three files: (1) biguint.go, (2) biguint_test.go, (3) go.mod.

The file biguint.go has some code that helps to figure things out. This file includes the bytesFromUInt64 method, which unpacks a 64-bit unsigned integer into a slice of bytes, and (*BigUInt).String(), which is a standard stringification method for BigUInt instances.

We also provide some testing infrastructure in biguint_test.go to help guide you in testing this first assignment, and to give you a base to work from when building your own tests in subsequent assignments.

The go.mod file is a module definition, whose full implications are discussed in Using Go Modules. Generally speaking, the file defines the root directory of a collection of Go packages, a canonical name by which one can import those packages, and any dependency information. In this case, the canonical name is example.org/cpsc416/a2, rooted at example.org due to this being a course assignment. There is no additional dependency information, except go 1.14, which indicates the version of the Go toolchain that initialized the module, and defines the provided code's Go language compatibility.

You should only modify the code in biguint.go. As a starting point, make sure you can run the provided tests, which should fail.

BigUInt API


The following API calls below are defined and we also provide their implementations:

  • type BigUInt struct {
      data []uint8
    }
    

    Our BigUInt type definition, containing a slice of unsigned bytes. Unsigned ints should be split up into 2 digit base 16 chunks, indexed from least to most significant, e.g., []uint8{ 0x00, 0xFF } represents 0xFF00. This is also an example of a slice type.

  • var ErrUnderflow = errors.New("arithmetic underflow")
    

    This is the underflow error for subtraction. Go's error handling is minimalistic by design, and can be confusing when coming from languages that have more heavyweight error handling support. Some general guidance is available on the basic idioms of Go error handling. Additionally, Go 1.13 and above adds the ability to standard-ly nest one error inside another, which may be useful in larger projects, where one kind of error might cause another.

    In this assignment, you just have to return this error in the correct situation.

  • func NewBigUInt(i uint64) *BigUInt
    

    The constructor for a BigUInt, based on a uint64. This function relies on bytesFromUInt64, which takes an unsigned 64-bit integer and converts it into an array of bytes, following the scheme for this assignment (least to most significant bytes). Note that the resulting slice and therefore the underlying bytes of the BigUInt do not include any leading zeroes, stopping at the most significant non-zero byte.

  • func (x *BigUInt) Bytes() []uint8
    

    Provides access to the raw bytes underlying a given BigUInt.

  • func (x *BigUInt) String() string
    

    This function demonstrates some more slice-based code. It generates a string representing x, under the following scheme:

    • digits should be printed in base 16, with lowercase letters
    • groups of 8 digits should be separated by underscores
    • no leading zeroes should be printed
    • the string should be prefixed with "0x"

    See the golang's printf-style string formatting manual.


The three API calls below are defined for you, but you have to provide the implementation:

  • func (x *BigUInt) Copy() *BigUInt
    

    Generates an exact and fully independent (deep) copy of a given BigUInt. Note that the copy should not share any memory with x.

  • func (x *BigUInt) Add(y *BigUInt) *BigUInt
    

    The Add method increases x by the number represented by y, and should return a pointer to x. Note that x's slice's size may increase as a result of this operation. More specifically, x's slice should have the minimum length necessary to represent the resulting sum.

  • func (x *BigUInt) Subtract(y *BigUInt) (*BigUInt, error)
    

    The Subtract method decreases x by the number represented by y, and returns a pointer to x. Note that x's slice's size may decrease as a result of this operation. If y > x, then Subtract should return (nil, ErrUnderflow), and x should be left unchanged (this may take some care). As with Add above, x's slice should have the minimum length necessary to represent the resulting number.

Testing

A large amount of testing code is provided in biguint_test.go. You do not need to modify this file for this assignment, but keep in mind that you will need to make your own tests in future assignments.

Assumptions you can make

  • For this assignment, no assumptions, other than basic Go functionality and any stated API semantics, are allowed.
  • You can assume that inputs to the BigUInt API will never be nil.

Assumptions you cannot make

  • Any bounds, aside from free memory (do not worry about memory allocation failure), on the size a BigUInt can reach.
  • We will run only the provided tests against your code. (We have more tests)

Implementation requirements

  • The client code must be runnable on CS ugrad server machines and be compatible with Go version 1.15.6.
  • Your code must be compatible with the given API, and run successfully against the provided test cases.
  • Your solution can only use standard library Go packages.
  • Your solution code must be Gofmt'd using gofmt.

Starter code

You can download the zipped starter code here.

Note that you cannot change the API. Our marking scripts will rely on this API to automatically grade your solution.

Handin instructions

Keep the provided file layout at the top-level in your solution. You may include additional files, but we do not expect this will be necessary. Do not reference any additional libraries in your solution.

More instructions about submitting using a git repository via https://github.students.cs.ubc.ca/ will be posted here and on Piazza.

Rough grading scheme

Your code must not change the API above. Your code must work on ugrad servers (e.g., remote.students.cs.ubc.ca).

If any of these are violated, your mark for this assignment is 0. This is true regardless of how many characters had to be changed to make your solution compile, and regardless of how well your solution works with a different API or on a different machine.
For a list of server machines you can use, please see this page. Note that the right version of Go (version 1.15.6) is installed in /cs/local/bin/go.

The high-level A1 mark breakdown looks like this:

  • 5% : Copy works
  • 35% : Add works
  • 60% : Subtract works

Advice

  • Start simple, and don't worry about edge cases until the main control flow works. If you're stuck on how to implement addition and subtraction, review long-form addition and subtraction and implement them as literally as you can. It's easy to forget the specifics and edge cases of the method, since it's so much easier to take shortcuts or rely on calculators most of the time.
  • Compile and run your code on the ugrad servers.

Make sure to follow the course collaboration policy and refer to the submission instructions that detail how to submit your solution.