Numerical I/O
Systems in Rust
Announcements
- Scaffolding assignment for
ix
- Big numbers are hard to type, think of, reason about, etc.
- Hard to check your work.
Homework
ix
beckons- Due Friday, 24 Oct. at 1440 ET.
- You will probably need to finish this to do that.
ix
vs. eg.i32
,i64
, can store infinite values, and your job is to make it.
Today
Struct
- Before anything else, have to find a way to make an
ix
.- I am granting you full freedom to implement
ix
as you see fit with one exception: - You must implement it yourself, not use an existing “BigNum” or “BigInt” crate, like [this])(https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html)
- I am granting you full freedom to implement
- Personally, I just used a sign boolean and a vector of unsigned values. You are welcome to take this approach:
- The real merits of showing this are:
- You can use
#![allow(non_camel_case_types)]
to avoid the annoying warning for a lower case name.- Naming a data structure in lower case is poor form (consider
String
vs.&str
) but… - I was out of ideas.
- Naming a data structure in lower case is poor form (consider
- You can use
- The structure is public, so can be used from a testing
src/main.rs
. - The internal fields are not public, so you can make any changes you like within
src/lib.rs
.
Test it
- You can test the same way we tested
f16
:
- The first
f16
is the crate name, the second is the type name.
Aside
- For my money,
f16::f16
looks terrible.
Aside
- Rust admonished you for using
f16
instead ofF16
.
warning: type `f16` should have an upper camel case name
--> src/lib.rs:1:12
|
1 | pub struct f16 {
| ^^^ help: convert the identifier to upper camel case (notice the capitalization): `F16`
|
= note: `#[warn(non_camel_case_types)]` on by default
- Everyone is a critic!
#![allow(non_camel_case_types)]
Aside
- You can read more on
struct
here: Rust Book 05.01
Input/Output
- Versus built-in types, we’ll just provide a ways to get values in and out.
- Vs.
f16
, this won’t be easy in the sense that we are explicitly trying to work with values that do not fit into any existing numerical type. - For example, I will provide the following function, but it isn’t particularly helpful.
- The real motivation for todays lab is to use a testing framework.
- I am providing mine, which you are not required to use but that I quite like.
- Values are input as command line arguments as hex strings, with a
0x
prefix.- I didn’t check if I can input negative values, content to create negatives via subtraction rather than define a standard.
- Values are output to
stdout
as hex values with no0x
prefix but with a sign.- This was so I could read them directly with Python
int()
for testing.
- This was so I could read them directly with Python
- Operations are specified using a capitalized, 3 letter abbreviation describing one of the four supported operations:
ADD, SUB, MUL, DIV, REM
- which I want to reiterate is four operations.- This should say “pattern matching” to you.
- Values are input as command line arguments as hex strings, with a
- I’ll present the components of the testing framework, and your task is to convince yourself you understand them.
src/main.rs
- Main:
- Uses the
ix
implementation fromsrc/lib.rs
- Reads command line arguments generated by
tester.py
- Converts two command line strings representing numerical hex vlaues into
ix
instances. - Captures the final command line argument to determine which operation to perform.
- Calls the appropriate
ix
arithmetic function and captures its return value.- I call these
<op>_ix
forop
inadd, sub, mul, div, rem
- I call these
- Prints to
stdout
the returnedix
as a hex string.
- Uses the
- While intended to be used by
tester.py
, it can also be used manually!
$ cargo run 0x10 0x20 ADD
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/bignum 0x10 0x20 ADD`
0000000000000030
- I do not add a newline after this value.
- You can, and probably should want to, make your own
src/main.rs
that does these things, but you are not required to do so. - I am providing mine as a “spoiler marked” HTML
<details>
block.
My src/main.rs
src/main.rs
use bignum::*;
fn main() {
let args: Vec<String> = std::env::args().collect();
let a = h2i_ix(&args[1]);
let b = h2i_ix(&args[2]);
match args[3].as_str() {
"ADD" => see_ix(&add_ix(&a, &b)),
"SUB" => see_ix(&sub_ix(&a, &b)),
"MUL" => todo!(),
"DIV" => todo!(),
"REM" => todo!(),
&_ => println!("Operator not recognized: choose from ADD, SUB, MUL, DIV, REM"),
}
}
tester.py
- Tester:
- Using Python’s built-in integers, generates two large random numbers, I generated mine to be between 500 and 512 bits.
- Convert them to hex strings.
- For each of the debatebly four or five operations, the tester:
- Uses
subprocess
to dispatch cargo to run with three command line arguments: the two hex strings and the operation (in all caps). - Captures the output of the process.
- Computes the same operation using Python arithmetic operations.
- Uses naive string comparison to compare the two values.
- Optionally raises a debug message in the event of a difference.
- Uses
- To support tester usage, I will provide:
- The
tester.py
code - An example run on a partial
ix
implementation, with only addition and subtraction completed.
- The
- I am spoiler marking both, in case you want to make your own.
My tester.py
tester.py
DEBUG = 0
CMD = "cargo run --"
import subprocess, os, random
from operator import add, sub, mul, floordiv as quo, mod as rem
bigone, bigtwo = random.randint(2 ** 500, 2 ** 512), random.randint(2 ** 500, 2 ** 512)
hexone, hextwo = hex(bigone), hex(bigtwo)
DEBUG and print("\nhexone =\n", hexone, "\nhextwo = \n", hextwo)
from operator import add, sub, mul, floordiv as quo, mod as rem
ops = {'ADD':add,'SUB':sub,'MUL':mul,'QUO':quo,'REM':rem}
for op in ops:
result = int(subprocess.check_output(["cargo", "run", hexone, hextwo, op]),16)
answer = ops[op](bigone,bigtwo)
if result != answer:
print("Operator", op, "failed.")
DEBUG and print("Expected:")
DEBUG and print(hex(answer))
DEBUG and print("Received:")
DEBUG and print(hex(result))
exit()
else:
print(op, "passes.")
Example output
- Note the panic caused by Rust
todo!()
and the separate, Pythonsubprocess.CalledProcessError
after the failure propagates back to the calling script.
$ python3 tester.py
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/bignum 0x2c2e4c34f428560aedbee82a9a7ca5a7071ef9d9b3b23834ef1ce63be90e052e94d1411de3c1191fdb1ebfd39fde41bbfc8b95e3faeae64a0fe21d50b9ce53d8 0x58010ed08d2415a81a17e369a3e00443d922d0219dd66c1b74473511140e1f4f24450840e9e1a7c4bd4cac368d30a4ba5aa075fb65ec92a714c7f73b42122bb0 ADD`
ADD passes.
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/bignum 0x2c2e4c34f428560aedbee82a9a7ca5a7071ef9d9b3b23834ef1ce63be90e052e94d1411de3c1191fdb1ebfd39fde41bbfc8b95e3faeae64a0fe21d50b9ce53d8 0x58010ed08d2415a81a17e369a3e00443d922d0219dd66c1b74473511140e1f4f24450840e9e1a7c4bd4cac368d30a4ba5aa075fb65ec92a714c7f73b42122bb0 SUB`
SUB passes.
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/bignum 0x2c2e4c34f428560aedbee82a9a7ca5a7071ef9d9b3b23834ef1ce63be90e052e94d1411de3c1191fdb1ebfd39fde41bbfc8b95e3faeae64a0fe21d50b9ce53d8 0x58010ed08d2415a81a17e369a3e00443d922d0219dd66c1b74473511140e1f4f24450840e9e1a7c4bd4cac368d30a4ba5aa075fb65ec92a714c7f73b42122bb0 MUL`
thread 'main' panicked at src/main.rs:10:18:
not yet implemented
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Traceback (most recent call last):
File "/home/user/tmp/ix/tester.py", line 19, in <module>
result = int(subprocess.check_output(["cargo", "run", hexone, hextwo, op]),16)
File "/usr/lib/python3.10/subprocess.py", line 421, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/usr/lib/python3.10/subprocess.py", line 526, in run
raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['cargo', 'run', '0x2c2e4c34f428560aedbee82a9a7ca5a7071ef9d9b3b23834ef1ce63be90e052e94d1411de3c1191fdb1ebfd39fde41bbfc8b95e3faeae64a0fe21d50b9ce53d8', '0x58010ed08d2415a81a17e369a3e00443d922d0219dd66c1b74473511140e1f4f24450840e9e1a7c4bd4cac368d30a4ba5aa075fb65ec92a714c7f73b42122bb0', 'MUL']' returned non-zero exit status 101.
Next Steps
- Implement addition and subtraction (you will likely want to do both at the same time) and test them.
- For a hint, I wrote the following helper functions:
// Helpers: Add/sub magnitudes (absolute values) of two numbers.
// "aug" and "add" are short for "augend" and "addend"
fn add_mag(aug_vals: &Vec<u64>, add_vals: &Vec<u64>) -> Vec<u64>
// "min" and "sub" are short for "minuend" and "subtrahend"
fn sub_mag(min_vals: &Vec<u64>, sub_vals: &Vec<u64>) -> Vec<u64>
// Compute the "greater than or equal" between two values.
fn gte_mag(a_vals: &Vec<u64>, b_vals: &Vec<u64>) -> bool
- I am also providing my entire
sub_ix
, again spoiler marked.