Skip to content

Commit

Permalink
Replace rounding-error prone floating point code with robust integer …
Browse files Browse the repository at this point in the history
…code.
  • Loading branch information
Peter Michael Green committed Aug 24, 2021
1 parent 5f525ff commit 297c655
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 14 deletions.
48 changes: 34 additions & 14 deletions src/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,6 @@ fn sign(i: &[u8]) -> IResult<&[u8], i32> {
})(i)
}

fn fractions(i: &[u8]) -> IResult<&[u8], f32> {
let (i, digits) = take_while(is_digit)(i)?;
let digits = str::from_utf8(digits).unwrap(); // This can't panic, `digits` will only include digits.
let f = format!("0.{}", digits).parse().unwrap(); // This can't panic, the string is a valid `f32`.

Ok((i, f))
}

// DATE

// [+/-]YYYY
Expand Down Expand Up @@ -204,8 +196,22 @@ fn time_second(i: &[u8]) -> IResult<&[u8], u32> {
n_digit_in_range(i, 2, 0..=60)
}

fn time_millisecond(fraction: f32) -> u32 {
(1000.0 * fraction) as u32
fn time_millisecond(i: &[u8]) -> IResult<&[u8], u32> {
let (i, mut digits) = take_while(is_digit)(i)?;
let mut l = digits.len();
if l > 3 {
digits = digits.get(0..3).unwrap();
}
let mut result = 0;
if l > 0 {
let digits = str::from_utf8(digits).unwrap(); // This can't panic, `digits` will only include digits.
result = digits.parse().unwrap();
}
while l < 3 {
result = result * 10;
l += 1;
}
Ok ((i,result))
}

// HH:MM:[SS][.(m*)][(Z|+...|-...)]
Expand All @@ -216,7 +222,7 @@ pub fn parse_time(i: &[u8]) -> IResult<&[u8], Time> {
opt(tag(b":")), // :
time_minute, // MM
opt(preceded(opt(tag(b":")), time_second)), // [SS]
opt(map(preceded(one_of(",."), fractions), time_millisecond)), // [.(m*)]
opt(preceded(one_of(",."), time_millisecond)), // [.(m*)]
opt(alt((timezone_hour, timezone_utc))), // [(Z|+...|-...)]
)),
|(h, _, m, s, ms, z)| {
Expand Down Expand Up @@ -288,13 +294,27 @@ fn duration_minute(i: &[u8]) -> IResult<&[u8], u32> {
// S[S][[,.][MS]]
fn duration_second_and_millisecond(i: &[u8]) -> IResult<&[u8], (u32, u32)> {
let (i, s) = m_to_n_digit_in_range(i, 1, 2, 0..=60)?;
let (i, ms) = opt(map(preceded(one_of(",."), fractions), duration_millisecond))(i)?;
let (i, ms) = opt(preceded(one_of(",."), duration_millisecond))(i)?;

Ok((i, (s, ms.unwrap_or(0))))
}

fn duration_millisecond(fraction: f32) -> u32 {
(1000.0 * fraction) as u32
fn duration_millisecond(i: &[u8]) -> IResult<&[u8], u32> {
let (i, mut digits) = take_while(is_digit)(i)?;
let mut l = digits.len();
if l > 3 {
digits = digits.get(0..3).unwrap();
}
let mut result = 0;
if l > 0 {
let digits = str::from_utf8(digits).unwrap(); // This can't panic, `digits` will only include digits.
result = digits.parse().unwrap();
}
while l < 3 {
result = result * 10;
l += 1;
}
Ok ((i,result))
}

fn duration_time(i: &[u8]) -> IResult<&[u8], (u32, u32, u32, u32)> {
Expand Down
16 changes: 16 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ fn test_date() {

#[test]
fn test_millisecond() {
let mut i=0;
while i < 1000 {
//regression test for pull request 36.
assert_eq!(
Ok(Time {
hour: 16,
minute: 43,
second: 0,
millisecond: i,
tz_offset_hours: 0,
tz_offset_minutes: 0
}),
time(format!("16:43:00.{:0>3}",i).as_str())
);
i+=1;
}
assert_eq!(
Ok(Time {
hour: 16,
Expand Down

0 comments on commit 297c655

Please # to comment.