经过昨天的聊天讨论,我决定编写一个反正切函数,将角度标准化为 [0,2\pi] 范围内(当然,以度为单位,[0,360] 范围内)。这样做很容易。我的问题是,除了将其原生添加到 LaTeX3 中,有没有更好的方法可以做到这一点,以便可以对其进行评估\fp_eval:n
,例如,
\fp_eval:n { atannorm(0.5,sqrt(3)/2)}
?
% !TEX program = lualatexmk
% !TEX encoding = UTF-8 Unicode
\documentclass{article}
\ExplSyntaxOn
\cs_new_protected:Nn \joe_atannormd:n {%
% create and set a temporary list to #1
\clist_set:Nn \l_tmpa_clist { #1 }
% need to extract the first argument
\fp_set:Nn \l_tmpa_fp { \clist_item:Nn \l_tmpa_clist { 1 } }
% normalize to [0,360] if the first argument < 0
\fp_eval:n { \fp_compare_p:n { \l_tmpa_fp < 0 } ? atand(#1) + 360 : atand(#1) }
}%
\NewDocumentCommand{\atannormd}{ r() }{%
\joe_atannormd:n { #1 }
}%
\cs_new_protected:Nn \joe_atannorm:n {%
% create and set a temporary list to #1
\clist_set:Nn \l_tmpa_clist { #1 }
% need to extract the first argument
\fp_set:Nn \l_tmpa_fp { \clist_item:Nn \l_tmpa_clist { 1 } }
% normalize to [0,2pi] if the first argument < 0
\fp_eval:n { \fp_compare_p:n { \l_tmpa_fp < 0 } ? atan(#1) + 2*pi : atan(#1) }
}%
\NewDocumentCommand{\atannorm}{ r() }{%
\joe_atannorm:n { #1 }
}%
\ExplSyntaxOff
\begin{document}
Normalized to \( 2\pi \):
\( \atannorm(0,1) \)
\( \atannorm(1,1) \)
\( \atannorm(1,0) \)
\( \atannorm(1,-1) \)
\( \atannorm(0,-1) \)
\( \atannorm(-1,-1) \)
\( \atannorm(-1,0) \)
Normalized to \( 360^\circ \):
\( \atannormd(0,1)^\circ \)
\( \atannormd(1,1)^\circ \)
\( \atannormd(1,0)^\circ \)
\( \atannormd(1,-1)^\circ \)
\( \atannormd(0,-1)^\circ \)
\( \atannormd(-1,-1)^\circ \)
\( \atannormd(-1,0)^\circ \)
\( \atannormd(0.5,sqrt(3)/2)^\circ \)
\end{document}
答案1
您可以定义atannorm
为“fp 字”,这样\fp_eval:n { atannorm(#1,#2) }
可以工作,但这需要使用l3fp
内部函数,所以这不是一个好主意。以在函数调用中多一个反斜杠为代价,您可以定义一个可扩展函数,\atannorm
其必需参数由 分隔()
:
\NewExpandableDocumentCommand \atannorm { r() }
{ \joe_atannorm:nnn {#1} { atan } { 2*pi } }
(第二和第三个参数是为了您可以根据相同的内部定义\atannorm
和\atannormd
)然后在逗号处拆分:
\cs_new:Npn \joe_atannorm:nnn #1
{ \__joe_atannorm:wwnn #1 \scan_stop: }
并将其传递给将所有内容组合在一起的宏:
\cs_new:Npn \__joe_atannorm:wwnn #1 , #2 \scan_stop: #3 #4
{ \use:e { ( #3 (#1,#2) \fp_compare:nNnT {#1} < { 0 } { + #4 } ) } }
代码不会检查输入是否确实包含,,
如果不包含,则会抛出低级错误(如果需要,可以很容易地添加)。诀窍\use:e
就在这里,因为l3fp
逐步扩展可能会相当慢,所以可以\use:e
加快速度。添加了一组额外的括号,这样就可以\atannorm(-1,-1)*2
按预期工作。
完整代码如下:
\documentclass{article}
\usepackage{xfp}
\ExplSyntaxOn
\NewExpandableDocumentCommand \atannorm { r() }
{ \joe_atannorm:nnn {#1} { atan } { 2*pi } }
\NewExpandableDocumentCommand \atannormd { r() }
{ \joe_atannorm:nnn {#1} { atand } { 360 } }
\cs_new:Npn \joe_atannorm:nnn #1
{ \__joe_atannorm:wwnn #1 \scan_stop: }
\cs_new:Npn \__joe_atannorm:wwnn #1 , #2 \scan_stop: #3 #4
{ \use:e { ( #3 (#1,#2) \fp_compare:nNnT {#1} < { 0 } { + #4 } ) } }
\ExplSyntaxOff
\begin{document}
Normalized to \( 2\pi \):
\( \fpeval{ \atannorm (0,1) } \)
\( \fpeval{ \atannorm (1,1) } \)
\( \fpeval{ \atannorm (1,0) } \)
\( \fpeval{ \atannorm (1,-1) } \)
\( \fpeval{ \atannorm (0,-1) } \)
\( \fpeval{ \atannorm (-1,-1) } \)
\( \fpeval{ \atannorm (-1,0) } \)
Normalized to \( 360^\circ \):
\( \fpeval{ \atannormd (0,1) }^\circ \)
\( \fpeval{ \atannormd (1,1) }^\circ \)
\( \fpeval{ \atannormd (1,0) }^\circ \)
\( \fpeval{ \atannormd (1,-1) }^\circ \)
\( \fpeval{ \atannormd (0,-1) }^\circ \)
\( \fpeval{ \atannormd (-1,-1) }^\circ \)
\( \fpeval{ \atannormd (-1,0) }^\circ \)
\( \fpeval{ \atannormd (0.5,sqrt(3)/2) }^\circ \)
\end{document}