Comment by adrianmonk
3 months ago
A less radical optimization that still might improve on the original:
const fn ordinal_date_to_calendar_date(year: i32, ordinal: u16) -> (i32, u8, u8) {
/// The number of days up to and including the given month. Common years
/// are first, followed by leap years.
const CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP: [[u16; 13]; 2] = [
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365],
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366],
];
let days = CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP[is_leap_year(year) as usize];
// Approximation which is either exactly right or one too small.
let mut month = (ordinal - 1) / 31;
// Fix the approximation if necessary.
if ordinal > days[(month + 1) as usize] {
month += 1;
}
(year, (month + 1) as u8, (ordinal - days[month as usize]) as u8)
}
The basic idea is to approximate simply by dividing by 31, then correct the error.
Because no month has more than 31 days, the approximation cannot be too large. And because only a little error accumulates, if it is too small, it will only be too small by 1. So only a single check is needed to correct the error.
Also, if you want to avoid a divide instruction, you can switch to:
let mut month (ordinal - 1) >> 5;
Then you're dividing by 32 but the error is still small enough for it to work the same.
(Note that there's no bounds checking, which might be a regression compared to the original. Also, I've made the arrays bigger to get rid of special cases.)
I benchmarked this out of curiosity. Even using the bitshift and eliding the bounds checks (unsoundly), it's 22% slower than my optimized version.