使用 expl3 测试闰年的问题

使用 expl3 测试闰年的问题

对于最近的一个项目,我尝试熟悉 LaTeX3,但不幸的是,我遇到了一些困难并且找不到问题,这就是为什么我请求一个有用的提示。

在项目中,我想检查某个日期的给定年份是否是闰年。因此我定义了一个\isLeapYear宏来在后台调用 LaTeX3 函数。

检查给定年份是否为闰年的常用算法如下(来自维基百科):

闰年算法,©维基百科

鉴于此,我编写了以下代码。请记住,我才刚刚开始学习 LaTeX3,说实话,尽管我或多或少精通几种编程语言,但这仍然很有挑战性。

\prg_new_conditional:Npnn \is_leapyear:n #1 { p, T, F, TF }
{
    \int_set:Nn \l_tmpa_int { \curriculum_modulo:nn {#1} {4} }
    \tl_if_eq:nnTF {\l_tmpa_int} {0} {
        \int_set:Nn \l_tmpa_int { \curriculum_modulo:nn {#1} {100} }
        \tl_if_eq:nnTF {\l_tmpa_int} {0} {
            \int_set:Nn \l_tmpa_int { \curriculum_modulo:nn {#1} {400} }
            \tl_if_eq:nnTF {\l_tmpa_int} {0} {\prg_return_true}{\prg_return_false}
        }{\prg_return_true}
    } {\prg_return_false}
}

\newcommand{\isLeapYear}[1]
{
    \bool_if:nTF { \is_leapyear_p:n {#1} } {leap~year} {normal~year}
}

运行此代码\isLeapYear{2024}缺失数字,视为零。發生錯誤。

这是确切的错误信息。

! Missing number, treated as zero.
<to be read again> 
                   \__bool_=_0:
l.25     \isLeapYear{2024}
                          
A number should have been here; I inserted `0'.
(If you can't figure out why I needed to see a number,
look up `weird error' in the index to The TeXbook.)

我的方法是否存在重大问题,还是我遗漏了什么?我期待任何有用的提示,以增加我对 LaTeX3 的了解并帮助我进一步发展。

平均能量损失

\documentclass{article}

\ExplSyntaxOn

% https://www.alanshawn.com/latex3-tutorial/#implementing-modulo-operation
\cs_set:Npn \curriculum_modulo:nn #1#2 {
    \int_set:Nn \l_tmpa_int { \int_div_truncate:nn {#1}{#2} }
    \int_eval:n { (#1) - \l_tmpa_int * (#2) }
}

\prg_new_conditional:Npnn \is_leapyear:n #1 { p, T, F, TF }
{
    \int_set:Nn \l_tmpa_int { \curriculum_modulo:nn {#1} {4} }
    \tl_if_eq:nnTF {\l_tmpa_int} {0} {
        \int_set:Nn \l_tmpa_int { \curriculum_modulo:nn {#1} {100} }
        \tl_if_eq:nnTF {\l_tmpa_int} {0} {
            \int_set:Nn \l_tmpa_int { \curriculum_modulo:nn {#1} {400} }
            \tl_if_eq:nnTF {\l_tmpa_int} {0} {\prg_return_true}{\prg_return_false}
        }{\prg_return_true}
    } {\prg_return_false}
}

\newcommand{\isLeapYear}[1]
{
    \bool_if:nTF { \is_leapyear_p:n {#1} } {leap~year} {normal~year}
}

\ExplSyntaxOff

\begin{document}
    \isLeapYear{2024}
\end{document}

答案1

\bool_if:n(TF)要求其第一个 ( n) 参数中的所有操作都是可扩展的(在 中标有 ★ interface3)。您使用的是\int_set:Nn(赋值永远不可扩展)和,它们是不可扩展的。您可以轻松地使用而不是,以及而不是+\tl_if_eq:nnTF来重写该代码。\int_mod:nn\curriculum_modulo:nn\int_compare:nNnTF\int_set:Nn\tl_if_eq:nnTF

\int_compare:nNnTF期望作为(第 1 和第 3)个参数,因此,您可以给它提供一个(强制)可扩展的表达式(其计算结果为整数),<integer expressions>而不是给它一个变量。如果您查看int\int_mod:nninterface3(目前在部分20.1 整数表达式),您会发现它是可扩展的,并且计算结果为整数,因此您可以放心地在任何 LaTeX 需要整数的地方使用它。

\documentclass{article}

\ExplSyntaxOn

\prg_new_conditional:Npnn \is_leapyear:n #1 { p, T, F, TF }
  {
    \int_compare:nNnTF { \int_mod:nn {#1} { 4 } } = { 0 }
      {
        \int_compare:nNnTF { \int_mod:nn {#1} { 100 } } = { 0 }
          {
            \int_compare:nNnTF { \int_mod:nn {#1} { 400 } } = { 0 }
              { \prg_return_true: }
              { \prg_return_false: }
          }
          { \prg_return_true: }
      }
      { \prg_return_false: }
}

\newcommand{\isLeapYear}[1]
  {
    \bool_if:nTF { \is_leapyear_p:n {#1} } {leap~year} {normal~year}
  }

\ExplSyntaxOff

\begin{document}
    \isLeapYear{2024}
\end{document}

答案2

\int_if_odd:nTF您可以在其中使用\fp_eval:n

您可以在其中\fp_eval:n使用布尔表达式。

您可以使用这些东西来传递控制序列标记\prg_return_true:或控制序列标记,\prg_return_false:具体取决于所讨论的数字是否表示闰年。

\documentclass{article}

\ExplSyntaxOn
\prg_new_conditional:Npnn \is_leapyear:n #1 { p, T, F, TF } {
  \int_if_odd:nTF{
     % the \fp_eval-expression yields 1 for leap-years, 0 otherwise.
     \fp_eval:n {    ( \int_mod:nn {#1}{4}=0 && \int_mod:nn{#1}{25}!=0 )
                  || \int_mod:nn {#1}{400}=0   }
  }{\prg_return_true:}{\prg_return_false:}
}
\cs_new:Npn {\isLeapYear} #1 {
  #1~is~a~\bool_if:nTF { \is_leapyear_p:n {#1} } {leap} {normal}~year
}
\ExplSyntaxOff

\begin{document}
  \isLeapYear{2024}

  \isLeapYear{2000}

  \isLeapYear{1600}

  \isLeapYear{1700}

  \isLeapYear{2023}
\end{document}

在此处输入图片描述

从数学上讲,
( \int_mod:nn {#1}{4}=0 && \int_mod:nn{#1}{25}!=0 )
您也可以使用
( \int_mod:nn {#1}{4}=0 && \int_mod:nn{#1}{100}!=0 )
我不知道在 LaTeX/expl3 中哪一个计算得更快。

答案3

有两个不错的答案,但它们并不完整。例如,给定的代码将返回 1500 年不是闰年,这是错误的。同样,在英国(和殖民地),1700 年是闰年。

当然这也是一个小玩笑,但是下面的代码展示了该语言的其他特性,所以我认为这是一个很好的补充。

\documentclass{article}

\ExplSyntaxOn

\prg_new_conditional:Nnn \sam_is_leapyear_newstyle:n { p, T, F, TF }
 {
  \bool_lazy_or:nnTF
   {% #1 is divisible by 4 but not by 100
    \bool_lazy_and_p:nn
     { \int_compare_p:n { \int_mod:nn { #1 } { 4 } = 0 } }
     { \int_compare_p:n { \int_mod:nn { #1 } { 100 } > 0 } }
   }
   {% #1 is divisible by 400
    \int_compare_p:n { \int_mod:nn { #1 } { 400 } = 0 }
   }
   { \prg_return_true: }
   { \prg_return_false: }
 }
\prg_new_conditional:Nnn \sam_is_leapyear_oldstyle:n { p, T, F, TF }
 {
  \int_compare:nTF { \int_mod:nn { #1 } { 4 } = 0 }
   { \prg_return_true: }
   { \prg_return_false: }
 }
\prg_new_conditional:Nnn \sam_is_leapyear:nn { p, T, F, TF }
 {
  \int_compare:nTF { #2 >= #1 }
   {
    \sam_is_leapyear_newstyle:nTF { #2 } { \prg_return_true: } { \prg_return_false: }
   }
   {
    \sam_is_leapyear_oldstyle:nTF { #2 } { \prg_return_true: } { \prg_return_false: }
   }
 }

\NewExpandableDocumentCommand{\isleapyearTF}{O{1582}mmm}
 {
  \sam_is_leapyear:nnTF { #1 } { #2 } { #3 } { #4 }
 }

\ExplSyntaxOff

\begin{document}

2022: \isleapyearTF{2022}{leap year}{not leap year}

2024: \isleapyearTF{2024}{leap year}{not leap year}

2000: \isleapyearTF{2000}{leap year}{not leap year}

1600: \isleapyearTF{1600}{leap year}{not leap year}

1700: \isleapyearTF{1700}{leap year}{not leap year}

1800: \isleapyearTF{2023}{leap year}{not leap year}

1500: \isleapyearTF{1500}{leap year}{not leap year}

1700 (UK): \isleapyearTF[1752]{1700}{leap year}{not leap year}

1800 (UK): \isleapyearTF[1752]{1800}{leap year}{not leap year}

1700 (RU): \isleapyearTF[1918]{1700}{leap year}{not leap year}

1800 (RU): \isleapyearTF[1918]{1800}{leap year}{not leap year}

1900 (RU): \isleapyearTF[1918]{1900}{leap year}{not leap year}

\end{document}

在此处输入图片描述

可选参数是我们要检查的公历的采用年份(默认为 1582 年)。

答案4

这是一个使用该\int_case:函数的解决方案。在我看来,这允许更简洁、更易读的代码结构。

\documentclass{article}

\ExplSyntaxOn

\prg_new_conditional:Npnn \__my_int_if_leap_year:n #1 { p , TF , T , F }
  {
    \int_case:nnF {#1}
      {
        { \int_div_truncate:nn {#1} {400} * 400 } { \prg_return_true:  }
        { \int_div_truncate:nn {#1} {100} * 100 } { \prg_return_false: }
        { \int_div_truncate:nn {#1} {  4} *   4 } { \prg_return_true:  }
      }
      { \prg_return_false: }
  }
% example user command
\NewExpandableDocumentCommand \IfLeapYearTF { m m m }
  { \__my_int_if_leap_year:nTF {#1} {#2} {#3} }

\ExplSyntaxOff

\begin{document}

\IfLeapYearTF{1999}{TRUE}{FALSE} % Expands to "FALSE"

\IfLeapYearTF{2000}{TRUE}{FALSE} % Expands to "TRUE"

\IfLeapYearTF{1900}{TRUE}{FALSE} % Expands to "FALSE"

\IfLeapYearTF{2024}{TRUE}{FALSE} % Expands to "TRUE"

\end{document}

相关内容