Reputation: 155
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;
}
}
(for some reason "pad" is not working at playground)
Upvotes: 0
Views: 338
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:
9876543210.12345678
9876543488.00000000
277.87654322
01010000 00010011 00101100 00000110
0x50132c06
Even f64
still makes errors:
9876543210.12345678
9876543210.1234569549560546875
0.0000001749560546854
01000010 00000010 01100101 10000000 10110111 01010000 11111100 11010111
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:
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