1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
use std::f64;

#[derive(Debug)]
pub enum ProtobufFloatParseError {
    EmptyString,
    CannotParseFloat,
}

pub type ProtobufFloatParseResult<T> = Result<T, ProtobufFloatParseError>;

pub const PROTOBUF_NAN: &str = "nan";
pub const PROTOBUF_INF: &str = "inf";

/// Format float as in protobuf `.proto` files
pub fn format_protobuf_float(f: f64) -> String {
    if f.is_nan() {
        PROTOBUF_NAN.to_owned()
    } else if f.is_infinite() {
        if f > 0.0 {
            format!("{}", PROTOBUF_INF)
        } else {
            format!("-{}", PROTOBUF_INF)
        }
    } else {
        let i = f as i64;
        if i as f64 == f {
            // Older rust versions did print float without `.0` suffix
            format!("{:?}.0", i)
        } else {
            // TODO: make sure doesn't lose precision
            format!("{:?}", f)
        }
    }
}

/// Parse float from `.proto` format
pub fn parse_protobuf_float(s: &str) -> ProtobufFloatParseResult<f64> {
    if s.is_empty() {
        return Err(ProtobufFloatParseError::EmptyString);
    }
    if s == PROTOBUF_NAN {
        return Ok(f64::NAN);
    }
    if s == PROTOBUF_INF || s == format!("+{}", PROTOBUF_INF) {
        return Ok(f64::INFINITY);
    }
    if s == format!("-{}", PROTOBUF_INF) {
        return Ok(f64::NEG_INFINITY);
    }
    match s.parse() {
        Ok(f) => Ok(f),
        Err(_) => Err(ProtobufFloatParseError::CannotParseFloat),
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_format_protobuf_float() {
        assert_eq!("10.0", format_protobuf_float(10.0));
        assert_eq!("-10.0", format_protobuf_float(-10.0));
        assert_eq!("10.5", format_protobuf_float(10.5));
        assert_eq!("-10.5", format_protobuf_float(-10.5));
    }
}