[Tex/LaTex] Discrete range/interval in siunitx

sirangesiunitx

Using siunitx, I can type $V = \SIrange[range-phrase=\text{ to },range-units=single]{100}{125}{\kilo\m\per\hour}$ to get

enter image description here

However, numerically speaking, I have to specify the step of the range in order to know exactly each speed value I will do my calculations at.

Therefore, I would like to understand how to create a command, e.g. \SIsteps, where it accepts the following arguments:

<start>,

<step> (optional and its default is 1) it can be integer or float,

<size> (optional) it is the total number of the elements in the range/interval

<end>, and

<unit>.

So, for example, all the following codes should have the same output of

enter image description here

The speed range is $V = \SIsteps[step=5]{100}{125}{\km}$

The speed range is $V = \SIsteps[size=6]{100}{}{\km}$

The speed range is $V = \SIsteps[size=6]{}{125}{\km}$

\documentclass{article}
\usepackage{unicode-math,siunitx}
\sisetup{
    load-configurations = abbreviations,
    inter-unit-product = \ensuremath{{\hspace{-0.5ex}}\cdot{\hspace{-0.5ex}}}
}
\setmathfont{Cambria Math}
\begin{document}
The speed range is $V = \left\{100,105,\ldots,125\right\}~\si{\km\per\hour}$
\end{document}

Best Answer

The macro \SIsteps has

  1. an optional argument for options to be passed to \num
  2. a mandatory argument with options for the steps
  3. an optional argument with options for \si
  4. a mandatory argument for the unit

As you requested, the default step is 1, but it can be specified either in the options or (following normal scope rules) with \stepsetup.

The keys are

  • start to set the starting point
  • end to set the end point
  • step to set the step
  • size to set the number of items to show
  • full to specify we want a full display of the values (default false)
  • sep to specify the separator between items (default comma)

You have better to specify two keys among start, end and size, but don't specify all three of them.

\documentclass{article}
\usepackage{siunitx,xfp}

\sisetup{
    load-configurations = abbreviations,
    inter-unit-product = \ensuremath{{\cdot}}
}


\ExplSyntaxOn

\keys_define:nn { diaa/steps }
 {
  start .fp_set:N   = \l__diaa_steps_start_fp,
  start .initial:n  = -\c_inf_fp,
  end   .fp_set:N   = \l__diaa_steps_end_fp,
  end   .initial:n  = \c_inf_fp,
  step  .fp_set:N   = \l__diaa_steps_step_fp,
  step  .initial:n  = 1,
  size  .int_set:N  = \l__diaa_steps_size_int,
  full  .bool_set:N = \l__diaa_steps_full_bool,
  full  .initial:n  = false,
  full  .default:n  = true,
  sep   .tl_set:N   = \l__diaa_steps_sep_tl,
  sep   .initial:n  = {,},
 }

