Skip to content

Commit

Permalink
Optimize arbitrary precision calculations, add rounding option
Browse files Browse the repository at this point in the history
  • Loading branch information
Kuuuube committed Jul 27, 2023
1 parent d0932e5 commit 33f9ef3
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 15 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ Allows converting single arabic numerals to japanese numerals, generating lists

- `--step-type`: The operation to apply the step as: `add`, `multiply`, or `exponent`. Do not use a range starting at `0` (the default) with `multiply` and `exponent`. Default: `add`.

- `--precise`: Enable arbitrary precision mode to remove float error (fractional exponent steps may cause infinite calculations). Default: `false`.
- `--precise`: Enable arbitrary precision mode to remove float error (fractional exponent steps use 50 digit precision to avoid infinite calculations). Default: `false`.

- `--round`: Rounds the result's decimal places in precise mode. Default: `50`.

- `--output=FILE`: Output filepath.

Expand Down
9 changes: 8 additions & 1 deletion src/args_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub fn parse_args() -> Option<Settings> {
"--step" => {settings.raw_step = safe_get_string(split.clone(), 1); settings.step = safe_parse_f64(safe_get_string(split.clone(), 1)); if safe_get_string(split.clone(), 1).split(".").collect::<Vec<&str>>().len() == 2 {settings.step_decimal_len = safe_get_string(split, 1).len()}},
"--step-type" => {settings.step_type = match safe_get_string(split, 1).as_str() {"multiply" => StepType::Multiply, "exponent" => StepType::Exponent, _ => StepType::Add}},
"--precise" => {settings.precise = true},
"--round" => {settings.round = safe_parse_i64(safe_get_string(split, 1))}
"--output" => {settings.output = safe_get_string(split, 1)},
"--correct" => {settings.correct = safe_get_string(split, 1)},
"--incorrect" => {settings.incorrect = safe_get_string(split, 1)},
Expand All @@ -35,7 +36,7 @@ pub fn parse_args() -> Option<Settings> {
}

fn help_message() {
println!("jp_number_converter\nUsage: jp_number_converter [OPTION]...\n\nModes:\n --mode=MODE (interactive|generation|guessing)\n\nAll Modes:\n --format=STR format string to override default in the following format:\n `Arabic: {{arabic}}, Hiragana: {{hiragana}}, Kanji: {{kanji}}, Banknote-style Daiji: {{banknote_daiji}}, Daiji: {{daiji}}\\n`\n\nInteractive Mode:\n --prompt=STR string to override default prompt message, only supports \\n variable\n\nGeneration Mode:\n --range=ARGS range of numbers in the following format: `1-1000`\n --step=FLOAT number to increment the output by\n --step-type (add|multiply|exponent)\n --precise enables arbitrary precision mode\n --output=FILE set output FILE\n\nGuessing Mode:\n --range=ARGS range of numbers in the following format: `1-1000`\n --prompt=STR string to override default prompt message, only supports \\n variable\n --correct=STR format string to override default correct message in the same format as --format\n --incorrect=STR format string to override default incorrect message in the same format as --format\n --weight makes all digits within the range equally likely\n --max-decimal the maximum decimal places in generated numbers");
println!("jp_number_converter\nUsage: jp_number_converter [OPTION]...\n\nModes:\n --mode=MODE (interactive|generation|guessing)\n\nAll Modes:\n --format=STR format string to override default in the following format:\n `Arabic: {{arabic}}, Hiragana: {{hiragana}}, Kanji: {{kanji}}, Banknote-style Daiji: {{banknote_daiji}}, Daiji: {{daiji}}\\n`\n\nInteractive Mode:\n --prompt=STR string to override default prompt message, only supports \\n variable\n\nGeneration Mode:\n --range=ARGS range of numbers in the following format: `1-1000`\n --step=FLOAT number to increment the output by\n --step-type (add|multiply|exponent)\n --precise enables arbitrary precision mode\n --round rounds the result's decimal places in precise mode\n --output=FILE set output FILE\n\nGuessing Mode:\n --range=ARGS range of numbers in the following format: `1-1000`\n --prompt=STR string to override default prompt message, only supports \\n variable\n --correct=STR format string to override default correct message in the same format as --format\n --incorrect=STR format string to override default incorrect message in the same format as --format\n --weight makes all digits within the range equally likely\n --max-decimal the maximum decimal places in generated numbers\n");
}

fn unknown_command_message(command: &str) {
Expand All @@ -55,6 +56,10 @@ fn safe_parse_f64(input: String) -> f64 {
return input.parse::<f64>().unwrap_or_default();
}

fn safe_parse_i64(input: String) -> i64 {
return input.parse::<i64>().unwrap_or_default();
}

fn safe_parse_usize(input: String) -> usize {
return input.parse::<usize>().unwrap_or_default();
}
Expand All @@ -69,6 +74,7 @@ pub struct Settings {
pub step_decimal_len: usize,
pub step_type: StepType,
pub precise: bool,
pub round: i64,
pub output: String,
pub correct: String,
pub incorrect: String,
Expand All @@ -88,6 +94,7 @@ impl Default for Settings {
step_decimal_len: 0,
step_type: StepType::Add,
precise: false,
round: 50,
output: "".to_string(),
correct: "Correct! Arabic: {arabic}, Hiragana: {hiragana}, Kanji: {kanji}, Banknote-style Daiji: {banknote_daiji}, Daiji: {daiji}\n".to_string(),
incorrect: "Incorrect. Arabic: {arabic}, Hiragana: {hiragana}, Kanji: {kanji}, Banknote-style Daiji: {banknote_daiji}, Daiji: {daiji}\n".to_string(),
Expand Down
8 changes: 4 additions & 4 deletions src/modes/generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub fn precise_generation_mode(settings: Settings) {
let bigdecimal_step = BigDecimal::from_str(&settings.raw_step).unwrap_or_default();
let mut i = start_number;

while i < end_number.clone() + bigdecimal_step.clone() {
while i < &end_number + &bigdecimal_step {
let input_string = clean_decimal_string(format!("{}", i));
let hiragana_output = if hiragana_convert {
hiragana::convert_number(&input_string)
Expand All @@ -123,9 +123,9 @@ pub fn precise_generation_mode(settings: Settings) {
generated_string += &format!("{}", settings.format_string.replace("{arabic}", &input_string).replace("{hiragana}", &hiragana_output).replace("{kanji}", &kanji_output).replace("{banknote_daiji}", &banknote_daiji_output).replace("{daiji}", &daiji_output).replace("\\n", "\n"));

match settings.step_type {
StepType::Add => i += bigdecimal_step.clone(),
StepType::Multiply => i *= bigdecimal_step.clone(),
StepType::Exponent => i = bigdecimal_powf(i, bigdecimal_step.clone())
StepType::Add => i += &bigdecimal_step,
StepType::Multiply => i = (i * &bigdecimal_step).round(settings.round),
StepType::Exponent => i = bigdecimal_powf(i, &bigdecimal_step).round(settings.round)
}
}

Expand Down
20 changes: 11 additions & 9 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bigdecimal::{BigDecimal, Zero, FromPrimitive};
use std::str::FromStr;

pub fn bigdecimal_powf(x: BigDecimal, e: BigDecimal) -> BigDecimal {
pub fn bigdecimal_powf(x: BigDecimal, e: &BigDecimal) -> BigDecimal {
let exponent_string = format!("{}", e);
let split: Vec<&str> = exponent_string.split(".").collect();
let split_whole = split.get(0).unwrap();
Expand All @@ -17,10 +17,11 @@ pub fn bigdecimal_powf(x: BigDecimal, e: BigDecimal) -> BigDecimal {
let simplified_numerator = numerator / gcd;
let simplified_denominator = denominator / gcd;

let whole_result = bigdecimal_powi(x.clone(), BigDecimal::from_u32(simplified_numerator).unwrap());
return bigdecimal_root(BigDecimal::from_u32(simplified_denominator).unwrap(), whole_result.clone());
let whole_result = bigdecimal_powi(&x.round(50), &BigDecimal::from_u32(simplified_numerator).unwrap()).round(50);
let result = bigdecimal_root(BigDecimal::from_u32(simplified_denominator).unwrap(), whole_result.clone());
return result;
} else {
return bigdecimal_powi(x.clone(), whole_value);
return bigdecimal_powi(&x, &whole_value);
}
}

Expand All @@ -33,10 +34,10 @@ fn euclid_gcd(mut m: u32, mut n: u32) -> u32 {
return n
}

pub fn bigdecimal_powi(x: BigDecimal, e: BigDecimal) -> BigDecimal {
pub fn bigdecimal_powi(x: &BigDecimal, e: &BigDecimal) -> BigDecimal {
let mut r = BigDecimal::from_str("1").unwrap();
let mut i = BigDecimal::zero();
while i < e {
while i < *e {
r *= x.clone();
i += 1;
}
Expand All @@ -53,9 +54,10 @@ pub fn bigdecimal_root(n: BigDecimal, x: BigDecimal) -> BigDecimal {
return BigDecimal::zero(); //NaN
}
loop {
d = (x.clone() / bigdecimal_powi(r.clone(), n.clone() - 1) - r.clone()) / n.clone();
r += d.clone();
if !(d.clone() >= BigDecimal::from_f64(f64::EPSILON).unwrap() * 10 || d <= BigDecimal::from_f64(-f64::EPSILON).unwrap() * 10) {
r = r.with_prec(50); //looping with round is too expensive, with_prec must be used
d = (&x / bigdecimal_powi(&r, &(&n - &1)) - &r) / &n;
r += &d;
if !(&d >= &(BigDecimal::from_f64(f64::EPSILON).unwrap() * 10) || &d <= &(BigDecimal::from_f64(-f64::EPSILON).unwrap() * 10)) {
break;
}
}
Expand Down

0 comments on commit 33f9ef3

Please sign in to comment.