Ryan Waskiewicz
Ryan Waskiewicz

Reputation: 321

Rust: Formatting a Float with a Minimum Number of Decimal Points

I have an f32 value that I'd like to print. Being a float, I can represent integers with this type as well

let a_float: f32 = 3.0;
let another_float: f32 = 3.14;
// let's pretend this was user input,
// we didn't know what they'd put enter, so we used f32 to cover our bases
let an_integer: f32 = 3;

I'd like to print a value stored as an f32 with a minimum amount of precision, but using as much as necessary to represent the value stored. If my desired minimum precision was one (1), I'd expect the following transformation to be done on the float:

let a_float: f32 = 3.0;            // print 3.0
let another_float: f32 = 3.14;     // print 3.14
let an_integer: f32 = 3;           // print 3.0

I know that I can set a finite number of decimal places using std::fmt's precision, but that doesn't seem to give me what I want. Is there a way to achieve this functionality without bringing in additional formatting crates? Pulling in additional crates isn't out of the realm of possibility, I'm moreso interested in what I'm able to do 'out of the box'

Upvotes: 2

Views: 5042

Answers (2)

Aerow
Aerow

Reputation: 41

Pretty print got me what I was looking for

fn main() {
    let a_float: f32 = 3.0;            // print 3.0
    let another_float: f32 = 3.14;     // print 3.14
    let an_integer: i32 = 3;           // print 3.0
    println!("{:?}", a_float);
    println!("{:?}", another_float);
    println!("{:?}", an_integer as f32);
}

Upvotes: 4

Kevin Reid
Kevin Reid

Reputation: 43773

Rust already does this by default. Every float is printed with as many digits as are necessary to denote that particular float uniquely.

Here's a program to demonstrate this. It generates 10000 random floats, converts them to strings, and checks how many digits can be deleted from the fractional part without changing the value.

(Caveat: This does not show that there aren't cases where the number could be represented in fewer digits by rounding it in a different direction, which can happen sometimes if I remember correctly. I'm not a float formatting expert.)

use std::collections::HashMap;
use rand::{Rng, thread_rng};

/// Change this to choose the type analyzed
type Float = f32;

fn main() {
    let mut rng = thread_rng();
    let mut digit_histogram = HashMap::new();
    for _ in 1..10000 {
        let x: Float = rng.gen_range(0.0..10.0);
        let string = x.to_string();
        
        // Break up string representation
        let before_exponent_pos = string.find('e').unwrap_or(string.len());
        let after_decimal_pos = string.find('.')
            .map(|p| p + 1)
            .unwrap_or(before_exponent_pos);
        let prefix = &string[..after_decimal_pos];
        let mut fractional_digits = &string[after_decimal_pos..before_exponent_pos];
        let suffix = &string[before_exponent_pos..];
        
        // What happens if we truncate the digits?
        let initial_digits = fractional_digits.len();
        let mut unnecessary_digits = 0;
        while fractional_digits.len() > 0 {
            fractional_digits = &fractional_digits[..fractional_digits.len() - 1];
            let shortened_string = format!("{}{}{}", 
                prefix,
                fractional_digits,
                suffix,
            );
            let shortened_x = shortened_string.parse::<Float>().unwrap();
            if shortened_x == x {
                unnecessary_digits += 1;
            } else {
                break;
            }
        }

        *(digit_histogram
            .entry((initial_digits, unnecessary_digits))
            .or_insert(0)) += 1;
    }
    
    // Summarize results.
    let mut digit_histogram = digit_histogram.into_iter().collect::<Vec<_>>();
    digit_histogram.sort_by_key(|pair| pair.0);
    for ((initial_digits, unnecessary_digits), occurrences) in digit_histogram {
        println!(
            "{} digits with {} unnecessary × {}",
            initial_digits,
            unnecessary_digits,
            occurrences);
    }
}

Runnable on Rust Playground. Results:

2 digits with 0 unnecessary × 1
3 digits with 0 unnecessary × 6
4 digits with 0 unnecessary × 25
5 digits with 0 unnecessary × 401
6 digits with 0 unnecessary × 4061
7 digits with 0 unnecessary × 4931
8 digits with 0 unnecessary × 504
9 digits with 0 unnecessary × 62
10 digits with 0 unnecessary × 8

The program saw a wide variety of numbers of digits, but never any that could be deleted without changing the answer.

Upvotes: 1

Related Questions