换算表对于将单位从一个系统转换为另一个系统非常有用。例如,要将 1 m^2/s 转换为 m^2/hr,我们写成:
我通常使用以下tabular
环境来生成:
\documentclass{article}
\usepackage{siunitx}
\begin{document}
\begin{center}
\begin{tabular}{c|c|c}
\SI{1}{m^2} & \SI{60}{s} & \SI{60}{min} \\ \hline
\si{s} & \si{min} & \si{hr}
\end{tabular} = \SI{3600}{m^2/hr}
\end{center}
\end{document}
我的问题是:如何创建命令/宏/包来自动执行此操作,如下所示:
- 我将按顺序输入单位;例如,
\conv{1 m^2/s -> 60 s/min -> 60 min/h}
给出所需的表格。 - 该表应该是可扩展的,也就是说,它可以容纳所需的任意数量的转换。
- 一个用于取消连续单位的选项,例如 和
s
,s
以及每个相邻单元格中的min
和。min
答案1
也许这接近您想要的。最终单位是根据第一个分子和最后一个分母计算得出的。如果不是这样,您必须在 的可选参数中指定单位\conv
,例如
\conv[<settings>]{...}[<unit>]
转换是根据分子和分母的值自动计算的。前导可选参数用于siunitx
设置,这将影响左侧和右侧。该命令可以进入文本或数学模式,而不会发生任何变化。
\documentclass{article}
\usepackage{xparse,siunitx,l3regex}
\ExplSyntaxOn
\NewDocumentCommand{\conv}{O{}mo}
{
\group_begin:
\sisetup{#1}
\IfNoValueTF{#3}
{
\jak_conv:nn { #2 } { \jak_conv_first_last: }
}
{
\jak_conv:nn { #2 } { #3 }
}
\group_end:
}
\seq_new:N \l_jak_conv_input_seq
\seq_new:N \l_jak_conv_output_top_seq
\seq_new:N \l_jak_conv_output_bot_seq
\seq_new:N \l__jak_conv_temp_seq
\tl_new:N \l__jak_conv_temp_tl
\cs_new_protected:Nn \jak_conv:nn
{
\seq_clear:N \l_jak_conv_output_top_seq
\seq_clear:N \l_jak_conv_output_bot_seq
\seq_set_split:Nnn \l_jak_conv_input_seq { * } { #1 }
\seq_map_inline:Nn \l_jak_conv_input_seq
{
\seq_set_split:Nnn \l__jak_conv_temp_seq { / } { ##1 }
\tl_set:Nx \l__jak_conv_temp_tl { \seq_item:Nn \l__jak_conv_temp_seq { 1 } }
\regex_replace_once:nnN { ([\d\.]*) (.*) } { \c{\__jak_conv_do:nn}\cB\{ \1 \cE\} \cB\{ \2 \cE\} } \l__jak_conv_temp_tl
\seq_put_right:NV \l_jak_conv_output_top_seq \l__jak_conv_temp_tl
\tl_set:Nx \l__jak_conv_temp_tl { \seq_item:Nn \l__jak_conv_temp_seq { 2 } }
\regex_replace_once:nnN { ([\d\.]*) (.*) } { \c{\__jak_conv_do:nn}\cB\{ \1 \cE\} \cB\{ \2 \cE\} } \l__jak_conv_temp_tl
\seq_put_right:NV \l_jak_conv_output_bot_seq \l__jak_conv_temp_tl
}
\cs_set_eq:NN \__jak_conv_do:nn \__jak_conv_do_si:nn
\begin{tabular}{c *{\int_eval:n { \seq_count:N \l_jak_conv_output_top_seq - 1 }}{|c}}
\seq_use:Nn \l_jak_conv_output_top_seq { & } \\
\hline
\seq_use:Nn \l_jak_conv_output_bot_seq { & }
\end{tabular}
\cs_set_eq:NN \__jak_conv_do:nn \__jak_conv_do_extract:nn
\tl_set:Nx \l__jak_conv_temp_tl
{
\seq_item:Nn \l_jak_conv_output_top_seq { 1 }
/
\seq_item:Nn \l_jak_conv_output_bot_seq { -1 }
}
\tl_set:Nx \l__jak_conv_temp_tl { \l__jak_conv_temp_tl }
\ensuremath
{
{}=
\cs_set_eq:NN \__jak_conv_do:nn \__jak_conv_do_eval:nn
\SI
{
\fp_eval:n
{
(\seq_use:Nn \l_jak_conv_output_top_seq { * })
/
(\seq_use:Nn \l_jak_conv_output_bot_seq { * })
}
}
{ #2 }
}
}
\cs_new:Nn \__jak_conv_do_eval:nn
{
\tl_if_blank:nTF { #1 } { 1 } { #1 }
}
\cs_new:Nn \__jak_conv_do_extract:nn { #2 }
\cs_new:Nn \__jak_conv_do_si:nn
{
\tl_if_blank:nTF { #1 } { \si{#2} } { \SI{#1}{#2} }
}
\cs_new:Nn \jak_conv_first_last: { \tl_use:N \l__jak_conv_temp_tl }
\ExplSyntaxOff
\begin{document}
\conv{1 m^2/s * 60 s/min * 60 min/h}
\medskip
\conv[round-mode=places,round-precision=6]{1 m^2/h * h/60min * min/60s }
\medskip
\conv{299792458 m/s * 60s/min * 60min/h * 24h/d * 365.25d/y}
\end{document}
注意光年的计算是准确的。
后续版本增强功能
变化是:在输入中,|
用于分隔分子和分母,因此m/s | h
可以不需特别注意而指定。调用\conv*
将抑制最终结果的打印。
\documentclass{article}
\usepackage{xparse,siunitx,l3regex}
\ExplSyntaxOn
\NewDocumentCommand{\conv}{sO{}mo}
{
\group_begin:
\IfBooleanF{#1}{ \bool_set_true:N \l__jack_conv_print_bool }
\sisetup{#2}
\IfNoValueTF{#4}
{
\jak_conv:nn { #3 } { \jak_conv_first_last: }
}
{
\jak_conv:nn { #3 } { #4 }
}
\group_end:
}
\seq_new:N \l_jak_conv_input_seq
\seq_new:N \l_jak_conv_output_top_seq
\seq_new:N \l_jak_conv_output_bot_seq
\seq_new:N \l__jak_conv_temp_seq
\tl_new:N \l__jak_conv_temp_tl
\bool_new:N \l__jack_conv_print_bool
\cs_new_protected:Nn \jak_conv:nn
{
\seq_clear:N \l_jak_conv_output_top_seq
\seq_clear:N \l_jak_conv_output_bot_seq
\seq_set_split:Nnn \l_jak_conv_input_seq { * } { #1 }
\seq_map_inline:Nn \l_jak_conv_input_seq
{
\seq_set_split:Nnn \l__jak_conv_temp_seq { | } { ##1 }
\tl_set:Nx \l__jak_conv_temp_tl { \seq_item:Nn \l__jak_conv_temp_seq { 1 } }
\regex_replace_once:nnN { ([\d\.]*) (.*) } { \c{\__jak_conv_do:nn}\cB\{ \1 \cE\} \cB\{ \2 \cE\} } \l__jak_conv_temp_tl
\seq_put_right:NV \l_jak_conv_output_top_seq \l__jak_conv_temp_tl
\tl_set:Nx \l__jak_conv_temp_tl { \seq_item:Nn \l__jak_conv_temp_seq { 2 } }
\regex_replace_once:nnN { ([\d\.]*) (.*) } { \c{\__jak_conv_do:nn}\cB\{ \1 \cE\} \cB\{ \2 \cE\} } \l__jak_conv_temp_tl
\seq_put_right:NV \l_jak_conv_output_bot_seq \l__jak_conv_temp_tl
}
\cs_set_eq:NN \__jak_conv_do:nn \__jak_conv_do_si:nn
\begin{tabular}{c *{\int_eval:n { \seq_count:N \l_jak_conv_output_top_seq - 1 }}{|c}}
\seq_use:Nn \l_jak_conv_output_top_seq { & } \\
\hline
\seq_use:Nn \l_jak_conv_output_bot_seq { & }
\end{tabular}
\cs_set_eq:NN \__jak_conv_do:nn \__jak_conv_do_extract:nn
\tl_set:Nx \l__jak_conv_temp_tl
{
\seq_item:Nn \l_jak_conv_output_top_seq { 1 }
/
\seq_item:Nn \l_jak_conv_output_bot_seq { -1 }
}
\tl_set:Nx \l__jak_conv_temp_tl { \l__jak_conv_temp_tl }
\bool_if:NT \l__jack_conv_print_bool
{
\ensuremath
{
{}=
\cs_set_eq:NN \__jak_conv_do:nn \__jak_conv_do_eval:nn
\SI
{
\fp_eval:n
{
(\seq_use:Nn \l_jak_conv_output_top_seq { * })
/
(\seq_use:Nn \l_jak_conv_output_bot_seq { * })
}
}
{ #2 }
}
}
}
\cs_new:Nn \__jak_conv_do_eval:nn
{
\tl_if_blank:nTF { #1 } { 1 } { #1 }
}
\cs_new:Nn \__jak_conv_do_extract:nn { #2 }
\cs_new:Nn \__jak_conv_do_si:nn
{
\tl_if_blank:nTF { #1 } { \si{#2} } { \SI{#1}{#2} }
}
\cs_new:Nn \jak_conv_first_last: { \tl_use:N \l__jak_conv_temp_tl }
\ExplSyntaxOff
\begin{document}
\conv{1 m^2|s * 60 s|min * 60 min|h}
\medskip
\conv[round-mode=places,round-precision=6]{1 m^2|h * h|60min * min|60s }
\medskip
$\conv{299792458 m|s * 60s|min * 60min|h * 24h|d * 365.25d|y}$
\medskip
$\conv*{299792458 m|s * 60s|min * 60min|h * 24h|d * 365.25d|y}$
\end{document}
答案2
这将根据语法等拆分列表 number;units->number;units
,并在最后显示图形结果以及表中的单位,但是不是最后一个单元(我现在还不知道!)
使用可选参数设置正确的最终单位!
这绝对不是万无一失的!
\documentclass{article}
\usepackage{siunitx}
\usepackage{multirow}
\usepackage{expl3}
\ExplSyntaxOn
\seq_new:N \l__unitconv_unit_seq
\seq_new:N \l__unitconv_numbers_seq
\cs_new:Nn \unitsplit:n {%
\seq_set_split:Nnn \l_tmpc_seq {/} { #1 }
\seq_set_eq:NN \l_tmpd_seq \l_tmpc_seq
\seq_map_inline:Nn \l_tmpd_seq { \seq_pop_left:NN \l_tmpc_seq }
\si{\seq_item:Nn \l_tmpc_seq {2}}
}
% Weird splitting
\cs_new:Nn \numberunit_split:n {
\seq_set_split:Nnn \l_tmpa_seq {;} { #1 }
\seq_set_split:Nnn \l_tmpc_seq {/} { #1 }
\seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
\seq_set_eq:NN \l_tmpd_seq \l_tmpc_seq
\seq_map_inline:Nn \l_tmpd_seq {
\seq_pop_left:NN \l_tmpc_seq \l_tmpb_tl
} % remove the number from the entry
\si{\l_tmpa_tl \seq_item:Nn \l_tmpa_seq {1}} % display first entry
}
\cs_new:Nn \numbersplit:n {%
\seq_set_split:Nnn \l_tmpa_seq {;} { #1 }
\seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
\seq_set_eq:NN \l_tmpb_seq \l_tmpa_seq
\seq_map_inline:Nn \l_tmpb_seq {
\seq_gput_right:Nx \l__unitconv_numbers_seq {\l_tmpa_tl} %{\seq_item:Nn \l_tmpa_seq {1}}
}
}
\cs_new:Nn \multiply_all_numbers:nn {%
\fp_set:Nn \l_tmpa_fp 1
\seq_map_inline:Nn #1 {% Multiplication loop
\fp_gset:Nn \l_tmpb_fp { \fp_eval:n {\l_tmpa_fp * ##1}}
\fp_set_eq:NN \l_tmpa_fp \l_tmpb_fp
}
\SI{\fp_use:N \l_tmpb_fp}{#2} % Display the result
}
\NewDocumentCommand{\conv}{ov}{%
\seq_clear:N \l__unitconv_unit_seq
\seq_clear:N \l__unitconv_numbers_seq
\seq_set_split:Nnn \l__unitconv_unit_seq {->} {#2} % store the input first and split it into several parts
\int_set:Nn \l_tmpa_int {\seq_count:N \l__unitconv_unit_seq} % How man input parts do we have?
\seq_map_inline:Nn \l__unitconv_unit_seq {\numbersplit:n {##1}} % Get the figures
%% Key value interface here later on?
\IfValueTF{#1}{% Has it an optional argument -> yes
\tl_set:Nn \l_tmpa_tl {#1} % Store it as the possible unit
}{%
\tl_set:Nn \l_tmpa_tl {unit-to-be-specified} % Ideally this would be autodetermined!
}
\par
% Display the stuff -> convert later on another macro
\begin{tabular}{*{\int_use:N \l_tmpa_int}{c|}c}
\seq_map_inline:Nn \l__unitconv_unit_seq {%
\numberunit_split:n{##1} &
}% Now multiply all numbers
\multirow{2}{*}{~=~\multiply_all_numbers:nn {\l__unitconv_numbers_seq} {\l_tmpa_tl }} \tabularnewline
\cline{1-\int_use:N \l_tmpa_int}
\seq_map_inline:Nn \l__unitconv_unit_seq { \unitsplit:n{##1} & } \tabularnewline
\end{tabular}
}
\ExplSyntaxOff
\begin{document}
\conv{1;m/s -> 60;s/min -> 60;min/h}
\conv{ 60;s/min -> 60;min/h -> 24; h/d}
\conv{ 60;s/min -> 60;min/h -> 24; h/d -> 365;d/y}
\conv[km/s]{ 3000000;km/s -> 60;s/min -> 60;min/h -> 24; h/d -> 365;d/y}
\end{document}
答案3
这是一个玩具版本,我认为它与@egreg 的方法非常相似,只是它不使用任何包,而且 最终单位是通过取消分子和分母中的单位而获得的,与它们出现的顺序无关(下面的两个例子表明最后一个单位是由第一个分子和最后一个分母构成的,但它比这更通用)。然而它有令人讨厌的局限性:
仅限于整数
容易发生算术溢出!
为了克服 1.,我需要编写一个例程来收集单位前面的数字,但我不知道允许的格式。这是 @egreg 的答案在 的帮助下完成的l3regex
。在这里,我们只做一个基本的TeX
计数分配。
对于 2.,任何数学引擎都可以。因此,我的小建议只是展示在没有包的情况下可以做什么。
第三个限制是它要求每个项目的形状为number unittop/unitbottom
。可以扩展代码以允许项目的形状number unit
为不带斜线。
\documentclass{article}
\usepackage{siunitx}
% \conv{1 m^2/s -> 60 s/min -> 60 min/h}
\makeatletter
\def\zapspaces #1 #2{#1#2\zapspaces }
\def\conv #1{\begin{center}
\count255 1
\let\conv@rowi\empty
\let\conv@rowii\empty
\let\conv@tops\empty
\let\conv@bots\empty
\def\conv@format{\@gobble}%
\let\JAK\relax\let\JBK\relax
\conv@ #1->\JAK}
\def\conv@ {\afterassignment\conv@a\count\z@=}
% I will assume that we systematically have unit/unit
% this could be modified
\def\conv@a #1->{\multiply\count255 by \count\z@
\conv@b #1/}
\def\conv@b #1/{\edef\conv@utop{\zapspaces #1 \@gobble}\conv@c }
\def\conv@c #1/{\edef\conv@ubot{\zapspaces #1 \@gobble}\conv@d }
\def\conv@gobtilJAK #1\JAK {}
\def\conv@d #1{\edef\conv@rowi{\unexpanded\expandafter{\conv@rowi}%
&\noexpand\SI{\the\count\z@}{\conv@utop}}%
\edef\conv@rowii{\unexpanded\expandafter{\conv@rowii}%
&\noexpand\si{\conv@ubot}}%
\edef\conv@format{\conv@format |c}%
\edef\conv@tops {\unexpanded\expandafter{\conv@tops}%
\JAK{\conv@utop}}%
\edef\conv@bots {\unexpanded\expandafter{\conv@bots}%
\JBK{\conv@ubot}}%
\conv@gobtilJAK #1\conv@q \JAK
\afterassignment\conv@a\count\z@=#1}
\def\conv@q\JAK\afterassignment\conv@a\count\z@=\JAK
{%
\edef\conv@value {\the\count255}%
\let\conv@@bots\conv@bots
\let\conv@bots\empty
\def\JBK ##1{\let\conv@@tops\conv@tops
\let\conv@tops\empty
\def\conv@tmpbot{##1}%
\def\conv@keepperhaps{\expandafter\def\expandafter\conv@bots
\expandafter{\conv@bots\JBK {##1}}}%
\conv@r}%
\conv@@bots
\conv@finish
}
\def\conv@r
{%
\def\JAK ##1{\def\conv@tmptop {##1}%
\ifx\conv@tmptop\conv@tmpbot
\let\conv@keepperhaps\empty
\def\conv@tmpbot{\NONE}%
\else
\expandafter\def\expandafter\conv@tops
\expandafter{\conv@tops\JAK {##1}}%
\fi}%
\conv@@tops
\conv@keepperhaps
}%
\def\conv@finish
{%
\def\JAK ##1{##1 }%
\def\JBK ##1{##1 }%
\expandafter\tabular\expandafter{\conv@format}%
\expandafter\@gobble\conv@rowi \\ \hline
\expandafter\@gobble\conv@rowii
\endtabular
\space= \SI{\conv@value}{\conv@tops/\conv@bots}
\end{center}
}
\makeatother
\begin{document}
% \begin{center}
% \begin{tabular}{c|c|c}
% \SI{1}{m^2} & \SI{60}{s} & \SI{60}{min} \\ \hline
% \si{s} & \si{min} & \si{hr}
% \end{tabular} = \SI{3600}{m^2/hr}
% \end{center}
\conv{1 m^2/s -> 60 s/min -> 60 min/h}
% can't do that !
%\conv{299792458 m/s * 60s/min * 60min/h * 24h/d * 365.25d/y}
\conv{ 60 s/min -> 60 min/h -> 24 h/d -> 365 d/y}
\end{document}
输出: