Cult of Tranquility
Cult of Tranquility

Reputation: 155

generate float with leading zeroes and string with trailing space

I'm trying to print out rows where the string "name" has a fixed length of 20 (trailing spaces).

I then want to generate a float number (amount) with 10 whole numbers and 8 decimals, problem is I cant figure out how to format the amount/float with leading zeroes making them the same length, also for some reason currently all the decimals becomes zero.

The output I want:

John Doe            D4356557654354645634564563.15343534
John Doe            C5674543545645634565456345.34535767
John Doe            C0000000000000000000000000.44786756
John Doe            D0000000000000000000865421.12576545

What the output currently looks like:

John Doe            12345678912345C390571360.00000000
John Doe            12345678912345D5000080896.00000000
John Doe            12345678912345C4320145.50000000
John Doe            12345678912345C1073856384.00000000

Code

use rand::Rng;
use pad::PadStr;

struct Report {
    name: String,
    account_number: i64,
    letter: char,
    amount: f32,
}

fn main() {

  let mut n = 1;
  let mut rng = rand::thread_rng();

  while n < 101 {
    let acc = Report {
        name: String::from("John Doe").pad_to_width(20),
        account_number: 12345678912345,
        letter: rng.gen_range('C'..='D'),
        amount: rng.gen_range(100.1..9999999999.9),
    };


    println!("{}{}{}{:.8}\n", acc.name, acc.account_number, acc.letter, acc.amount);
    n += 1;
  }

  }

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e229dbd212a94cd9cc0be507568c48d5

(for some reason "pad" is not working at playground)

Upvotes: 0

Views: 338

Answers (1)

Finomnis
Finomnis

Reputation: 22601

The reason you don't see any after-comma values is that f32 does not have enough precision to represent the value you would like to print. In fact, no commonly used base-2 float has the precision you need here, converting from base 10 to base 2 back and forth with fractional numbers will always result in rounding errors.

For example, if you represent '9876543210.12345678' (a number your string can represent) in IEEE-754 32-bit floats (which f32 is), you get:

  • Desired value: 9876543210.12345678
  • Most accurate representation: 9876543488.00000000
  • Error due to conversion: 277.87654322
  • Binary Representation: 01010000 00010011 00101100 00000110
  • Hexadecimal Representation: 0x50132c06

Even f64 still makes errors:

  • Desired value: 9876543210.12345678
  • Value actually stored in double: 9876543210.1234569549560546875
  • Error due to conversion: 0.0000001749560546854
  • Binary Representation: 01000010 00000010 01100101 10000000 10110111 01010000 11111100 11010111
  • Hexadecimal Representation: 0x42026580B750FCD7

Don't use floats for this task. Use integers instead. For example two integers that can then be printed with a dot in between.

If you really do need the actual value, use a decimal float instead, so it can actually represent the values you are trying to encode.

Another alternative would be to use fixed point integers. In other words, shift all values of your numbers by 8 decimals and represent them as an integer. Then, add the dot again when printing them. In your case you are lucky; if you store 9876543210.12345678 as the integer 987654321012345678, it still fits into 64 bits, so you can use a u64 to represent it. In fact, given that your example seems to be talking about an amount of money, this is most likely how it was intended.


Example:

  • Don't use pad for padding. It's already built into Rust's format macro: format!("{:<20}", "John Doe")
use rand::Rng;

#[derive(Debug)]
struct MoneyAmount(u64);

impl MoneyAmount {
    fn as_value_string(&self) -> String {
        let mut s = format! {"{:>018}", self.0};
        s.insert(10, '.');
        s
    }
}

struct Report {
    name: String,
    account_number: i64,
    letter: char,
    amount: MoneyAmount,
}

fn main() {
    let mut rng = rand::thread_rng();

    for _ in 0..10 {
        let acc = Report {
            name: "John Doe".to_string(),
            account_number: 12345678912345,
            letter: rng.gen_range('C'..='D'),
            amount: MoneyAmount(rng.gen_range(100100000000..999999999999999999)),
        };

        println!(
            "{:<20}{}{}{}",
            acc.name,
            acc.account_number,
            acc.letter,
            acc.amount.as_value_string()
        );
    }
}
John Doe            12345678912345D3628299098.68538932
John Doe            12345678912345C5874745565.85000457
John Doe            12345678912345C6337870441.26543580
John Doe            12345678912345D1215442576.70454002
John Doe            12345678912345C3402018622.70996714
John Doe            12345678912345C7999783867.43749281
John Doe            12345678912345D5797682336.45356635
John Doe            12345678912345D1707577080.35404025
John Doe            12345678912345D5813907399.04925935
John Doe            12345678912345C0611246390.19108372

Upvotes: 1

Related Questions