From 4956da0ee29028f0e5058611bdab1c43287cbde6 Mon Sep 17 00:00:00 2001 From: Angelica-Schell <47483301+Angelica-Schell@users.noreply.github.com> Date: Mon, 15 Jul 2024 08:58:53 -0400 Subject: [PATCH] Benchmarking (#2185) This pull request introduces a test suite using runt meant to compare the implementations of the two different implementations of binary to fixed. It currently uses a python script to write the tests and test the rust functions. --------- Co-authored-by: Adrian Sampson --- data-conversion/src/main.rs | 515 ++++++++++++++++++ .../scripts/binary_to_fixed_speed.py | 46 ++ .../scripts/binary_to_fixed_tests.py | 113 ++++ tools/data-conversion/src/main.rs | 186 +++++-- .../testsuite/binary_inputs.txt | 10 + .../testsuite/expect/test_1.expect | 1 + .../testsuite/expect/test_10.expect | 1 + .../testsuite/expect/test_2.expect | 1 + .../testsuite/expect/test_3.expect | 1 + .../testsuite/expect/test_4.expect | 1 + .../testsuite/expect/test_5.expect | 1 + .../testsuite/expect/test_6.expect | 1 + .../testsuite/expect/test_7.expect | 1 + .../testsuite/expect/test_8.expect | 1 + .../testsuite/expect/test_9.expect | 1 + .../testsuite/inputs/test_1.in | 1 + .../testsuite/inputs/test_10.in | 1 + .../testsuite/inputs/test_2.in | 1 + .../testsuite/inputs/test_3.in | 1 + .../testsuite/inputs/test_4.in | 1 + .../testsuite/inputs/test_5.in | 1 + .../testsuite/inputs/test_6.in | 1 + .../testsuite/inputs/test_7.in | 1 + .../testsuite/inputs/test_8.in | 1 + .../testsuite/inputs/test_9.in | 1 + tools/data-conversion/testsuite/runt.toml | 11 + tools/data-conversion/testsuite/test.txt | 4 + .../data-conversion/testsuite/testsuite.yaml | 93 ++++ 28 files changed, 952 insertions(+), 46 deletions(-) create mode 100644 data-conversion/src/main.rs create mode 100644 tools/data-conversion/scripts/binary_to_fixed_speed.py create mode 100644 tools/data-conversion/scripts/binary_to_fixed_tests.py create mode 100644 tools/data-conversion/testsuite/binary_inputs.txt create mode 100644 tools/data-conversion/testsuite/expect/test_1.expect create mode 100644 tools/data-conversion/testsuite/expect/test_10.expect create mode 100644 tools/data-conversion/testsuite/expect/test_2.expect create mode 100644 tools/data-conversion/testsuite/expect/test_3.expect create mode 100644 tools/data-conversion/testsuite/expect/test_4.expect create mode 100644 tools/data-conversion/testsuite/expect/test_5.expect create mode 100644 tools/data-conversion/testsuite/expect/test_6.expect create mode 100644 tools/data-conversion/testsuite/expect/test_7.expect create mode 100644 tools/data-conversion/testsuite/expect/test_8.expect create mode 100644 tools/data-conversion/testsuite/expect/test_9.expect create mode 100644 tools/data-conversion/testsuite/inputs/test_1.in create mode 100644 tools/data-conversion/testsuite/inputs/test_10.in create mode 100644 tools/data-conversion/testsuite/inputs/test_2.in create mode 100644 tools/data-conversion/testsuite/inputs/test_3.in create mode 100644 tools/data-conversion/testsuite/inputs/test_4.in create mode 100644 tools/data-conversion/testsuite/inputs/test_5.in create mode 100644 tools/data-conversion/testsuite/inputs/test_6.in create mode 100644 tools/data-conversion/testsuite/inputs/test_7.in create mode 100644 tools/data-conversion/testsuite/inputs/test_8.in create mode 100644 tools/data-conversion/testsuite/inputs/test_9.in create mode 100644 tools/data-conversion/testsuite/runt.toml create mode 100644 tools/data-conversion/testsuite/test.txt create mode 100644 tools/data-conversion/testsuite/testsuite.yaml diff --git a/data-conversion/src/main.rs b/data-conversion/src/main.rs new file mode 100644 index 0000000000..c62e2587ee --- /dev/null +++ b/data-conversion/src/main.rs @@ -0,0 +1,515 @@ +//use std::env; +use argh::FromArgs; +use std::error::Error; +use std::fmt; +use std::fs::read_to_string; +use std::fs::File; +use std::io::{self, Write}; +use std::str::FromStr; + +//cargo run -- --from $PATH1 --to $PATH2 --ftype "binary" --totype "hex" + +#[derive(Debug)] +struct ParseNumTypeError; + +impl fmt::Display for ParseNumTypeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid number type") + } +} + +impl Error for ParseNumTypeError {} + +#[derive(Debug, PartialEq, Clone, Copy)] // Add PartialEq derivation here - What is this? +enum NumType { + Binary, + Float, + Hex, + Fixed, +} + +impl ToString for NumType { + fn to_string(&self) -> String { + match self { + NumType::Binary => "binary".to_string(), + NumType::Float => "float".to_string(), + NumType::Hex => "hex".to_string(), + NumType::Fixed => "fixed".to_string(), + } + } +} + +impl FromStr for NumType { + type Err = ParseNumTypeError; + + fn from_str(input: &str) -> Result { + match input { + "binary" => Ok(NumType::Binary), + "float" => Ok(NumType::Float), + "hex" => Ok(NumType::Hex), + "fixed" => Ok(NumType::Fixed), + _ => Err(ParseNumTypeError), + } + } +} + +#[derive(FromArgs)] +/// get arguments to convert +struct Arguments { + /// file to convert from + #[argh(option)] + from: String, + + /// file to convery to + #[argh(option)] + to: String, + + /// type to convert from + #[argh(option)] + ftype: NumType, + + /// type to convert to + #[argh(option)] + totype: NumType, + + /// optional exponent for fixed_to_binary -> default is -1 + #[argh(option, default = "-1")] + exp: i32, + + /// optional for fixed_to_binary using bit slicing. If choosen, will use bit slicing. + #[argh(switch, short = 'b')] + bits: bool, +} + +fn main() { + let args: Arguments = argh::from_env(); + + convert( + &args.from, + &args.to, + args.ftype, + args.totype, + args.exp, + args.bits, + ); +} + +/// Converts [filepath_get] from type [convert_from] to type +/// [convert_to] in [filepath_send] + +/// # Arguments +/// +/// * `filepath_get` - A reference to a `String` representing the path to the input file +/// containing data to be converted. +/// * `filepath_send` - A reference to a `String` representing the path to the output file +/// where the converted data will be written. +/// * `convert_from` - A reference to a `NumType` enum indicating the type of the input data. +/// * `convert_to` - A reference to a `NumType` enum indicating the type of the output data. +/// * `exponent` - An `i32` value used as the exponent for conversions involving fixed-point numbers. +/// +/// # Returns +/// +/// Returns `Ok(())` if the conversion and file writing operations are successful, +/// or an `Err` if an I/O error occurs during the process. +fn convert( + filepath_get: &String, + filepath_send: &String, + convert_from: NumType, + convert_to: NumType, + exponent: i32, + bits: bool, +) { + // Create the output file + let mut converted = File::create(filepath_send).expect("creation failed"); + + match (convert_from, convert_to) { + (NumType::Hex, NumType::Binary) => { + for line in read_to_string(filepath_get).unwrap().lines() { + hex_to_binary(line, &mut converted) + .expect("Failed to write binary to file"); + } + } + (NumType::Float, NumType::Binary) => { + for line in read_to_string(filepath_get).unwrap().lines() { + float_to_binary(line, &mut converted) + .expect("Failed to write binary to file"); + } + } + (NumType::Fixed, NumType::Binary) => { + for line in read_to_string(filepath_get).unwrap().lines() { + fixed_to_binary(line, &mut converted, exponent) + .expect("Failed to write binary to file"); + } + } + (NumType::Binary, NumType::Hex) => { + for line in read_to_string(filepath_get).unwrap().lines() { + binary_to_hex(line, &mut converted) + .expect("Failed to write hex to file"); + } + } + (NumType::Binary, NumType::Float) => { + for line in read_to_string(filepath_get).unwrap().lines() { + binary_to_float(line, &mut converted) + .expect("Failed to write float to file"); + } + } + (NumType::Binary, NumType::Fixed) => { + if !bits { + for line in read_to_string(filepath_get).unwrap().lines() { + binary_to_fixed(line, &mut converted, exponent) + .expect("Failed to write fixed-point to file"); + } + } else { + for line in read_to_string(filepath_get).unwrap().lines() { + binary_to_fixed_bit_slice(line, &mut converted, exponent) + .expect("Failed to write fixed-point to file"); + } + } + } + _ => panic!( + "Conversion from {} to {} is not supported", + convert_from.to_string(), + convert_to.to_string() + ), + } + eprintln!( + "Successfully converted from {} to {} in {}", + convert_from.to_string(), + convert_to.to_string(), + filepath_send + ); +} + +/// Formats [to_format] properly for float values +fn format_binary(to_format: u32) -> String { + let binary_str = format!("{:032b}", to_format); + format!( + "{} {} {}", + &binary_str[0..1], // Sign bit + &binary_str[1..9], // Exponent + &binary_str[9..] // Significand + ) +} + +fn format_hex(to_format: u32) -> String { + format!("0x{:X}", to_format) +} + +/// Converts a string representation of a floating-point number to its binary +/// format and appends the result to the specified file. +/// +/// This function takes a string slice representing a floating-point number, +/// converts it to a 32-bit floating-point number (`f32`), then converts this +/// number to its binary representation. The binary representation is formatted +/// as a string and written to the specified file, followed by a newline. +/// +/// # Arguments +/// +/// * `float_string` - A string slice containing the floating-point number to be converted. +/// * `filepath_send` - A mutable reference to a `File` where the binary representation +/// will be appended. +/// +/// # Returns +/// +/// This function returns a `std::io::Result<()>` which is `Ok` if the operation +/// is successful, or an `Err` if an I/O error occurs while writing to the file. +/// +/// # Panics +/// +/// This function will panic if the input string cannot be parsed as a floating-point number. +fn float_to_binary( + float_string: &str, + filepath_send: &mut File, +) -> std::io::Result<()> { + let float_of_string: f32; + // Convert string to float + match float_string.parse::() { + Ok(parsed_num) => float_of_string = parsed_num, + Err(_) => { + panic!("Failed to parse float from string") + } + } + + // Convert float to binary + let binary_of_float = float_of_string.to_bits(); + let formatted_binary_str = format_binary(binary_of_float); + + // Write binary string to the file + filepath_send.write_all(formatted_binary_str.as_bytes())?; + filepath_send.write_all(b"\n")?; + + Ok(()) +} + +/// Converts a string representation of a hexadecimal number to its binary +/// format and appends the result to the specified file. +/// +/// This function takes a string slice representing a hexadecimal number, +/// converts it to a 32-bit integer (`u32`), then converts this number to its +/// binary representation. The binary representation is formatted as a string +/// and written to the specified file, followed by a newline. +/// +/// # Arguments +/// +/// * `hex_string` - A string slice containing the hexadecimal number to be converted. +/// * `filepath_send` - A mutable reference to a `File` where the binary representation +/// will be appended. +/// +/// # Returns +/// +/// This function returns a `std::io::Result<()>` which is `Ok` if the operation +/// is successful, or an `Err` if an I/O error occurs while writing to the file. +/// +/// # Error +/// +/// This function will panic if the input string cannot be parsed as a hexadecimal number. +fn hex_to_binary(hex_string: &str, filepath_send: &mut File) -> io::Result<()> { + // Convert hex to binary + let binary_of_hex = u32::from_str_radix(hex_string, 16) + .expect("Failed to parse hex string"); + + // Format nicely + let formatted_binary_str = format!("{:b}", binary_of_hex); + + // Write binary string to the file + filepath_send.write_all(formatted_binary_str.as_bytes())?; + filepath_send.write_all(b"\n")?; + + Ok(()) +} + +/// Converts a string representation of a binary number to its hexadecimal +/// format and appends the result to the specified file. +/// +/// This function takes a string slice representing a binary number, +/// converts it to a 32-bit integer (`u32`), then converts this number to its +/// hexadecimal representation. The hexadecimal representation is formatted +/// as a string and written to the specified file, followed by a newline. +/// +/// # Arguments +/// +/// * `binary_string` - A string slice containing the binary number to be converted. +/// * `filepath_send` - A mutable reference to a `File` where the hexadecimal representation +/// will be appended. +/// +/// # Returns +/// +/// This function returns a `std::io::Result<()>` which is `Ok` if the operation +/// is successful, or an `Err` if an I/O error occurs while writing to the file. +/// +/// # Panics +/// +/// This function will panic if the input string cannot be parsed as a binary number. +fn binary_to_hex( + binary_string: &str, + filepath_send: &mut File, +) -> io::Result<()> { + let hex_of_binary = u32::from_str_radix(binary_string, 2) + .expect("Failed to parse binary string"); + + let formatted_hex_str = format_hex(hex_of_binary); + + filepath_send.write_all(formatted_hex_str.as_bytes())?; + filepath_send.write_all(b"\n")?; + + Ok(()) +} + +/// Converts a string representation of a binary number to its floating-point +/// format and appends the result to the specified file. +/// +/// This function takes a string slice representing a binary number, +/// converts it to a 32-bit integer (`u32`), then interprets this integer as +/// the binary representation of a 32-bit floating-point number (`f32`). +/// The floating-point representation is formatted as a string and written +/// to the specified file, followed by a newline. +/// +/// # Arguments +/// +/// * `binary_string` - A string slice containing the binary number to be converted. +/// * `filepath_send` - A mutable reference to a `File` where the floating-point representation +/// will be appended. +/// +/// # Returns +/// +/// This function returns a `std::io::Result<()>` which is `Ok` if the operation +/// is successful, or an `Err` if an I/O error occurs while writing to the file. +/// +/// # Panics +/// +/// This function will panic if the input string cannot be parsed as a binary number. +fn binary_to_float( + binary_string: &str, + filepath_send: &mut File, +) -> io::Result<()> { + let binary_value = u32::from_str_radix(binary_string, 2) + .expect("Failed to parse binary string"); + + // Interpret the integer as the binary representation of a floating-point number + let float_value = f32::from_bits(binary_value); + + let formated_float_str = format!("{:?}", float_value); + + filepath_send.write_all(formated_float_str.as_bytes())?; + filepath_send.write_all(b"\n")?; + + Ok(()) +} + +/// Converts a string representation of a fixed-point number to its binary +/// format and appends the result to the specified file. +/// +/// This function takes a string slice representing a fixed-point number, +/// multiplies it by 2 raised to the power of the negative exponent, converts the result +/// to a 32-bit integer, and then to its binary representation. The binary representation +/// is formatted as a string and written to the specified file, followed by a newline. +/// +/// # Arguments +/// +/// * `fixed_string` - A string slice containing the fixed-point number to be converted. +/// * `filepath_send` - A mutable reference to a `File` where the binary representation +/// will be appended. +/// * `exponent` - A floating-point number representing the exponent to be applied in the +/// conversion process. +/// +/// # Returns +/// +/// This function returns a `std::io::Result<()>` which is `Ok` if the operation +/// is successful, or an `Err` if an I/O error occurs while writing to the file. +/// +/// # Panics +/// +/// This function will panic if the input string cannot be parsed as a fixed-point number. +fn fixed_to_binary( + fixed_string: &str, + filepath_send: &mut File, + exp_int: i32, + // scale: usize, +) -> io::Result<()> { + // Convert fixed value from string to int + let fixed_value: f32; + match fixed_string.parse::() { + Ok(parsed_num) => fixed_value = parsed_num, + Err(_) => { + panic!("Bad fixed value input") + } + } + + //exponent int to float so we can multiply + let exponent = exp_int as f32; + + // Exponent math + let multiplied_fixed = fixed_value * 2_f32.powf(-exponent); + + // Convert to a 32-bit integer + let multiplied_fixed_as_i32 = multiplied_fixed as i32; + + // Convert to a binary string with 32 bits + let binary_of_fixed = format!("{:032b}", multiplied_fixed_as_i32); + + // Write binary string to the file + filepath_send.write_all(binary_of_fixed.as_bytes())?; + filepath_send.write_all(b"\n")?; + + Ok(()) +} + +/// Converts a string representation of a binary number to its fixed-point +/// format and appends the result to the specified file. +/// +/// This function takes a string slice representing a binary number, +/// converts it to a 32-bit unsigned integer, interprets this integer as +/// a floating-point number, divides it by 2 raised to the power of the negative exponent, +/// and converts the result to its fixed-point representation. The fixed-point +/// representation is formatted as a string and written to the specified file, +/// followed by a newline. +/// +/// # Arguments +/// +/// * `binary_string` - A string slice containing the binary number to be converted. +/// * `filepath_send` - A mutable reference to a `File` where the fixed-point representation +/// will be appended. +/// * `exponent` - A floating-point number representing the exponent to be applied in the +/// conversion process. +/// +/// # Returns +/// +/// This function returns a `std::io::Result<()>` which is `Ok` if the operation +/// is successful, or an `Err` if an I/O error occurs while writing to the file. +/// +/// # Panics +/// +/// This function will panic if the input string cannot be parsed as a binary number. +fn binary_to_fixed( + binary_string: &str, + filepath_send: &mut File, + exp_int: i32, +) -> io::Result<()> { + // Convert binary value from string to int + let binary_value = match u32::from_str_radix(binary_string, 2) { + Ok(parsed_num) => parsed_num, + Err(_) => panic!("Bad binary value input"), + }; + + // Convert to fixed + let int_of_binary = binary_value as f32; + + //exponent int to float so we can multiply + let exponent = exp_int as f32; + + // Exponent math + let divided: f32 = int_of_binary / 2_f32.powf(-exponent); + + let string_of_divided = divided.to_string(); + + // filepath_send.write_all(divided)?; + filepath_send.write_all(string_of_divided.as_bytes())?; + filepath_send.write_all(b"\n")?; + + Ok(()) +} + +fn binary_to_fixed_bit_slice( + binary_string: &str, + filepath_send: &mut File, + exp_int: i32, +) -> io::Result<()> { + // Parse the binary string to an integer + let binary = + i32::from_str_radix(binary_string, 2).expect("Bad binary value input"); + + // Get bitmask from exponent + let shift_amount = -exp_int; + let int_mask = !((1 << shift_amount) - 1); + let frac_mask = (1 << shift_amount) - 1; + + // Apply bit mask and shift integer part + // Apply bit masks and shift to get the integer and fractional parts + let integer_part = (binary & int_mask) >> shift_amount; + let fractional_part = binary & frac_mask; + + // Convert the integer part to its binary string representation + let int_part_binary = format!("{:b}", integer_part); + + // Convert the fractional part to its binary string representation + let mut frac_part_binary = String::new(); + let mut frac = fractional_part; + for _ in 0..shift_amount { + frac <<= 1; + if frac & (1 << shift_amount) != 0 { + frac_part_binary.push('1'); + frac -= 1 << shift_amount; + } else { + frac_part_binary.push('0'); + } + } + + // Append the integer and fractional parts + let combined_binary_representation = + format!("{}.{}", int_part_binary, frac_part_binary); + + // Write the combined binary representation to the file + writeln!(filepath_send, "{}", combined_binary_representation)?; + + Ok(()) +} diff --git a/tools/data-conversion/scripts/binary_to_fixed_speed.py b/tools/data-conversion/scripts/binary_to_fixed_speed.py new file mode 100644 index 0000000000..f3880b278c --- /dev/null +++ b/tools/data-conversion/scripts/binary_to_fixed_speed.py @@ -0,0 +1,46 @@ +import random +import os +import subprocess + + +def generate_binary_string(length): + """Generate a random binary string of given length.""" + return "".join(random.choice("01") for _ in range(length)) + + +def generate_tests(num_tests): + """Generate `num_tests` random tests.""" + tests = [] + for _ in range(num_tests): + # Generate a random binary string (up to 32 bits for u32 in Rust) + binary_string = generate_binary_string(random.randint(1, 24)) + tests.append((binary_string)) + + return tests + + +def write_input_files(tests, input_dir): + os.makedirs(input_dir, exist_ok=True) + input_paths = [] + + for binary_string in tests: + input_path = os.path.join(input_dir, f"speed_test.in") + with open(input_path, "a") as f: + f.write(f"{binary_string}\n") + input_paths.append(input_path) + + return input_paths + + +if __name__ == "__main__": + num_tests = 1_000_000 + input_dir = "./testsuite/inputs" + exponent = -4 + + # Generate Tests + tests = generate_tests(num_tests) + + # Write Input File + write_input_files(tests, input_dir) + + print(f"Input files are located in {input_dir}.") diff --git a/tools/data-conversion/scripts/binary_to_fixed_tests.py b/tools/data-conversion/scripts/binary_to_fixed_tests.py new file mode 100644 index 0000000000..26ef418422 --- /dev/null +++ b/tools/data-conversion/scripts/binary_to_fixed_tests.py @@ -0,0 +1,113 @@ +import random +import os +import subprocess + + +def generate_binary_string(length): + """Generate a random binary string of given length.""" + return "".join(random.choice("01") for _ in range(length)) + + +def generate_tests(num_tests): + """Generate `num_tests` random tests.""" + tests = [] + for _ in range(num_tests): + # Generate a random binary string (up to 32 bits for u32 in Rust) + binary_string = generate_binary_string(random.randint(1, 24)) + tests.append((binary_string)) + + return tests + + +def write_input_files(tests, input_dir): + os.makedirs(input_dir, exist_ok=True) + input_paths = [] + + for idx, binary_string in enumerate(tests): + input_path = os.path.join(input_dir, f"test_{idx+1}.in") + with open(input_path, "w") as f: + f.write(f"{binary_string}") + input_paths.append(input_path) + + return input_paths + + +def write_expect_files(tests, expect_dir): + os.makedirs(expect_dir, exist_ok=True) + expect_paths = [] + + for idx, binary_string in enumerate(tests): + expect_path = os.path.join(expect_dir, f"test_{idx+1}.expect") + with open(expect_path, "w") as f: + f.write(f"{binary_string}") + expect_paths.append(expect_path) + + +def convert_binary_to_fixed(binary_string, exponent): + """Convert binary string to a fixed-point number.""" + binary_value = int(binary_string, 2) # Convert binary string to integer + fixed_point_number = binary_value * ( + 2 ** exponent + ) # Calculate the fixed-point number + formatted = "{:.8e}".format(fixed_point_number) + return formatted + "\n" + + +def run_rust_function(input_file, output_file, exponent): + rust_command = f"../../target/debug/data-conversion --from {input_file} --to {output_file} --ftype 'binary' --totype 'fixed' --exp {exponent}" + result = subprocess.run(rust_command, shell=True, capture_output=True, text=True) + if result.returncode != 0: + print(f"Can't run rust function") + return result.returncode == 0 + + +def compare_files(output_file, expect_file): + with open(output_file, "r") as f: + output_content = f.read().strip() + output_content = float(output_content) + with open(expect_file, "r") as f: + expect_content = f.read().strip() + expect_content = float(expect_content) + + return output_content == expect_content + + +if __name__ == "__main__": + num_tests = 100 + input_dir = "../testsuite/inputs" + expect_dir = "../testsuite/expect" + output_dir = "../testsuite/outputs" + exponent = -4 + + # Generate Tests + tests = generate_tests(num_tests) + + # Write Input Files + input_paths = write_input_files(tests, input_dir) + + # Generate Expected Output + results = [] + for binary_string in tests: + results.append(convert_binary_to_fixed(binary_string, exponent)) + expect_paths = write_expect_files(results, expect_dir) + + # Make sure the output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Run Tests and Compare Outputs + for idx, test_file in enumerate(input_paths): + input_file = test_file + output_file = os.path.join(output_dir, f"test_{idx+1}.out") + expect_file = os.path.join(expect_dir, f"test_{idx+1}.expect") + + if run_rust_function(input_file, output_file, exponent): + if compare_files(output_file, expect_file): + print(f"Test {idx+1} passed.") + else: + print(f"Test {idx+1} failed: output does not match expected.") + else: + print(f"Test {idx+1} failed to run.") + + print(f"Input files are located in {input_dir}/.") + print(f"Expected output files are located in {expect_dir}/.") + print(f"Output files are located in {output_dir}/.") diff --git a/tools/data-conversion/src/main.rs b/tools/data-conversion/src/main.rs index 51de1586b3..f41315a257 100644 --- a/tools/data-conversion/src/main.rs +++ b/tools/data-conversion/src/main.rs @@ -4,10 +4,11 @@ use std::error::Error; use std::fmt; use std::fs::read_to_string; use std::fs::File; +use std::io::stdout; use std::io::{self, Write}; use std::str::FromStr; -//cargo run -- --from $PATH1 --to $PATH2 --ftype "binary" --totype "hex" +//cargo run -- --from $PATH1 --to $PATH2 --ftype "from" --totype "to" #[derive(Debug)] struct ParseNumTypeError; @@ -60,9 +61,9 @@ struct Arguments { #[argh(option)] from: String, - /// file to convery to + /// optional file to convery to #[argh(option)] - to: String, + to: Option, /// type to convert from #[argh(option)] @@ -75,12 +76,23 @@ struct Arguments { /// optional exponent for fixed_to_binary -> default is -1 #[argh(option, default = "-1")] exp: i32, + + /// optional for fixed_to_binary using bit slicing. If choosen, will use bit slicing. + #[argh(switch, short = 'b')] + bits: bool, } fn main() { let args: Arguments = argh::from_env(); - convert(&args.from, &args.to, args.ftype, args.totype, args.exp); + convert( + &args.from, + &args.to, + args.ftype, + args.totype, + args.exp, + args.bits, + ); } /// Converts [filepath_get] from type [convert_from] to type @@ -102,13 +114,16 @@ fn main() { /// or an `Err` if an I/O error occurs during the process. fn convert( filepath_get: &String, - filepath_send: &String, + filepath_send: &Option, convert_from: NumType, convert_to: NumType, exponent: i32, + bits: bool, ) { - // Create the output file - let mut converted = File::create(filepath_send).expect("creation failed"); + // Create the output file if filepath_send is Some + let mut converted: Option = filepath_send + .as_ref() + .map(|path| File::create(path).expect("creation failed")); match (convert_from, convert_to) { (NumType::Hex, NumType::Binary) => { @@ -142,9 +157,16 @@ fn convert( } } (NumType::Binary, NumType::Fixed) => { - for line in read_to_string(filepath_get).unwrap().lines() { - binary_to_fixed(line, &mut converted, exponent) - .expect("Failed to write fixed-point to file"); + if !bits { + for line in read_to_string(filepath_get).unwrap().lines() { + binary_to_fixed(line, &mut converted, exponent) + .expect("Failed to write fixed-point to file"); + } + } else { + for line in read_to_string(filepath_get).unwrap().lines() { + binary_to_fixed_bit_slice(line, &mut converted, exponent) + .expect("Failed to write fixed-point to file"); + } } } _ => panic!( @@ -153,13 +175,20 @@ fn convert( convert_to.to_string() ), } - - eprintln!( - "Successfully converted from {} to {} in {}", - convert_from.to_string(), - convert_to.to_string(), - filepath_send - ); + if let Some(filepath) = filepath_send { + eprintln!( + "Successfully converted from {} to {} in {}", + convert_from.to_string(), + convert_to.to_string(), + filepath + ); + } else { + eprintln!( + "Successfully converted from {} to {}", + convert_from.to_string(), + convert_to.to_string(), + ); + } } /// Formats [to_format] properly for float values @@ -201,7 +230,7 @@ fn format_hex(to_format: u32) -> String { /// This function will panic if the input string cannot be parsed as a floating-point number. fn float_to_binary( float_string: &str, - filepath_send: &mut File, + filepath_send: &mut Option, ) -> std::io::Result<()> { let float_of_string: f32; // Convert string to float @@ -216,9 +245,13 @@ fn float_to_binary( let binary_of_float = float_of_string.to_bits(); let formatted_binary_str = format_binary(binary_of_float); - // Write binary string to the file - filepath_send.write_all(formatted_binary_str.as_bytes())?; - filepath_send.write_all(b"\n")?; + if let Some(file) = filepath_send.as_mut() { + file.write_all(formatted_binary_str.as_bytes())?; + file.write_all(b"\n")?; + } else { + stdout().write_all(formatted_binary_str.as_bytes())?; + stdout().write_all(b"\n")?; + } Ok(()) } @@ -245,7 +278,10 @@ fn float_to_binary( /// # Error /// /// This function will panic if the input string cannot be parsed as a hexadecimal number. -fn hex_to_binary(hex_string: &str, filepath_send: &mut File) -> io::Result<()> { +fn hex_to_binary( + hex_string: &str, + filepath_send: &mut Option, +) -> io::Result<()> { // Convert hex to binary let binary_of_hex = u32::from_str_radix(hex_string, 16) .expect("Failed to parse hex string"); @@ -254,8 +290,15 @@ fn hex_to_binary(hex_string: &str, filepath_send: &mut File) -> io::Result<()> { let formatted_binary_str = format!("{:b}", binary_of_hex); // Write binary string to the file - filepath_send.write_all(formatted_binary_str.as_bytes())?; - filepath_send.write_all(b"\n")?; + + if let Some(file) = filepath_send.as_mut() { + // Write binary string to the file + file.write_all(formatted_binary_str.as_bytes())?; + file.write_all(b"\n")?; + } else { + stdout().write_all(formatted_binary_str.as_bytes())?; + stdout().write_all(b"\n")?; + } Ok(()) } @@ -284,15 +327,21 @@ fn hex_to_binary(hex_string: &str, filepath_send: &mut File) -> io::Result<()> { /// This function will panic if the input string cannot be parsed as a binary number. fn binary_to_hex( binary_string: &str, - filepath_send: &mut File, + filepath_send: &mut Option, ) -> io::Result<()> { let hex_of_binary = u32::from_str_radix(binary_string, 2) .expect("Failed to parse binary string"); let formatted_hex_str = format_hex(hex_of_binary); - filepath_send.write_all(formatted_hex_str.as_bytes())?; - filepath_send.write_all(b"\n")?; + if let Some(file) = filepath_send.as_mut() { + // Write binary string to the file + file.write_all(formatted_hex_str.as_bytes())?; + file.write_all(b"\n")?; + } else { + stdout().write_all(formatted_hex_str.as_bytes())?; + stdout().write_all(b"\n")?; + } Ok(()) } @@ -322,7 +371,7 @@ fn binary_to_hex( /// This function will panic if the input string cannot be parsed as a binary number. fn binary_to_float( binary_string: &str, - filepath_send: &mut File, + filepath_send: &mut Option, ) -> io::Result<()> { let binary_value = u32::from_str_radix(binary_string, 2) .expect("Failed to parse binary string"); @@ -332,8 +381,14 @@ fn binary_to_float( let formated_float_str = format!("{:?}", float_value); - filepath_send.write_all(formated_float_str.as_bytes())?; - filepath_send.write_all(b"\n")?; + if let Some(file) = filepath_send.as_mut() { + // Write binary string to the file + file.write_all(formated_float_str.as_bytes())?; + file.write_all(b"\n")?; + } else { + stdout().write_all(formated_float_str.as_bytes())?; + stdout().write_all(b"\n")?; + } Ok(()) } @@ -364,9 +419,8 @@ fn binary_to_float( /// This function will panic if the input string cannot be parsed as a fixed-point number. fn fixed_to_binary( fixed_string: &str, - filepath_send: &mut File, + filepath_send: &mut Option, exp_int: i32, - // scale: usize, ) -> io::Result<()> { // Convert fixed value from string to int let fixed_value: f32; @@ -389,9 +443,14 @@ fn fixed_to_binary( // Convert to a binary string with 32 bits let binary_of_fixed = format!("{:032b}", multiplied_fixed_as_i32); - // Write binary string to the file - filepath_send.write_all(binary_of_fixed.as_bytes())?; - filepath_send.write_all(b"\n")?; + if let Some(file) = filepath_send.as_mut() { + // Write binary string to the file + file.write_all(binary_of_fixed.as_bytes())?; + file.write_all(b"\n")?; + } else { + stdout().write_all(binary_of_fixed.as_bytes())?; + stdout().write_all(b"\n")?; + } Ok(()) } @@ -424,16 +483,11 @@ fn fixed_to_binary( /// This function will panic if the input string cannot be parsed as a binary number. fn binary_to_fixed( binary_string: &str, - filepath_send: &mut File, + filepath_send: &mut Option, exp_int: i32, ) -> io::Result<()> { - // Create an array with the elements of fixed_string - let words: Vec<&str> = binary_string.split_whitespace().collect(); - let binary_str: &&str = - words.first().unwrap_or(&"There is not a binary number"); - // Convert binary value from string to int - let binary_value = match u32::from_str_radix(binary_str, 2) { + let binary_value = match u32::from_str_radix(binary_string, 2) { Ok(parsed_num) => parsed_num, Err(_) => panic!("Bad binary value input"), }; @@ -447,11 +501,51 @@ fn binary_to_fixed( // Exponent math let divided: f32 = int_of_binary / 2_f32.powf(-exponent); - let string_of_divided = divided.to_string(); + let string_of_divided = format!("{:+.8e}", divided); + + if let Some(file) = filepath_send.as_mut() { + // Write binary string to the file + file.write_all(string_of_divided.as_bytes())?; + file.write_all(b"\n")?; + } else { + stdout().write_all(string_of_divided.as_bytes())?; + stdout().write_all(b"\n")?; + } + + Ok(()) +} - // filepath_send.write_all(divided)?; - filepath_send.write_all(string_of_divided.as_bytes())?; - filepath_send.write_all(b"\n")?; +fn binary_to_fixed_bit_slice( + binary_string: &str, + filepath_send: &mut Option, + exp_int: i32, +) -> io::Result<()> { + // Convert binary string to an integer (assuming binary_string is a valid binary representation) + let binary_int = u32::from_str_radix(binary_string, 2).unwrap(); + + // Adjust the binary point based on the exponent + let mut result = binary_int; + if exp_int < 0 { + // If exponent is negative, shift right (multiply by 2^(-exp_int)) + result >>= -exp_int as u32; + } else { + // If exponent is positive, shift left (multiply by 2^(exp_int)) + result <<= exp_int as u32; + } + + // Convert result to a fixed-point decimal representation + let fixed_value = result as f32; + + let string_of_fixed = format!("{:.8e}", fixed_value); + + if let Some(file) = filepath_send.as_mut() { + // Write binary string to the file + file.write_all(string_of_fixed.as_bytes())?; + file.write_all(b"\n")?; + } else { + stdout().write_all(string_of_fixed.as_bytes())?; + stdout().write_all(b"\n")?; + } Ok(()) } diff --git a/tools/data-conversion/testsuite/binary_inputs.txt b/tools/data-conversion/testsuite/binary_inputs.txt new file mode 100644 index 0000000000..c0c562d5c4 --- /dev/null +++ b/tools/data-conversion/testsuite/binary_inputs.txt @@ -0,0 +1,10 @@ +10111100111000010100000101110001 +1010 +111001 +01101000011010001010111010100 +10010100010101001100111001101001 +010100111100110000001110111 +10 +1000111111010101 +10011110011 +100101011010000000000010100101 diff --git a/tools/data-conversion/testsuite/expect/test_1.expect b/tools/data-conversion/testsuite/expect/test_1.expect new file mode 100644 index 0000000000..373892ba35 --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_1.expect @@ -0,0 +1 @@ +582 diff --git a/tools/data-conversion/testsuite/expect/test_10.expect b/tools/data-conversion/testsuite/expect/test_10.expect new file mode 100644 index 0000000000..da6a807667 --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_10.expect @@ -0,0 +1 @@ +0.875 diff --git a/tools/data-conversion/testsuite/expect/test_2.expect b/tools/data-conversion/testsuite/expect/test_2.expect new file mode 100644 index 0000000000..cedce7f749 --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_2.expect @@ -0,0 +1 @@ +2.625 diff --git a/tools/data-conversion/testsuite/expect/test_3.expect b/tools/data-conversion/testsuite/expect/test_3.expect new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_3.expect @@ -0,0 +1 @@ +1 diff --git a/tools/data-conversion/testsuite/expect/test_4.expect b/tools/data-conversion/testsuite/expect/test_4.expect new file mode 100644 index 0000000000..01efb109fa --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_4.expect @@ -0,0 +1 @@ +686.5625 diff --git a/tools/data-conversion/testsuite/expect/test_5.expect b/tools/data-conversion/testsuite/expect/test_5.expect new file mode 100644 index 0000000000..9cd6c29970 --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_5.expect @@ -0,0 +1 @@ +0.0625 diff --git a/tools/data-conversion/testsuite/expect/test_6.expect b/tools/data-conversion/testsuite/expect/test_6.expect new file mode 100644 index 0000000000..378c9be594 --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_6.expect @@ -0,0 +1 @@ +32.6875 diff --git a/tools/data-conversion/testsuite/expect/test_7.expect b/tools/data-conversion/testsuite/expect/test_7.expect new file mode 100644 index 0000000000..341065e696 --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_7.expect @@ -0,0 +1 @@ +84092202.875 diff --git a/tools/data-conversion/testsuite/expect/test_8.expect b/tools/data-conversion/testsuite/expect/test_8.expect new file mode 100644 index 0000000000..7d385d419c --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_8.expect @@ -0,0 +1 @@ +0.25 diff --git a/tools/data-conversion/testsuite/expect/test_9.expect b/tools/data-conversion/testsuite/expect/test_9.expect new file mode 100644 index 0000000000..90afb3e998 --- /dev/null +++ b/tools/data-conversion/testsuite/expect/test_9.expect @@ -0,0 +1 @@ +183 diff --git a/tools/data-conversion/testsuite/inputs/test_1.in b/tools/data-conversion/testsuite/inputs/test_1.in new file mode 100644 index 0000000000..3621de2dee --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_1.in @@ -0,0 +1 @@ +10010001100000 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_10.in b/tools/data-conversion/testsuite/inputs/test_10.in new file mode 100644 index 0000000000..96e38be913 --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_10.in @@ -0,0 +1 @@ +1110 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_2.in b/tools/data-conversion/testsuite/inputs/test_2.in new file mode 100644 index 0000000000..cdd2b8d386 --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_2.in @@ -0,0 +1 @@ +101010 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_3.in b/tools/data-conversion/testsuite/inputs/test_3.in new file mode 100644 index 0000000000..1746da6312 --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_3.in @@ -0,0 +1 @@ +10000 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_4.in b/tools/data-conversion/testsuite/inputs/test_4.in new file mode 100644 index 0000000000..a9ef8a4f1f --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_4.in @@ -0,0 +1 @@ +010101011101001 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_5.in b/tools/data-conversion/testsuite/inputs/test_5.in new file mode 100644 index 0000000000..56a6051ca2 --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_5.in @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_6.in b/tools/data-conversion/testsuite/inputs/test_6.in new file mode 100644 index 0000000000..e265ea111f --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_6.in @@ -0,0 +1 @@ +1000001011 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_7.in b/tools/data-conversion/testsuite/inputs/test_7.in new file mode 100644 index 0000000000..bc7df70a8e --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_7.in @@ -0,0 +1 @@ +1010000001100100101001010101110 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_8.in b/tools/data-conversion/testsuite/inputs/test_8.in new file mode 100644 index 0000000000..58089a949e --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_8.in @@ -0,0 +1 @@ +0100 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/inputs/test_9.in b/tools/data-conversion/testsuite/inputs/test_9.in new file mode 100644 index 0000000000..7e5f6b863d --- /dev/null +++ b/tools/data-conversion/testsuite/inputs/test_9.in @@ -0,0 +1 @@ +0101101110000 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/runt.toml b/tools/data-conversion/testsuite/runt.toml new file mode 100644 index 0000000000..7e13a99a43 --- /dev/null +++ b/tools/data-conversion/testsuite/runt.toml @@ -0,0 +1,11 @@ +ver = "0.4.1" + +[suite] +name = "Rust Tests" + +[[tests]] +name = "Fixed-Point Conversion Tests" +cmd = "../../../target/debug/data-conversion --from {} --to {} --ftype 'binary' --totype 'fixed' --exp -4" +paths = ["inputs/test_*.in"] +expect_dir = "expect" + diff --git a/tools/data-conversion/testsuite/test.txt b/tools/data-conversion/testsuite/test.txt new file mode 100644 index 0000000000..c91f309f91 --- /dev/null +++ b/tools/data-conversion/testsuite/test.txt @@ -0,0 +1,4 @@ +00111101110011001100110011001101 +00111110010011001100110011001101 +00111111100011001100110011001101 +00111110100000000000000000000000 \ No newline at end of file diff --git a/tools/data-conversion/testsuite/testsuite.yaml b/tools/data-conversion/testsuite/testsuite.yaml new file mode 100644 index 0000000000..f91d1ef231 --- /dev/null +++ b/tools/data-conversion/testsuite/testsuite.yaml @@ -0,0 +1,93 @@ +suite: + cases: + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_1.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '1.1875 + + ' + name: Test Case 1 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_2.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '0.125 + + ' + name: Test Case 2 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_3.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '15820057.5 + + ' + name: Test Case 3 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_4.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '668072.0 + + ' + name: Test Case 4 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_5.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '4368369.3125 + + ' + name: Test Case 5 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_6.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '643.875 + + ' + name: Test Case 6 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_7.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '2489.3125 + + ' + name: Test Case 7 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_8.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '49753537.875 + + ' + name: Test Case 8 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_9.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '5.4375 + + ' + name: Test Case 9 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + - command: cargo run -- --from /Users/Angelica/Desktop/calyx/tools/data-conversion/testsuite/inputs/test_10.in + --ftype "binary" --totype "fixed" --exp -4 + expect: + exit_code: 0 + stdout: '10.0 + + ' + name: Test Case 10 + working_directory: /Users/Angelica/Desktop/calyx/tools/data-conversion/scripts + name: Rust Tests