Алгоритм преобразования серийной даты (Excel) в год-месяц-день в C ++ - PullRequest
1 голос
/ 22 июня 2019

Эта запись здесь предоставляет очень аккуратный и чистый C ++ алгоритм для преобразования последовательной даты (Excel) в ее явное представление год-месяц-день (и обратно).Позвольте мне вставить сжатую версию для удобства:

void ExcelSerialDateToDMY(int nSerialDate, int& nDay, int& nMonth, int& nYear)
{
    // Modified Julian to DMY calculation with an addition of 2415019
    int l  = nSerialDate + 68569 + 2415019;
    int n  = int(( 4 * l ) / 146097);
    l      = l - int(( 146097 * n + 3 ) / 4);
    int i  = int(( 4000 * ( l + 1 ) ) / 1461001);
    l      = l - int(( 1461 * i ) / 4) + 31;
    int j  = int(( 80 * l ) / 2447);
    nDay   = l - int(( 2447 * j ) / 80);
    l      = int(j / 11);
    nMonth = j + 2 - ( 12 * l );
    nYear  = 100 * ( n - 49 ) + i + l;
}

int DMYToExcelSerialDate(int nDay, int nMonth, int nYear)
{
    // DMY to Modified Julian calculated with an extra subtraction of 2415019.
    return int(( 1461 * ( nYear + 4800 + int(( nMonth - 14 ) / 12) ) ) / 4) +
           int(( 367 * ( nMonth - 2 - 12 * ( ( nMonth - 14 ) / 12 ) ) ) / 12) -
           int(( 3 * ( int(( nYear + 4900 + int(( nMonth - 14 ) / 12) ) / 100) ) ) / 4) +
           nDay - 2415019 - 32075;
}

Например

 2019-06-22 <--> 43638
 2000-01-28 <--> 36553
 1989-09-21 <--> 32772

Вышеупомянутый пост за 2002 год, поэтому мне интересно, есть ли альтернативные реализации, которыелучше.Под «лучше» я подразумеваю, например, быстрее, короче или менее скрытно.Или даже алгоритмы, которые, возможно, обеспечивают определенное количество предварительных вычислений (например, записывают 1 января серийную дату для желаемого диапазона лет, скажем, с 1900 по 2200, а затем выполняют быстрый поиск).

1 Ответ

2 голосов
/ 22 июня 2019

Алгоритмы, которые вы показываете, очень хороши.На моей платформе (clang ++ -O3) они создают объектный код без ветвей (конвейерные конвейеры) и без доступа к удаленной памяти (отсутствует кеш).В паре существует диапазон действия от -4800-03-01 до миллионов лет в будущем (большой диапазон).Во всем этом диапазоне они моделируют григорианский календарь.

Вот несколько альтернативных алгоритмов , которые очень похожи.Одно из отличий состоит в том, что у вас есть эпоха 19-01-01-01, а у тех, что я представляю, эпоха 1970-01-01.Однако очень легко отрегулировать эпоху по разности этих эпох (25569 дней), как показано ниже:

constexpr
std::tuple<int, unsigned, unsigned>
civil_from_days(int z) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    z += 719468 - 25569;
    const int era = (z >= 0 ? z : z - 146096) / 146097;
    const unsigned doe = static_cast<unsigned>(z - era * 146097);          // [0, 146096]
    const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // [0, 399]
    const int y = static_cast<int>(yoe) + era * 400;
    const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);                // [0, 365]
    const unsigned mp = (5*doy + 2)/153;                                   // [0, 11]
    const unsigned d = doy - (153*mp+2)/5 + 1;                             // [1, 31]
    const unsigned m = mp + (mp < 10 ? 3 : -9);                            // [1, 12]
    return std::tuple<int, unsigned, unsigned>(y + (m <= 2), m, d);
}

constexpr
int
days_from_civil(int y, unsigned m, unsigned d) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    y -= m <= 2;
    const int era = (y >= 0 ? y : y-399) / 400;
    const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
    return era * 146097 + static_cast<int>(doe) - (719468 - 25569);
}

Эти алгоритмы действительны в течение миллионов лет как вперед, так и назад (в том числе до -4800)-03-01).Хотя этот дополнительный диапазон не принесет вам большой выгоды, потому что григорианский календарь даже не начинался до 1582-10-15.

Я скомпилировал обе пары алгоритмов в macOS, используя clang++ -O3 -S, и набор, который у меня естьнемного меньший объектный код (около 10%).Несмотря на то, что они все такие маленькие, без ветвей и без кеш-памяти, попытка проверить эту выгоду путем измерения производительности была бы сложной задачей.

Я не нахожу читаемость одного набора выше, чем другого,Однако эта пара алгоритмов имеет раздражающе исчерпывающий вывод для тех, кому интересно, как работают эти алгоритмы, и модульные тесты, чтобы убедиться, что алгоритмы работают в диапазоне +/- 1 миллион лет.

Можно достичь очень незначительной производительности в вышеупомянутых алгоритмах, ограничив диапазон действия [2000-03-01, 2400-02-29], установив const int era = 5 в обоих алгоритмах.Я не тестировал производительность этой опции.Я ожидал бы, что такое усиление будет в уровне шума.

Или может быть какое-то незначительное преимущество в производительности, ограничивая диапазон от [0000-03-01, миллионы лет вперед], не учитывая отрицательные значенияera:

В civil_from_days:

const int era = z / 146097;

В days_from_civil:

const int era = y / 400;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...