\NewDocumentCommand{\SIsteps}{O{}mO{}m}
 {
  \group_begin:
  \keys_set:nn { diaa/steps } { #2 }
  \int_compare:nT { \l__diaa_steps_size_int > 0 }
   {% size has been specified
    \fp_compare:nTF { \l__diaa_steps_end_fp = \c_inf_fp }
     {% end has not been specified
      \fp_set:Nn \l__diaa_steps_end_fp
       {
        \l__diaa_steps_start_fp + (\l__diaa_steps_size_int-1)*\l__diaa_steps_step_fp
       }
     }
     {% start has not been specified
      \fp_set:Nn \l__diaa_steps_start_fp
       {
        \l__diaa_steps_end_fp - (\l__diaa_steps_size_int-1)*\l__diaa_steps_step_fp
       }
     }
   }
  \ensuremath
   {
    \{
    \bool_if:NTF \l__diaa_steps_full_bool
     {% full display
      \fp_step_inline:nnnn
       { \l__diaa_steps_start_fp } % start
       { \l__diaa_steps_step_fp } % step
       { \l__diaa_steps_end_fp - \l__diaa_steps_step_fp } % end
       {
        \num[#1]{ \fp_eval:n { ##1 } } \l__diaa_steps_sep_tl
       }
       \num[#1]{ \fp_eval:n { \l__diaa_steps_end_fp } }
     }
     {% short display
      \num[#1]{ \fp_eval:n { \l__diaa_steps_start_fp } }
      \l__diaa_steps_sep_tl
      \num[#1]{ \fp_eval:n { \l__diaa_steps_start_fp + \l__diaa_steps_step_fp } }
      \l__diaa_steps_sep_tl
      \dots\l__diaa_steps_sep_tl
      \num[#1]{ \fp_eval:n { \l__diaa_steps_end_fp } }
     }
    \}
    \,\si[#3]{#4}
   }
  \group_end:
 }
\NewDocumentCommand{\stepsetup}{m}
 {
  \keys_set:nn { diaa/steps } { #1 }
 }

\ExplSyntaxOff

\begin{document}

The speed range is $V = \SIsteps{step=5,start=100,end=125}{\km\per\hour}$

The speed range is $V = \SIsteps{size=6,start=100}{\km\per\hour}$ % step is one

The speed range is $V = \SIsteps{size=6,start=100,step=5}{\km\per\hour}$

\stepsetup{step=5}

The speed range is $V = \SIsteps{size=6,end=125}{\km\per\hour}$

The speed range is $V = \SIsteps{size=6,end=125,full,sep={;}}{\km\per\hour}$

\end{document}

enter image description here

The explicit \, between the brace and the unit might be \l__siunitx_number_unit_product_tl, but this is an internal of siunitx and it's bad practice to use it. This might possibly be offered as a user level token list in the future.

You can also add support for angles (but not minutes and seconds, for that you'd be charged a thousand times).

\documentclass{article}
\usepackage{siunitx,xfp}

\sisetup{
    load-configurations = abbreviations,
    inter-unit-product = \ensuremath{{\cdot}}
}


\ExplSyntaxOn

\keys_define:nn { diaa/steps }
 {
  start .fp_set:N   = \l__diaa_steps_start_fp,
  start .initial:n  = -\c_inf_fp,
  end   .fp_set:N   = \l__diaa_steps_end_fp,
  end   .initial:n  = \c_inf_fp,
  step  .fp_set:N   = \l__diaa_steps_step_fp,
  step  .initial:n  = 1,
  size  .int_set:N  = \l__diaa_steps_size_int,
  full  .bool_set:N = \l__diaa_steps_full_bool,
  full  .initial:n  = false,
  full  .default:n  = true,
  sep   .tl_set:N   = \l__diaa_steps_sep_tl,
  sep   .initial:n  = {,},
 }

\NewDocumentCommand{\SIsteps}{O{}mO{}m}
 {
  \group_begin:
  \diaa_sisteps:nnnnNn { #1 } { #2 } { #3 } { #4 }
                       \num 
                       { \l__siunitx_number_unit_product_tl \si[#3]{#4} }
  \group_end:
 }
\NewDocumentCommand{\anglesteps}{O{}mO{}}
 {
  \group_begin:
  \diaa_sisteps:nnnnNn { #1 } { #2 } { #3 } { }
                       \ang 
                       { }
  \group_end:
 }

\cs_new_protected:Nn \diaa_sisteps:nnnnNn
 {
  \keys_set:nn { diaa/steps } { #2 }
  \int_compare:nT { \l__diaa_steps_size_int > 0 }
   {% size has been specified
    \fp_compare:nTF { \l__diaa_steps_end_fp = \c_inf_fp }
     {% end has not been specified
      \fp_set:Nn \l__diaa_steps_end_fp
       {
        \l__diaa_steps_start_fp + (\l__diaa_steps_size_int-1)*\l__diaa_steps_step_fp
       }
     }
     {% start has not been specified
      \fp_set:Nn \l__diaa_steps_start_fp
       {
        \l__diaa_steps_end_fp - (\l__diaa_steps_size_int-1)*\l__diaa_steps_step_fp
       }
     }
   }
  \ensuremath
   {
    \{
    \bool_if:NTF \l__diaa_steps_full_bool
     {% full display
      \fp_step_inline:nnnn
       { \l__diaa_steps_start_fp } % start
       { \l__diaa_steps_step_fp } % step
       { \l__diaa_steps_end_fp - \l__diaa_steps_step_fp } % end
       {
        #5[#1]{ \fp_eval:n { ##1 } } \l__diaa_steps_sep_tl
       }
       #5[#1]{ \fp_eval:n { \l__diaa_steps_end_fp } }
     }
     {% short display
      #5[#1]{ \fp_eval:n { \l__diaa_steps_start_fp } }
      \l__diaa_steps_sep_tl
      #5[#1]{ \fp_eval:n { \l__diaa_steps_start_fp + \l__diaa_steps_step_fp } }
      \l__diaa_steps_sep_tl
      \fp_compare:nT
       { (\l__diaa_steps_end_fp-\l__diaa_steps_start_fp)/\l__diaa_steps_step_fp > 3 }
       { \dots\l__diaa_steps_sep_tl }
      #5[#1]{ \fp_eval:n { \l__diaa_steps_end_fp } }
     }
    \}
    #6
   }
 }
\NewDocumentCommand{\stepsetup}{m}
 {
  \keys_set:nn { diaa/steps } { #1 }
 }

\ExplSyntaxOff

\begin{document}

The speed range is $V = \SIsteps{step=5,start=100,end=125}{\km\per\hour}$

The speed range is $V = \SIsteps{size=6,start=100}{\km\per\hour}$ % step is one

The speed range is $V = \SIsteps{size=6,start=100,step=5}{\km\per\hour}$

The speed range is $V = \SIsteps{size=6,start=84.37,step=5}{\km\per\hour}$

\stepsetup{step=5}

The speed range is $V = \SIsteps{size=6,end=125}{\km\per\hour}$

The speed range is $V = \SIsteps{size=6,end=125,full,sep={;}}{\km\per\hour}$

The speed range is $V = \SIsteps{step=5,start=20,end=30}{\km\per\hour}$

\stepsetup{step=1}

Angle ranges:

\anglesteps{step=5,start=100,end=125}

\anglesteps{size=6,start=100}

\anglesteps{size=6,start=100,step=5}

\anglesteps{size=6,start=84.37,step=5}

\stepsetup{step=5}

\anglesteps{size=6,end=125}

\anglesteps{size=6,end=125,full,sep={;}}

\anglesteps{step=5,start=20,end=30}

\end{document}

enter image description here

UPDATE for siunitx version 3

The load-configurations option is no longer valid and the first code works the same as with version 2.

For the second code, replace \l__siunitx_number_unit_product_tl with \l__siunitx_quantity_product_tl (there seems to be no interface to it yet).

In addition \si should also become \unit and, for uniformity, \SIsteps should perhaps be renamed \qtysteps.