对于最近的一个项目,我尝试熟悉 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:nn
interface3
(目前在部分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}