diff --git a/MANIFEST b/MANIFEST
index 8f7950d32..11f9dd744 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1016,6 +1016,15 @@ t/complex/physics.xml
t/complex/si.pdf
t/complex/si.tex
t/complex/si.xml
+t/complex/si_preamble.pdf
+t/complex/si_preamble.tex
+t/complex/si_preamble.xml
+t/complex/siV2.pdf
+t/complex/siV2.tex
+t/complex/siV2.xml
+t/complex/siV3.pdf
+t/complex/siV3.tex
+t/complex/siV3.xml
t/complex/sunset.jpg
t/complex/tcilatex_minimal.pdf
t/complex/tcilatex_minimal.tex
diff --git a/lib/LaTeXML/Package/siunitx.sty.ltxml b/lib/LaTeXML/Package/siunitx.sty.ltxml
index b6173b4db..58d676c3e 100644
--- a/lib/LaTeXML/Package/siunitx.sty.ltxml
+++ b/lib/LaTeXML/Package/siunitx.sty.ltxml
@@ -14,20 +14,137 @@ package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
+use LaTeXML::Core::Box;
+use Math::Trig qw(:radial :pi deg2rad rad2deg);
+
+# siunitx version 3 was released in May 2021. I'm not sure which is in GitHub's TeXLive 2021
+my $FMT_YEAR = Expand(T_CS('\fmtversion'))->toString;
+$FMT_YEAR =~ s/\D.*//;
+my $VERSION_TWO = ( $FMT_YEAR < 2021 ); # or 2022?
+# or it was loaded with [=v2] or [=2021-04-09], once issue #2719 is fixed
+# differences are tested in v2 and v3 files
+
+# In fact, the input-separators can only be changed in the package's load phase
+my @PREAMBLE_SETTINGS = qw( list-input-separator product-input-separator table-column-type );
+
+# A meta setting sets other settings. Look for `.meta:n` in `siunitx.sty` to find them
+my %META_SETTING = (
+ 'color' => ['number-color', 'unit-color'],
+ 'digit-group-size' => ['digit-group-first-size', 'digit-group-other-size'],
+ 'mode' => ['number-mode', 'unit-mode'],
+ 'per-mode' => ['display-per-mode', 'inline-per-mode'],
+ 'print-implicit-plus' => ['print-mantissa-implicit-plus', 'print-exponent-implicit-plus'],
+ 'table-alignment' => ['table-number-alignment', 'table-text-alignment'],
+ # we also use this for synonym keys
+ 'add-integer-zero' => ['print-zero-integer'],
+ 'print-zero-integer' => ['add-integer-zero'],
+ 'drop-uncertainty' => ['omit-uncertainty'],
+ 'omit-uncertainty' => ['drop-uncertainty'],
+ 'bracket-ambiguous-numbers' => ['bracket-numbers'],
+ 'bracket-numbers' => ['bracket-ambiguous-numbers'],
+ 'add-arc-degree-zero' => ['fill-angle-degrees'],
+ 'add-arc-minute-zero' => ['fill-angle-minutes'],
+ 'add-arc-second-zero' => ['fill-angle-seconds'],
+ 'fill-angle-degrees' => ['add-arc-degree-zero'],
+ 'fill-angle-minutes' => ['add-arc-minute-zero'],
+ 'fill-angle-seconds' => ['add-arc-second-zero'],
+ 'print-unity-mantissa' => ['retain-unity-mantissa'],
+ 'retain-unity-mantissa' => ['print-unity-mantissa'],
+ 'print-zero-exponent' => ['retain-zero-exponent'],
+ 'retain-zero-exponent' => ['print-zero-exponent'],
+ 'product-symbol' => ['output-product'],
+ 'output-product' => ['product-symbol'],
+ 'quantity-product' => ['number-unit-product'],
+ 'number-unit-product' => ['quantity-product'],
+ 'multi-part-units' => ['separate-uncertainty-units'],
+ 'separate-uncertainty-units' => ['multi-part-units'],
+ 'table-align-text-before' => ['table-align-text-pre'],
+ 'table-align-text-pre' => ['table-align-text-before'],
+ 'table-align-text-after' => ['table-align-text-post'],
+ 'table-align-text-post' => ['table-align-text-after'],
+);
+
+my %ANTONYM = (
+ 'drop-zero-decimal' => 'add-decimal-zero',
+ 'add-decimal-zero' => 'drop-zero-decimal'
+);
+
+# choice settings only allow a fixed set of options. Look for `.choices:nn` and `.choice:` in `siunitx.sty`
+my %CHOICE_SETTING = (
+ # These come from .choices:nn
+ 'display-per-mode' => [
+ 'fraction', 'power', 'power-positive-first', 'repeated-symbol', 'symbol', 'single-symbol'],
+ 'exponent-mode' => ['engineering', 'fixed', 'input', 'scientific', 'threshold'],
+ 'inline-per-mode' => [
+ 'fraction', 'power', 'power-positive-first', 'repeated-symbol', 'symbol', 'single-symbol'],
+ 'list-exponents' => ['combine', 'combine-bracket', 'individual'],
+ 'list-units' => ['bracket', 'brackets', 'independent', 'repeat', 'single'],
+ 'number-mode' => ['match', 'math', 'text'],
+ 'prefix-mode' => ['combine-exponent', 'extract-exponent', 'input'],
+ 'product-exponents' => ['combine', 'combine-bracket', 'individual'],
+ 'product-units' => [
+'bracket', 'brackets', 'bracket-power', 'brackets-power', 'independent', 'power', 'repeat', 'single'],
+ 'qualifier-mode' => ['bracket', 'brackets', 'combine', 'phrase', 'space', 'subscript', 'text'],
+ 'range-exponents' => ['combine', 'combine-bracket', 'individual'],
+ 'range-units' => ['bracket', 'brackets', 'independent', 'repeat', 'single'],
+ 'round-direction' => ['down', 'nearest', 'up'],
+ 'round-mode' => ['figures', 'none', 'places', 'uncertainty'],
+ 'table-alignment-mode' => ['none', 'format', 'marker'],
+ 'table-number-alignment' => ['center', 'left', 'right'],
+ 'table-text-alignment' => ['center', 'left', 'right', 'none'],
+ 'uncertainty-descriptor-mode' => ['bracket', 'bracket-separator', 'separator', 'subscript'],
+ 'uncertainty-mode' => ['compact', 'compact-marker', 'full', 'separate'],
+ 'uncertainty-round-direction' => ['down', 'nearest', 'up'],
+ 'unit-mode' => ['match', 'math', 'text'],
+ # These come from .choice: (this is also used for deprecated boolean options)
+ 'angle-mode' => ['arc', 'decimal', 'input'],
+ 'complex-angle-unit' => ['degrees', 'radians'],
+ 'complex-mode' => ['cartesian', 'polar', 'input'],
+ 'complex-root-position' => ['after-number', 'before-number'],
+ 'compound-boundary-mode' => ['number', 'text'],
+ 'compound-exponents' => ['combine', 'combine-bracket', 'individual'],
+ 'compound-separator-mode' => ['number', 'text'],
+ 'compound-units' => ['bracket', 'bracket-power', 'power', 'repeat', 'single'],
+ 'group-digits' => ['all', 'decimal', 'integer', 'none', 'true', 'false'],
+ 'multi-part-units' => ['brackets', 'repeat', 'single', 'true'], # deprecated in v3
+ 'product-mode' => ['phrase', 'symbol'],
+ 'round-half' => ['even', 'up'],
+ 'separate-uncertainty-units' => ['bracket', 'repeat', 'single'],
+ 'scientific-notation' => ['engineering', 'fixed', 'false', 'true'] # deprecated in v3
+);
+
+my %LOCALE = (
+ BR => { 'exponent-product' => '\times', 'inter-unit-product' => '\,', 'output-decimal-marker' => '{,}' },
+ DE => { 'exponent-product' => '\cdot', 'inter-unit-product' => '\,', 'output-decimal-marker' => '{,}' },
+ FR => { 'exponent-product' => '\times', 'inter-unit-product' => '\,', 'output-decimal-marker' => '{,}' },
+ IT => { 'exponent-product' => '\times', 'inter-unit-product' => '\,', 'output-decimal-marker' => '{,}' },
+ PL => { 'exponent-product' => '\cdot', 'inter-unit-product' => '\cdot', 'output-decimal-marker' => '{,}' },
+ SI => { 'exponent-product' => '\times', 'inter-unit-product' => '\,', 'output-decimal-marker' => '{,}' },
+ UK => { 'exponent-product' => '\times', 'inter-unit-product' => '\,', 'output-decimal-marker' => '.' },
+ US => { 'exponent-product' => '\times', 'inter-unit-product' => '\,', 'output-decimal-marker' => '.' },
+ ZA => { 'exponent-product' => '\times', 'inter-unit-product' => '\cdot', 'output-decimal-marker' => '{,}' }
+);
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# TODO:
-# * rounding options for number formatting
# * Semantics! should be possible to directly construct XMDual's for these
# without invoking the MathParser at all.
-# * table alignments!
+# * table alignments?
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
-# recent siunitx have an expectation for latex3 capability.
-RequirePackage('expl3');
-# Would be nice if we could load the package (without errors!),
+
+# Would be nice if we could load the package (without errors!):
+# InputDefinitions('siunitx', type=>'sty', noltxml => 1);
# in order to pick up all the unit definitions!
+# But siunitx runs into many errors, starting with:
+# Error:recursion:\q_recursion_stop Token T_CS[\q_recursion_stop] expands into itself!
+# at siunitx.sty; line 6587 col 1 - line 6587 col 4
+# defining as empty
+# Next token is T_CS[\__siunitx_number_token_auxi:NN] ( == Core::Definition::Expandable[\__siunitx_number_token_auxi:NN {}{}])
+# In Core::Definition::Expandable[\q_recur... latex; from line 1 col 1 to line 1 col 1
+# <= Core::Gullet[@0x13361da10] <= Core::Stomach[@0x132025e10] <= Core::Gullet[@0x13361da10] <= ...
+
RequirePackage('xcolor');
RequirePackage('amstext');
RequirePackage('array');
@@ -40,51 +157,133 @@ foreach my $key (qw(
free-standing-units overwrite-functions
bracket-numbers detect-family detect-italic detect-mode
detect-shape detect-weight multi-part-units parse-numbers
- parse-units product-units
+ group-four-digits
copy-complex-root copy-decimal-marker
- bracket-negative-numbers bracket-numbers
- separate-uncertainty tight-spacing
- retain-explicit-plus add-decimal-zero add-integer-zero
+ bracket-numbers
+ separate-uncertainty
+ retain-explicit-decimal-marker retain-explicit-plus add-decimal-zero add-integer-zero
retain-unity-mantissa retain-zero-exponent
omit-uncertainty
add-arc-degree-zero add-arc-minute-zero add-arc-second-zero
angle-symbol-over-decimal
- sticky-per prefixes-as-symbols
+ prefixes-as-symbols
+ retain-zero-uncertainty retain-negative-zero
+
+ propagate-math-font reset-math-version reset-text-family reset-text-series reset-text-shape text-family-to-math text-series-to-math
+ evaluate-expression
+ drop-exponent drop-uncertainty drop-zero-decimal round-pad round-zero-positive
+ bracket-ambiguous-numbers bracket-negative-numbers print-exponent-implicit-plus print-implicit-plus print-mantissa-implicit-plus
+ print-unity-mantissa print-zero-exponent print-zero-integer simplify-uncertainty tight-spacing zero-decimal-as-symbol
+ exponent-to-prefix list-independent-prefix product-independent-prefix range-independent-prefix
+ print-complex-unity
+ angle-symbol-over-decimal fill-angle-degrees fill-angle-minutes fill-angle-seconds
+ free-standing-units overwrite-command space-before-unit unit-optional-argument use-xspace
+ bracket-unit-denominator forbid-literal-units parse-units power-half-as-sqrt sticky-per
+ allow-quantity-breaks extract-mass-in-kilograms
+ table-align-comparator table-align-exponent table-align-text-after table-align-text-before table-align-uncertainty table-auto-round table-fixed-width
)) {
DefKeyVal('SIX', $key, '', 'true'); }
+# despite setting the $default to 'true', siunitx will treat the default as false
+# we'll set things to true at the end
sub six_get {
- my ($key) = @_;
- return LookupValue('SIX_' . ToString($key)); }
+ my (@keys) = @_;
+ foreach my $key (@keys) {
+ if (my $v = LookupValue('SIX_' . ToString($key))) {
+ return $v; } }
+ return; }
# And should probably trim spaces off the values...
sub six_getBool {
- my ($key) = @_;
- my $v = LookupValue('SIX_' . ToString($key));
- return unless defined $v;
- $v = ToString($v); $v =~ s/^\s+//; $v =~ s/\s+$//;
- return $v eq 'true'; }
-
-# Should really figure out how Choice keyvals are set up.
+ my (@keys) = @_;
+ foreach my $key (@keys) {
+ my $v = LookupValue('SIX_' . ToString($key));
+ if (defined $v) {
+ $v = ToString($v); $v =~ s/^\s+//; $v =~ s/\s+$//;
+ return $v eq 'true'; } }
+ return 0; } # a key / value wasn't found. return false
+ # (setting keys without values to true happens because of key / value stuff)
+
+# choice key vals are checked on setting in six_setup
sub six_getChoice {
- my ($key) = @_;
- my $v = LookupValue('SIX_' . ToString($key));
- $v = $v && ToString($v);
- $v =~ s/^\s*//; $v =~ s/\s*$//;
- return $v; }
+ my (@keys) = @_;
+ foreach my $key (@keys) {
+ if (my $v = LookupValue('SIX_' . ToString($key))) {
+ $v = $v && ToString($v);
+ $v =~ s/^\s*//; $v =~ s/\s*$//;
+ return $v; } }
+ Fatal('expected', 'keys', undef, "No values found for choices @keys.");
+ return; }
# Various operators, punctuation, etc, are given in text mode.
+# even \times?
sub six_get_op {
- my ($kv, $key) = @_;
- my $text = six_get($key);
- return I_wrap($kv, ($text ? Tokens(T_CS('\text'), T_BEGIN, $text, T_END) : ())); }
+ my ($kv, @keys) = @_;
+ foreach my $key (@keys) {
+ if (my $text = six_get($key)) {
+ return I_wrap($kv, Tokens(T_CS('\text'), T_BEGIN, $text, T_END)); } }
+ return I_wrap($kv, ()); }
sub six_setup {
my ($kv) = @_;
- my $hash = $kv->getKeyVals;
- foreach my $key (keys %$hash) {
- my $value = $kv->getValue($key);
- AssignValue('SIX_' . $key => $value); }
+ my @keyVals;
+# because %META_SETTING (and table-format and group-four-digits) can change other settings (see siunitx#841),
+# we can't use the usual $kv->getKeyVals because the hash is randomly ordered
+ my @orderedKV = $kv->getPairs;
+ while (@orderedKV) {
+ my $key = shift(@orderedKV);
+ my $val = shift(@orderedKV);
+ push(@keyVals, [$key, $val]); }
+ foreach my $keyVal (@keyVals) {
+ my $key = $$keyVal[0];
+ next if ((grep { $_ eq $key } @PREAMBLE_SETTINGS) and not LookupValue('inPreamble'));
+ #my $value = $kv->getValue($key);
+ my $value = $$keyVal[1];
+ if ($key eq 'locale') {
+ if ($LOCALE{ $value->toString }) {
+ while (my ($lkey, $lval) = each %{ $LOCALE{ $value->toString } }) {
+ AssignValue('SIX_' . $lkey => Tokenize($lval)); } }
+ else {
+ Error('unexpected', $value, undef, "Key 'siunitx/locale' accepts only a fixed set of choices"); } }
+ elsif ($key eq 'table-format') {
+ six_setup_table_format($value); }
+ else {
+ if ($key eq 'group-four-digits' && $value->ToString eq 'true') {
+ $key = 'digit-group-size';
+ $value = T_OTHER(4); }
+ AssignValue('SIX_' . $key => $value); }
+ if ($META_SETTING{$key}) {
+ foreach my $subkey (@{ $META_SETTING{$key} }) {
+ AssignValue('SIX_' . $subkey => $value); } }
+ if ($ANTONYM{$key}) {
+ AssignValue('SIX_' . $ANTONYM{$key} => Tokenize($value->ToString eq 'true' ? 'false' : 'true')); }
+ if ($CHOICE_SETTING{$key}) {
+ unless (grep { $_ eq $value->toString } @{ $CHOICE_SETTING{$key} }) {
+ Error('unexpected', $value, undef, "Key 'siunitx/$key' accepts only a fixed set of choices"); } } }
+ return; }
+
+# build the table format by decomposing the parsed table-format value
+# these were explicit options in v2, but removed for just table-format in v3
+# we'll store the consequences of the table-format key in the keys used in v2
+sub six_setup_table_format {
+ my ($tokens) = @_;
+ my @tokens = $tokens->unlist;
+ my ($pre, $post);
+ ($pre, @tokens) = six_peel_group(@tokens);
+ ($post, @tokens) = six_peel_tail_group(@tokens);
+ AssignValue('SIX_table-space-text-pre' => ($pre || ''));
+ AssignValue('SIX_table-space-text-post' => ($post || ''));
+ # set the defaults
+ AssignValue('SIX_table-comparator' => Tokenize('false'));
+ AssignValue('SIX_table-sign-exponent' => Tokenize('false'));
+ AssignValue('SIX_table-figures-exponent' => T_LETTER(0));
+ AssignValue('SIX_table-figures-uncertainty' => T_LETTER(0));
+ # then parse the format
+ six_parse_number(undef, Tokens(@tokens), 1);
+ # setting table-format resets these
+ AssignValue('SIX_table-alignment-mode' => Tokenize('format'));
+ AssignValue('SIX_table-number-alignment' => Tokenize('center'));
+# AssignValue('SIX_table-number-alignment' => Tokenize('center' . ( $VERSION_TWO ? '-decimal-marker' : '' ) ) );
return; }
# These two should be wrapped around macros that process arguments;
@@ -108,6 +307,8 @@ sub six_end_processing {
DefPrimitive('\sisetup RequiredKeyVals:SIX', sub { six_setup($_[1]); });
DefMacro('\ProvidesExplFile{}{}{}{}', '');
+# without this, we end up in expl syntax for some reason
+
DefPrimitiveI('\lx@six@initialize', undef, sub {
my $pkgoptions = LookupValue('opt@siunitx.sty');
my $setup = $pkgoptions && Tokenize('\sisetup{' . join(',', map { $_ } @$pkgoptions) . '}');
@@ -125,6 +326,28 @@ DefPrimitiveI('\lx@six@initialize', undef, sub {
InputDefinitions('siunitx-binary', type => 'cfg', noltxml => 1); }
if (six_getBool('free-standing-units')) {
six_enableUnitMacros(six_getBool('overwrite-functions')); }
+ # the table-column-type setting (default S) can be changed in the preamble,
+ # so we'll read it after that point to actually set up the columns
+ my $tableColHeaders = six_get('table-column-type')->toString;
+ foreach my $tableColHeader (split(//, $tableColHeaders)) {
+ DefColumnType("$tableColHeader OptionalKeyVals:SIX", sub {
+ my ($gullet, $kv) = @_;
+ my $align = 'center';
+ if ($kv) {
+ my $mode = ($kv->getValue('table-alignment-mode') || six_get('table-alignment-mode') || '')->ToString;
+ # this doesn't quite work - might need table-text-alignment or table-alignment
+ # but we won't know that until we've parsed the cell, at which point it's too late to change the alignment?
+ if ($mode eq 'format' || $mode eq 'none' || $kv->hasKey('table-number-alignment')) {
+ $align = ($kv->getValue('table-number-alignment') || six_get('table-number-alignment') || '')->ToString; }
+ elsif ($mode eq 'marker') {
+ $align = 'char:' . six_get('output-decimal-marker')->ToString; } }
+ $LaTeXML::BUILD_TEMPLATE->addColumn(
+ before => Tokens(T_BEGIN,
+ T_CS('\lx@si@column@prep'), ($kv ? (T_OTHER('['), $kv->revert(), T_OTHER(']')) : ()),
+ T_CS('\lx@SI@column@parse')),
+ align => $align,
+ after => Tokens(T_CS('\lx@si@column@end'), T_END));
+ return; }); }
return; });
AtBeginDocument('\lx@six@initialize');
@@ -176,12 +399,9 @@ DefMathI('\SIUnitSymbolMicro', undef, UTF(0xB5));
# input-close-uncertainty, input-comparators, input-complex-roots,
# input-decimal-markers, input-digits, input-exponent-markers,
# input-open-uncertainty, input-protect-tokens, input-signs, input-uncertainty-signs,
-# input-symbols, parse-numbers
+# input-symbols, parse-numbers, input-ignore
# Also options for multi-part numbers:
# input-product, input-quotient
-
-# Not yet handled:
-# input-ignore
#======================================================================
# Low-level parsing aids.
@@ -197,14 +417,18 @@ sub six_match_keys {
my ($tokens, @sixkeys) = @_;
# Skip spaces...
# Remove & return all tokens matching one of the sets @sixkeys
- my @tomatch = (T_SPACE, map { @{ six_get($_) || [] } } @sixkeys);
- my @matched = ();
+ my @tomatch = (map { @{ six_get($_) || [] } } @sixkeys);
+ my @toignore = (T_SPACE, @{ six_get('input-ignore') || [] });
+ my @matched = ();
my $t;
while (($t = $$tokens[0])
- && grep { scalar(@$tokens) && $t->equals($_) } @tomatch) {
+ && ((grep { scalar(@$tokens) && $t->toString eq $_->toString } @tomatch)
+ || (grep { scalar(@$tokens) && $t->toString eq $_->toString } @toignore))) {
shift(@$tokens);
- push(@matched, $t) unless $t->equals(T_SPACE);
- push(@matched, T_SPACE) if $t->getCatcode == CC_CS; }
+ unless (grep { scalar(@$tokens) && $t->toString eq $_->toString } @toignore) {
+ # this means that ignore overrides match
+ push(@matched, $t);
+ push(@matched, T_SPACE) if $t->getCatcode == CC_CS; } }
return (@matched ? Tokens(@matched) : undef); }
sub six_match_sign {
@@ -213,115 +437,228 @@ sub six_match_sign {
# Match (and REMOVE!) leading tokens that fit the pattern of simplenumber
sub six_match_simplenumber {
- my ($tokens) = @_;
- my $sign = six_match_sign($tokens);
- my $integer = six_match_keys($tokens, 'input-digits', 'input-symbols');
+ my ($tokens, $is_table_formatter) = @_;
+ my $sign = six_match_sign($tokens);
+ my $integer = six_match_keys($tokens, 'input-digits', 'input-symbols');
my ($decimal, $fraction);
if ($decimal = six_match_keys($tokens, 'input-decimal-markers')) {
$fraction = six_match_keys($tokens, 'input-digits', 'input-symbols'); }
+ if ($is_table_formatter) {
+ AssignValue('SIX_table-sign-mantissa' => Tokenize($sign ? 'true' : 'false'));
+ AssignValue('SIX_table-figures-integer' => $integer || Tokenize(0));
+ AssignValue('SIX_table-figures-decimal' => $fraction || Tokenize(0));
+ return; }
return
($sign || $integer || $decimal || $fraction
? { sign => $sign, integer => $integer, decimal => $decimal, fraction => $fraction }
: undef); }
sub six_match_uncertainnumber {
- my ($tokens) = @_;
- my $number = six_match_simplenumber($tokens);
- my $uncertainty;
- if (my $sign = six_match_keys($tokens, 'input-uncertainty-signs')) {
- # \pm form ('separate') allows decimal! (ie. has the same point as the main number)
+ my ($tokens, $is_table_formatter) = @_;
+ my $number = six_match_simplenumber($tokens, $is_table_formatter);
+ my (@uncertainties, $uncertaintySign);
+ while ($uncertaintySign = six_match_keys($tokens, 'input-uncertainty-signs', 'input-open-uncertainty')) {
+ if (my $whoops = six_match_keys($tokens, 'input-complex-roots')) {
+ # Whoops, really should be complex!!!!
+ unshift(@$tokens, map { $_ ? $_->unlist : () } $uncertaintySign, $whoops);
+ last; }
my ($int, $dec, $frac);
$int = six_match_keys($tokens, 'input-digits', 'input-symbols');
+ if ($is_table_formatter) {
+ AssignValue('SIX_table-figures-uncertainty' => $int);
+ return; }
if ($dec = six_match_keys($tokens, 'input-decimal-markers')) {
$frac = six_match_keys($tokens, 'input-digits', 'input-symbols'); }
# Ambiguous: # \pm # is uncertainty unless followed by i, in which case complex!
- if (my $whoops = six_match_keys($tokens, 'input-decimal-markers', 'input-complex-roots')) {
+ if (my $whoops = six_match_keys($tokens, 'input-complex-roots')) {
# Whoops, really should be complex!!!!
- unshift(@$tokens, map { $_ ? $_->unlist : () } $sign, $int, $dec, $frac, $whoops); }
+ unshift(@$tokens, map { $_ ? $_->unlist : () } $uncertaintySign, $int, $dec, $frac, $whoops);
+ last; }
+ my %uncertainty = (integer => $int, decimal => $dec, fraction => $frac);
+ if ($uncertaintySign->equals(six_get('input-open-uncertainty'))) {
+ if (six_match_keys($tokens, 'input-uncertainty-divider')) {
+ my ($aint, $adec, $afrac);
+ $aint = six_match_keys($tokens, 'input-digits', 'input-symbols');
+ if ($adec = six_match_keys($tokens, 'input-decimal-markers')) {
+ $afrac = six_match_keys($tokens, 'input-digits', 'input-symbols'); }
+ %uncertainty = (%uncertainty, ainteger => $aint, adecimal => $adec, afraction => $afrac); }
+ six_match_keys($tokens, 'input-close-uncertainty'); }
else {
- $uncertainty = { sign => $sign, integer => $int, decimal => $dec, fraction => $frac }; } }
- elsif (six_match_keys($tokens, 'input-open-uncertainty')) {
- # Parenthesized ONLY allows digits (ie. in the same positions as the last digits of the number)
- $uncertainty = { integer => six_match_keys($tokens, 'input-digits', 'input-symbols') };
- six_match_keys($tokens, 'input-close-uncertainty'); }
- return
- ($uncertainty ? { operator => 'uncertain', arg1 => $number, arg2 => $uncertainty } : $number); }
+ $uncertainty{sign} = $uncertaintySign; }
+ if (!is_explicit_zero(\%uncertainty) || $uncertainty{ainteger} || six_getBool('retain-zero-uncertainty')) {
+ push(@uncertainties, \%uncertainty); } }
+ return scalar(@uncertainties)
+ ? { operator => 'uncertain', arg1 => $number, arg2 => \@uncertainties }
+ : $number; }
# Parse a Complex number, possibly with Exponential (see above)
sub six_match_complexnumber {
- my ($tokens) = @_;
- my $number = six_match_uncertainnumber($tokens);
- if (my $i = six_match_keys($tokens, 'input-complex-roots')) { # pure imaginary!
- my $sign = $$number{sign}; $$number{sign} = undef; # Make sign "infix"
+ my ($tokens, $is_table_formatter) = @_;
+ my $number = six_match_uncertainnumber($tokens, $is_table_formatter);
+ if (my $i = six_match_keys($tokens, 'input-complex-roots')) { # pure imaginary, make sign "infix"
+ my $sign = $$number{sign};
+ delete $$number{sign};
$number = { operator => 'complex', symbol => $i, sign => $sign, arg2 => $number }; }
# Check if followed by a sign, then expect imaginary part
elsif (my $sign = six_match_sign($tokens)) { # Imaginary part
my ($i, $imag);
if ((($i = six_match_keys($tokens, 'input-complex-roots'))
- && ($imag = six_match_uncertainnumber($tokens)))
+ && ($imag = six_match_uncertainnumber($tokens) || { integer => T_OTHER('1') })) # watch for 1+i
|| (($imag = six_match_uncertainnumber($tokens))
&& ($i = six_match_keys($tokens, 'input-complex-roots')))) {
- $number = { operator => 'complex', arg1 => $number, symbol => $i, sign => $sign, arg2 => $imag }; }
+ $$imag{sign} = $sign;
+ $number = { operator => 'complex', arg1 => $number, symbol => $i, arg2 => $imag }; }
else {
Error('unexpected', 'sign', undef, "expected to find complex number"); } }
+ elsif (six_match_keys($tokens, 'complex-angle-separator')) {
+ my $imag_angle = six_match_uncertainnumber($tokens);
+ $number = { operator => 'complex', arg1 => $number, arg2 => $imag_angle, input_form => 'polar' }; }
return $number; }
sub six_match_scinumber {
- my ($tokens) = @_;
- my $number = six_match_complexnumber($tokens);
+ my ($tokens, $is_table_formatter) = @_;
+ my $sign_mantissa = six_match_sign($tokens);
+ my $number = six_match_complexnumber($tokens, $is_table_formatter);
# Now check if followed by exponent
if (my $mark = six_match_keys($tokens, 'input-exponent-markers')) {
- my $sign = six_match_sign($tokens);
- my $exp = six_match_keys($tokens, 'input-digits', 'input-symbols');
+ my $sign_exponent = six_match_sign($tokens);
+ my $exp = six_match_keys($tokens, 'input-digits', 'input-symbols');
+ if ($is_table_formatter) {
+ AssignValue('SIX_table-sign-exponent' => Tokenize($sign_exponent ? 'true' : 'false'));
+ AssignValue('SIX_table-figures-exponent' => $exp);
+ return; }
$number = { operator => 'exponent', arg1 => $number,
- arg2 => { sign => $sign, integer => $exp } }; }
+ arg2 => { sign => $sign_exponent, integer => $exp } }; }
+ $$number{sign} = $sign_mantissa if ($sign_mantissa);
return $number; }
sub six_match_compoundnumber {
- my ($tokens) = @_;
- if (my $comp = six_match_keys($tokens, 'input-comparators')) {
- return { operator => 'comparator', comparator => $comp, arg1 => six_match_number($tokens) }; }
+ my ($tokens, $is_table_formatter) = @_;
+ my $comp = six_match_keys($tokens, 'input-comparators');
+ if ($is_table_formatter) {
+ AssignValue('SIX_table-comparator' => Tokenize('true')) if ($comp);
+ six_match_scinumber($tokens, $is_table_formatter);
+ return; }
+ if ($comp) {
+ return { operator => 'comparator', comparator => $comp, arg1 => six_match_scinumber($tokens) }; }
else {
my $number = six_match_scinumber($tokens);
while (1) {
- my $op;
- if ($op = six_match_keys($tokens, 'input-product')) {
+ if (six_match_keys($tokens, 'input-product') || six_match_keys($tokens, 'product-input-separator')) {
$number = { operator => 'product', arg1 => $number, arg2 => six_match_scinumber($tokens) }; }
- elsif ($op = six_match_keys($tokens, 'input-quotient')) {
+ elsif (six_match_keys($tokens, 'input-quotient')) {
$number = { operator => 'quotient', arg1 => $number, arg2 => six_match_scinumber($tokens) }; }
else {
return $number; } }
- return $number; } } # never gets here...
+ Fatal('internal', undef, undef, 'This point should not be reached');
+ return $number; } }
sub six_match_number {
- my ($tokens) = @_;
- return six_match_compoundnumber($tokens); }
+ my ($tokens, $is_table_formatter) = @_;
+ return six_match_compoundnumber($tokens, $is_table_formatter); }
#======================================================================
# Post-processing numbers
+# round-half, round-minimum, round-mode, round-precision, scientific-notation, zero-decimal-to-integer
# Options NOT YET HANDLED:
-# fixed-exponent,
-# minimum-integer-digits, retain-unity-mantissa
-# round-half, round-integer-to-decimal, round-minimum
-# round-mode, round-precision, scientific-notation, zero-decimal-to-integer
+# round-integer-to-decimal
sub six_postprocess {
my ($number) = @_;
+ $number = six_ensure_scientific($number) if (six_getChoice('exponent-mode') ne 'input');
+ six_round($number) if six_getBool('parse-numbers');
return six_postprocess_aux($number); }
+sub six_ensure_scientific {
+ my ($number) = @_;
+ my $op = $$number{operator};
+ return $number if ($op && $op eq 'exponent');
+ if (!$op || $op eq 'complex' || $op eq 'uncertain') {
+ return { arg1 => $number, operator => 'exponent', arg2 => { integer => T_OTHER('0') } }; }
+ else {
+ $$number{arg1} = six_ensure_scientific($$number{arg1}) if ($$number{arg1});
+ $$number{arg2} = six_ensure_scientific($$number{arg2}) if ($$number{arg2}); }
+ return $number; }
+
sub six_postprocess_aux {
my ($number) = @_;
if (!$number) { }
elsif (my $op = $$number{operator}) {
- $$number{arg1} = six_postprocess_aux($$number{arg1});
- $$number{arg2} = six_postprocess_aux($$number{arg2}); }
+ if ($op eq 'uncertain') {
+ if (six_getBool('drop-uncertainty')) {
+ $$number{sign} ||= $$number{arg1}->{sign};
+ $number = $$number{arg1}; }
+ else {
+ $$number{arg2} = [map { six_postprocess_aux($_) } @{ $$number{arg2} }]; } }
+ elsif ($op eq 'exponent') {
+ my $exponentMode = six_getChoice('exponent-mode');
+ if ($exponentMode ne 'input') {
+ $$number{arg1}->{decimal} ||= six_get('output-decimal-marker');
+ my @intTokens = $$number{arg1}->{integer} && $$number{arg1}->{integer}->can('unlist') ? $$number{arg1}->{integer}->unlist : Tokens();
+ my @fracTokens = $$number{arg1}->{fraction} && $$number{arg1}->{fraction}->can('unlist') ? $$number{arg1}->{fraction}->unlist : Tokens();
+ my $parsedExp = six_UnTeX($$number{arg2}) || 0;
+ if ($exponentMode eq 'fixed') {
+ my $fixedExponent = six_get('fixed-exponent')->toString;
+ $$number{arg1} = six_get_number_with_sci_exp($number, $fixedExponent);
+ $parsedExp = $fixedExponent; }
+ else {
+ # we don't want to use six_get_number_with_sci_exp because that drops the exponent
+ while (1 < @intTokens && !number_is_zero([@intTokens[0 .. $#intTokens - 1]])) {
+ unshift(@fracTokens, pop(@intTokens));
+ $parsedExp++; }
+ while (number_is_zero(\@intTokens) && !number_is_zero(\@fracTokens)) {
+ push(@intTokens, shift(@fracTokens));
+ $parsedExp--; }
+ if ($exponentMode eq 'engineering') {
+ while ($parsedExp % 3) {
+ push(@intTokens, (shift(@fracTokens) || T_OTHER('0')));
+ $parsedExp--; } }
+ elsif ($exponentMode eq 'threshold') {
+ my $tokens = six_get('exponent-thresholds')->clone;
+ my $lowerThreshold = ToString(six_match_keys($tokens, 'input-digits', 'input-signs'));
+ six_match_keys($tokens, 'exponent-threshold-separator');
+ my $upperThreshold = ToString(six_match_keys($tokens, 'input-digits'));
+ if ($lowerThreshold < $parsedExp && $parsedExp < $upperThreshold) {
+ while (0 < $parsedExp) {
+ push(@intTokens, (shift(@fracTokens) || T_OTHER('0')));
+ $parsedExp--; }
+ while ($parsedExp < 0) {
+ unshift(@fracTokens, (pop(@intTokens) || T_OTHER('0')));
+ $parsedExp++; } } }
+ $$number{arg1}{integer} = Tokens(@intTokens);
+ $$number{arg1}{fraction} = Tokens(@fracTokens);
+ delete $$number{arg1}{decimal} if (!@fracTokens); }
+ $$number{arg2} = { integer => Tokenize(($parsedExp < 0 ? -1 : 1) * $parsedExp) };
+ $$number{arg2}{sign} = T_OTHER('-') if ($parsedExp < 0); }
+ if (six_getBool('drop-exponent')) {
+ if ($$number{arg2} && $$number{arg2}{integer}) {
+ # mimic an siunitx warning
+ Warn('expected', undef, undef, 'Potentially ambiguous dropping of exponent'); }
+ $$number{sign} ||= $$number{arg1}->{sign};
+ $number = six_postprocess_aux($$number{arg1}); }
+ $$number{arg2} = six_postprocess_aux($$number{arg2}); }
+ else {
+ $$number{arg1} = six_postprocess_aux($$number{arg1}); } }
else {
if (six_getBool('add-decimal-zero') && $$number{decimal} && !$$number{fraction}) {
- $$number{fraction} = T_OTHER('0'); }
+ $$number{fraction} = Tokenize(0); }
if (six_getBool('add-integer-zero') && $$number{decimal} && !$$number{integer}) {
- $$number{integer} = T_OTHER('0'); }
+ $$number{integer} = Tokenize(0); }
if (my $s = !$$number{sign} && six_get('explicit-sign')) {
$$number{sign} = $s; } }
+ while ($$number{fraction} && @{ $$number{fraction} }
+ && $$number{fraction}->[-1]->equals(T_OTHER('0')) && six_getBool('drop-zero-decimal')) {
+ pop(@{ $$number{fraction} }); }
+ if ($$number{integer} && $$number{fraction} && number_is_zero(@{ $$number{fraction} })
+ && (six_getBool('zero-decimal-to-integer') || six_getBool('drop-zero-decimal'))) {
+ delete $$number{decimal};
+ delete $$number{fraction}; }
+ $$number{integer} ||= Tokens() if (six_get('minimum-integer-digits')->toString);
+ $$number{fraction} ||= Tokens() if (six_get('minimum-decimal-digits')->toString);
+ while ($$number{fraction} && @{ $$number{fraction} } < six_get('minimum-decimal-digits')->toString) {
+ push(@{ $$number{fraction} }, T_OTHER('0')); }
+ while ($$number{integer} && @{ $$number{integer} } < six_get('minimum-integer-digits')->toString) {
+ unshift(@{ $$number{integer} }, T_OTHER('0')); }
return $number; }
# Given an uncertain number whose uncertainty is not separate (ie. it is relative)
@@ -331,24 +668,37 @@ sub six_compute_separate_uncertainty {
my $number = $$uncertain{arg1};
my $uncertainty = $$uncertain{arg2};
my $num = $$uncertainty{integer};
+ my $anum = $$uncertainty{ainteger};
return $uncertainty if $$uncertainty{sign}; # Has sign? already separate
+ if ($$uncertainty{decimal}) { # Has decimal? Use that
+ $$uncertainty{decimal} = six_get('output-decimal-marker');
+ $$uncertainty{sign} = T_CS('\pm');
+ return $uncertainty; }
my @dig = $num->unlist;
+ my @adig = $anum && $anum->unlist;
my $n = scalar(@dig);
my $ndigits = ($$number{fraction} ? scalar($$number{fraction}->unlist) : 0);
+ my %returned = (sign => T_CS('\pm'), decimal => six_get('output-decimal-marker'));
if ($n <= $ndigits) {
for (my $i = $n ; $i < $ndigits ; $i++) {
- unshift(@dig, T_OTHER('0')); }
- return { sign => T_CS('\pm'),
- integer => T_OTHER('0'), decimal => six_get('output-decimal-marker'),
- fraction => Tokens(@dig) }; }
+ unshift(@dig, T_OTHER('0'));
+ if ($anum) {
+ unshift(@adig, T_OTHER('0')); } }
+ @returned{ 'integer', 'fraction' } = (T_OTHER('0'), Tokens(@dig));
+ if ($anum) {
+ @returned{ 'ainteger', 'afraction' } = (T_OTHER('0'), Tokens(@adig)); } }
else {
- my @man = ();
+ my @man = ();
+ my @aman = ();
for (my $i = $n ; $i > $ndigits ; $i--) {
- push(@man, shift(@dig)); }
- return { sign => T_CS('\pm'),
- integer => Tokens(@man), decimal => six_get('output-decimal-marker'),
- fraction => Tokens(@dig) }; } }
+ push(@man, shift(@dig));
+ if ($anum) {
+ push(@aman, shift(@adig)); } }
+ @returned{ 'integer', 'fraction' } = (Tokens(@man), Tokens(@dig));
+ if ($anum) {
+ @returned{ 'ainteger', 'afraction' } = (Tokens(@aman), Tokens(@adig)); } }
+ return \%returned; }
sub six_compute_relative_uncertainty {
my ($uncertain) = @_;
@@ -390,42 +740,83 @@ sub six_apply_mathligatures {
my $repl;
if (@tokens && ($repl = $six_mathligatures{ $t->getCSName }{ $tokens[0]->getCSName })) {
shift(@tokens); push(@r, $repl); }
- elsif ($t->getCatcode == CC_COMMENT) { }
else {
push(@r, $t); } }
return @r; }
+sub six_compute_separate_uncertainties {
+ my ($number) = @_;
+ my $arg1 = $$number{arg1};
+ my $arg2 = $$number{arg2};
+ my @uncertainties;
+ my $fake_number = {
+ arg1 => $arg1,
+ operator => 'uncertain' };
+ foreach (@{$arg2}) {
+ $$fake_number{arg2} = $_;
+ push(@uncertainties, six_compute_separate_uncertainty($fake_number)); }
+ return \@uncertainties; }
+
+sub six_compute_relative_uncertainties {
+ my ($number) = @_;
+ my $arg1 = $$number{arg1};
+ my $arg2 = $$number{arg2};
+ my @uncertainties;
+ my $fake_number = {
+ arg1 => $arg1,
+ operator => 'uncertain' };
+ foreach (@{$arg2}) {
+ $$fake_number{arg2} = $_;
+ push(@uncertainties, six_compute_relative_uncertainty($fake_number)); }
+ return \@uncertainties; }
+
# Note that these 2 will return Tokens if parse-numbers is false!!!!
# TODO: Don't signal error if we're doing table columns!?
# These extract & REMOVE the number from $expr (Tokens),
# NOT reading from $gullet, which is only passed in for error reporting.
+# This is also used to parse a table-format
sub six_parse_number {
- my ($gullet, $expr) = @_;
+ my ($gullet, $expr, $is_table_formatter) = @_;
my $result = $expr;
if (six_getBool('parse-numbers')) {
my $expanded = Expand($expr);
my $tokens = [six_apply_mathligatures($expanded->unlist)];
- $result = six_postprocess(six_match_number($tokens));
+ if ($is_table_formatter) {
+ six_match_number($tokens, $is_table_formatter);
+ return; }
+ my $matched = six_match_number($tokens);
+ if ($$matched{integer} && $$matched{decimal} && !$$matched{fraction} && !six_getBool('retain-explicit-decimal-marker')) {
+ delete $$matched{decimal}; }
+ # todo upgrade
+ if ($$matched{sign} && $$matched{sign}->ToString eq '+' && !six_getBool('retain-explicit-plus')) {
+ # make sure an explicit-sign doesn't get added later
+ $$matched{sign} = T_CS('\lx@InvisiblePlus'); }
+ if ($$matched{sign} && $$matched{sign}->ToString eq '-' && is_explicit_zero($matched) && !six_getBool('retain-negative-zero')) {
+ delete $$matched{sign}; }
+ $result = six_postprocess($matched);
if (scalar(@$tokens)) {
- Error('unexpected', $$tokens[0], $gullet,
- "Not matched in \\num: " . ToString(Tokens(@$tokens)) . "\n");
- return $expr; } }
+ Error('unexpected', $$tokens[0], $gullet, 'Not matched in \num: ' . ToString(Tokens(@$tokens)) . "\n");
+ $result = $expr; } }
return $result; }
sub six_parse_numbers {
my ($gullet, $expr) = @_;
- my $result = $expr;
+ # these options are constant: angle doesn't exist, and
+ # list and product must be set when the package is loaded
+ my @numberSeparators = ('list-input-separator',
+ 'angle-input-separator',
+ 'product-input-separator');
my @results = ();
if (six_getBool('parse-numbers')) {
my $expanded = Expand($expr);
my $tokens = [six_apply_mathligatures($expanded->unlist)];
- while (1) {
- $result = six_postprocess(six_match_number($tokens));
- push(@results, $result);
- if (@$tokens && $$tokens[0]->equals(T_OTHER(';'))) {
- shift(@$tokens); }
- else {
- last; } }
+ my $separatorsFound;
+ do {
+ push(@results, six_postprocess(six_match_scinumber($tokens)));
+ $separatorsFound = six_match_keys($tokens, @numberSeparators);
+ if ($separatorsFound && scalar(@{$separatorsFound}) > 1) {
+ push(@results, (undef) x (scalar(@{$separatorsFound}) - 1)); } # eg \ang{;;3}
+ } while ($separatorsFound);
if (scalar(@$tokens)) {
Error('unexpected', $$tokens[0], $gullet,
"Not matched in \\num: " . ToString(Tokens(@$tokens)) . "\n");
@@ -437,10 +828,27 @@ sub six_parse_numbers {
my @r = ();
while (@tokens && !$tokens[0]->equals(T_OTHER(';'))) {
push(@r, shift(@tokens)); }
- push(@results, Tokens(@r)); }
- }
+ push(@results, Tokens(@r)); } }
return @results; }
+sub number_is_zero {
+ my ($number) = @_;
+ unless (defined $number) {
+ return 1; }
+ if (ref($number) ne 'ARRAY' && $number->equals(T_OTHER('0'))) {
+ return 1; }
+ unless (ref($number) eq 'ARRAY' || $number->isa('ARRAY')) {
+ return 0; }
+ foreach my $digit (@{$number}) {
+ unless ((ref($digit) eq 'SCALAR' && $digit == 0) || (ref($digit) eq 'LaTeXML::Core::Token' && $digit->ToString eq '0')) {
+ return 0; } }
+ return 1; }
+
+sub is_explicit_zero {
+ my ($arg) = @_;
+ return $arg && !$$arg{operator} && !$$arg{arg1} && !$$arg{arg2}
+ && number_is_zero($$arg{integer}) && number_is_zero($$arg{fraction}); }
+
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Formatting numbers
# Options:
@@ -450,7 +858,7 @@ sub six_parse_numbers {
# output-complex-root, output-decimal-marker, output-exponent-marker, output-open-uncertainty,
# separate-uncertainty, uncertainty-separator
# Also options for multi-part numbers:
-# fraction-function, output-product, output-quatient, quotient-mode
+# fraction-function, output-product, output-quotient, quotient-mode
# Not handled:
# tight-spacing
@@ -460,42 +868,82 @@ sub six_parse_numbers {
# A Simple Number is a number with (possibly) sign, integer & fraction parts as well as uncertainty
# but not exponent, imaginary,....
sub six_format_simplenumber {
- my ($number, $bracket) = @_;
+ my ($number, %flags) = @_;
+ if (six_getBool('print-zero-exponent') && !$flags{in_sci_number}) {
+ return six_format_scinumber({ arg1 => $number, arg2 => {}, operator => 'exponent' }, %flags); }
# Not ONLY format the number, but arrange for a fork representing the semantics!
- my @tokens = ();
- my @trailer = ();
- my $sign = $$number{sign};
- my $integer = $$number{integer};
- my $fraction = $$number{fraction};
- my $grouping = six_getChoice('group-digits');
- if ($sign) {
-
- if (ToString($sign) eq '-') {
- if (my $c = six_get('negative-color')) {
- push(@tokens, T_BEGIN, T_CS('\color'), T_BEGIN, $c->unlist, T_END);
- unshift(@trailer, T_END); }
- if (six_getBool('bracket-negative-numbers')) {
- push(@tokens, six_get('open-bracket'));
- unshift(@trailer, six_get('close-bracket')); }
+ my @tokens = ();
+ my @trailer = ();
+ $flags{table_signable} = $flags{in_table_number} && !$flags{in_uncertainty}
+ && six_getBool('table-sign-' . ($flags{in_exponent} && $flags{in_sci_number} ? 'exponent' : 'mantissa'));
+ my $sign = $$number{sign};
+ my $integer = $$number{integer};
+ my $fraction = $$number{fraction};
+ my $grouping = six_getChoice('group-digits');
+ my $needs_sign_space = 0;
+
+ push @tokens, $sign if ($sign && $flags{in_exponent});
+ if ($integer) {
+ my @intTokens = $integer->unlist;
+ while (@intTokens && $intTokens[0]->equals(T_OTHER('0')) && six_get('minimum-integer-digits')->ToString < @intTokens) {
+ shift(@intTokens); }
+ if (!@intTokens && six_getBool('print-zero-integer')) {
+ unshift(@intTokens, T_OTHER('0')); }
+ $integer = Tokens(@intTokens);
+ if (($grouping eq 'all') || ($grouping eq 'true') || ($grouping eq 'integer')) {
+ $integer = six_groupdigits($integer, +1); } }
+ elsif (six_getBool('print-zero-integer')) {
+ $integer = Tokens(T_OTHER('0')); }
+ if ($flags{in_table_number} && !$flags{in_uncertainty}) {
+ my $type = $flags{in_exponent} && $flags{in_sci_number} ? 'exponent' : 'integer';
+ my $digits_needed = six_get("table-figures-$type")->toString;
+ my $digits_have = ($integer && @{$integer}) || 0;
+ if ($digits_have < $digits_needed) {
+ if ($type eq 'exponent') {
+ ${ $flags{pad_right_ref} } .= '2' x ($digits_needed - $digits_have); }
+ elsif ($type eq 'integer') {
+ ${ $flags{pad_left_ref} } .= '2' x ($digits_needed - $digits_have); } }
+ if ($needs_sign_space && $type ne 'uncertainty') {
+ if ($flags{in_exponent}) {
+ ${ $flags{pad_right_ref} } .= '.'; } # exponentiated +
else {
- push(@tokens, $sign); } }
- elsif ((ToString($sign) eq '+') && !six_getBool('retain-explicit-plus')) { }
- else {
- push(@tokens, $sign); } }
+ ${ $flags{pad_left_ref} } .= '2'; } } } # mantissa +
if ($integer) {
- my $i = (($grouping eq 'true') || ($grouping eq 'integer')
- ? six_groupdigits($integer, +1)
- : $integer);
- push(@tokens, $i); }
- if (my $d = (six_getBool('copy-decimal-marker')
- ? $$number{decimal}
- : ($fraction || $$number{decimal} ? six_get('output-decimal-marker') : undef))) {
- push(@tokens, $d); }
+ push(@tokens, $integer); }
+ my $decimal = six_getBool('copy-decimal-marker')
+ ? $$number{decimal}
+ : (($fraction && @{$fraction}) || ($$number{decimal} && $VERSION_TWO) ? six_get('output-decimal-marker') : undef);
+ if ($decimal) {
+ push(@tokens, $decimal); }
+ my $using_symbol_for_zero;
+ if ($fraction) {
+ my @fracTokens = $fraction ? $fraction->unlist : ();
+ if (number_is_zero($fraction)) {
+ if (six_getBool('zero-decimal-as-symbol')) {
+ $using_symbol_for_zero = 1;
+ $fraction = six_get('zero-symbol'); }
+ elsif (@fracTokens) {
+ $fraction = Tokens(@fracTokens);
+ $fraction = (($grouping eq 'all') || ($grouping eq 'true') || ($grouping eq 'decimal')
+ ? six_groupdigits($fraction, -1)
+ : $fraction); } }
+ else {
+ $fraction = Tokens(@fracTokens);
+ $fraction = (($grouping eq 'all') || ($grouping eq 'true') || ($grouping eq 'decimal')
+ ? six_groupdigits($fraction, -1)
+ : $fraction); } }
+ elsif (six_getBool('zero-decimal-as-symbol')) {
+ $using_symbol_for_zero = 1;
+ $fraction = six_get('zero-symbol'); }
+ if ($flags{in_table_number} && !$flags{in_uncertainty} && !$flags{in_exponent}) {
+ my $digits_needed = six_get('table-figures-decimal')->toString;
+ ${ $flags{pad_right_ref} } .= '.' if ($digits_needed && !$fraction && !$$number{decimal});
+ $fraction = Tokens() if ($digits_needed && !$fraction);
+ ${ $flags{pad_right_ref} } .= '2' x ($digits_needed - @{$fraction}) if (@{$fraction} < $digits_needed); }
if ($fraction) {
- my $f = (($grouping eq 'true') || ($grouping eq 'decimal')
- ? six_groupdigits($fraction, -1)
- : $fraction);
- push(@tokens, $f); }
+ push(@tokens, $fraction); }
+ # if ( $flags{in_table_number} ) {
+ # return Tokens(@tokens, @trailer); }
return I_dual({ revert_as => 'presentation' },
I_symbol({ role => 'NUMBER', meaning => six_number_string($number) }),
I_wrap({}, Tokens(@tokens, @trailer))); }
@@ -505,128 +953,468 @@ sub six_groupdigits {
my @digs = $digits->unlist;
my $min = ToString(six_get('group-minimum-digits'));
return $digits if $min > scalar(@digs);
- my @r = ();
- my $g = 3;
- my $sep = six_get('group-separator');
- if ($direction > 0) {
+ my @r = ();
+ my $g_first = ToString(six_get('digit-group-first-size'));
+ my $g_other = ToString(six_get('digit-group-other-size'));
+ my $sep = six_get('group-separator');
+ if ($direction > 0) {
+ for (my $i = 0 ; @digs && $i < $g_first ; $i++) { unshift(@r, pop(@digs)); }
+ unshift(@r, $sep) if @digs;
while (@digs) {
- for (my $i = 0 ; @digs && $i < $g ; $i++) { unshift(@r, pop(@digs)); }
+ for (my $i = 0 ; @digs && $i < $g_other ; $i++) { unshift(@r, pop(@digs)); }
unshift(@r, $sep) if @digs; } }
else {
+ for (my $i = 0 ; @digs && $i < $g_first ; $i++) { push(@r, shift(@digs)); }
+ push(@r, $sep) if @digs;
while (@digs) {
- for (my $i = 0 ; @digs && $i < $g ; $i++) { push(@r, shift(@digs)); }
+ for (my $i = 0 ; @digs && $i < $g_other ; $i++) { push(@r, shift(@digs)); }
push(@r, $sep) if @digs; } }
return Tokens(@r); }
+# rounding has been surprisingly annoying with its edge cases
+sub six_round {
+ my ($number) = @_;
+ my $mode = six_getChoice('round-mode');
+ return if (is_explicit_zero($number) || $mode eq 'none');
+ my $op = $$number{operator};
+ if ((!!($op) && $op eq 'uncertain') != ($mode eq 'uncertainty')) {
+ return; }
+ if ($op) {
+ if ($op eq 'uncertain') {
+ six_round_uncertain_number($number); }
+ elsif ($op eq 'complex' || $op eq 'product' || $op eq 'quotient') {
+ six_round($$number{arg1});
+ six_round($$number{arg2}); }
+ elsif ($op eq 'comparator' || $op eq 'exponent') {
+ six_round($$number{arg1}); }
+ else {
+ Error('operator', 'operator', undef, "unknown operator: $op"); } }
+ else {
+ my $precision = six_get('round-precision')->toString;
+ shift(@{ $$number{integer} }) while ($$number{integer} && scalar(@{ $$number{integer} }) && $$number{integer}[0]->ToString eq '0');
+ if ($mode eq 'figures') {
+ my $excess_zeros = 0;
+ if (number_is_zero($$number{integer})) {
+ foreach my $digit (@{ $$number{fraction} }) {
+ if ($digit->ToString eq '0') {
+ $excess_zeros++; }
+ else {
+ last; } } }
+ six_round_simple_number($number, $precision + $excess_zeros, 0); }
+ elsif ($mode eq 'places') {
+ six_round_simple_number($number, $precision + scalar(@{ $$number{integer} || [] }), 0); }
+ else {
+ Fatal('internal', 'unreachable', undef, 'this code should not have been reached'); } }
+ return; }
+
+sub six_round_uncertain_number {
+ my ($number) = @_;
+ my $precision = six_get('round-precision')->toString;
+ shift(@{ $$number{arg2}[0]{integer} }) while (scalar(@{ $$number{arg2}[0]{integer} }) && $$number{arg2}[0]{integer}[0]->equals(T_OTHER(0)));
+ my $digits_lost = six_round_simple_number($$number{arg2}[0], $precision, 1);
+ for (1 .. $#{ $$number{arg2} }) {
+ shift(@{ $$number{arg2}[$_]{integer} }) while (scalar(@{ $$number{arg2}[$_]{integer} }) && $$number{arg2}[$_]{integer}[-1]->equals(T_OTHER(0)));
+ six_round_simple_number($$number{arg2}[$_], $precision, 1); }
+ my $new_precision = scalar(@{ $$number{arg1}{integer} || [] }) + scalar(@{ $$number{arg1}{fraction} || [] }) - $digits_lost;
+ my $added_zeros = six_round_simple_number($$number{arg1}, $new_precision, 0);
+ if ($added_zeros && !($$number{arg1}{fraction} && scalar(@{ $$number{arg1}{fraction} }))) {
+ foreach my $uncert (@{ $$number{arg2} }) {
+ if (!defined $$uncert{fraction}) {
+ push(@{ $$uncert{integer} }, T_OTHER(0)) for (1 .. $added_zeros); } } }
+ return; }
+
+# uncertain numbers are rounded in two stages: first the uncertainty, then the number
+# each stage can affect the other part, so we return the necessary information:
+# when rounding the uncertainty, how many digits were truncated away
+# when rounding the main number, how many zeros pad the integer part
+sub six_round_simple_number {
+ my ($number, $figures, $is_uncertainty) = @_;
+ my ($stolen_from_integer, $fraction_size) = (0, 0);
+ my @dropped;
+ if ($figures <= scalar(@{ $$number{integer} || [] })) {
+ $stolen_from_integer = scalar(@{ $$number{integer} || [] }) - $figures;
+ @dropped = splice(@{ $$number{integer} }, $figures);
+ push(@dropped, @{ $$number{fraction} }) if (scalar(@{ $$number{fraction} || [] }));
+ $fraction_size = scalar(@{ $$number{fraction} || [] });
+ delete $$number{decimal};
+ delete $$number{fraction}; }
+ else {
+ @dropped = splice(@{ $$number{fraction} }, $figures - scalar(@{ $$number{integer} || [] }));
+ $fraction_size = scalar(@{ $$number{fraction} }); }
+ my $round_direction = $is_uncertainty ? six_getChoice('uncertainty-round-direction', 'round-direction') : six_getChoice('round-direction');
+ unless (number_is_zero(Tokens(@dropped)) || $round_direction eq 'down') {
+ my $round_up = $round_direction eq 'up';
+ my $first_dropped = shift(@dropped)->toString;
+ $round_up ||= (5 < $first_dropped || (5 == $first_dropped && !number_is_zero(Tokens(@dropped))));
+ if (5 == $first_dropped && number_is_zero(Tokens(@dropped))) {
+ $round_up ||= six_getChoice('round-half') eq 'up';
+ my $remaining = scalar(@{ $$number{fraction} || [] }) ? $$number{fraction}[-1]->toString
+ : (scalar(@{ $$number{integer} || [] }) ? $$number{integer}[-1]->toString : 0);
+ $round_up ||= $remaining % 2; } # round-half eq 'even'
+ if ($round_up && round_last_digit_up($number)) {
+ # we added an extra figure at the beginning of {integer}. to compensate, drop one at the end
+ if (scalar(@{ $$number{fraction} || [] })) {
+ pop(@{ $$number{fraction} }); }
+ else {
+ if ($is_uncertainty) {
+ $stolen_from_integer += $#{ $$number{integer} };
+ $$number{integer} = Tokenize(1); } } } }
+ unless ($is_uncertainty) {
+ push(@{ $$number{integer} }, Token(0)) for (1 .. $stolen_from_integer); }
+ if (six_getBool('round-pad') && !$is_uncertainty) {
+ push(@{ $$number{fraction} }, T_OTHER(0)) while (scalar(@{ $$number{integer} || [] }) + scalar(@{ $$number{fraction} || [] }) < $figures);
+ $$number{fraction} = Tokens(@{ $$number{fraction} || [] }); }
+ if (!$is_uncertainty && is_explicit_zero($number)) {
+ if (my $round_min = six_get('round-minimum')->toString) {
+ $$number{arg1} = six_parse_number(undef, Tokenize($round_min));
+ if ($$number{sign}) {
+ $$number{arg1}{sign} = $$number{sign}; }
+ $$number{comparator} = ($$number{sign} && $$number{sign}->toString eq '-') ? Tokenize('>') : Tokenize('<');
+ delete $$number{$_} foreach (qw(sign integer decimal fraction));
+ $$number{operator} = 'comparator'; }
+ elsif (six_getBool('round-zero-positive')) {
+ delete $$number{sign}; }
+ elsif ($$number{sign} && ToString($$number{sign}) eq '-') {
+ $$number{rounded_negative} = 1; } }
+ return ($is_uncertainty ? $fraction_size : 0) + $stolen_from_integer; }
+
+# returns that a leading 9 was rounded up to 10
+sub round_last_digit_up {
+ my ($number) = @_;
+ my $i = $#{ $$number{fraction} }; # this instantiates {frac} as [] if it wasn't there already
+ while (0 <= $i) {
+ my $digit = ($$number{fraction}[$i]->toString + 1) % 10;
+ $$number{fraction}[$i] = Token($digit);
+ if ($digit) {
+ $$number{fraction} = Tokens($$number{fraction});
+ return 0; }
+ $i--; }
+ if (scalar(@{ $$number{fraction} })) {
+ $$number{fraction} = Tokens($$number{fraction}); }
+ else {
+ delete $$number{fraction}; }
+ $i = $#{ $$number{integer} };
+ while (0 <= $i) {
+ my $digit = ($$number{integer}[$i]->toString + 1) % 10;
+ $$number{integer}[$i] = Token($digit);
+ if ($digit) {
+ $$number{integer} = Tokens($$number{integer});
+ return 0; }
+ $i--; }
+ unshift(@{ $$number{integer} }, Token(1));
+ $$number{integer} = Tokens($$number{integer});
+ return 1; }
+
sub show_thing {
my ($thing) = @_;
return (ref $thing eq 'HASH'
? '{' . join(',', map { $_ . '=' . show_thing($$thing{$_}); } grep { defined $$thing{$_}; } sort keys %$thing) . '}'
- : ToString($thing)); }
+ : (ref $thing eq 'ARRAY'
+ ? '[' . join(',', map { show_thing($_) } @$thing) . ']'
+ : ToString($thing))); }
+
+# or maybe six_number_string
+sub six_UnTeX {
+ my ($number) = @_;
+ my $op = $$number{operator};
+ if ($op && $op eq 'comparator') {
+ return UnTeX($$number{comparator}) . six_UnTeX($$number{arg1}); }
+ if ($op && $op eq 'exponent') {
+ return six_UnTeX($$number{arg1}) . 'e' . six_UnTeX($$number{arg2}); }
+ if ($op && $op eq 'uncertain') {
+ return six_UnTeX($$number{arg1}) . join('', map { '(' . six_UnTeX($_) . ')' } @{ $$number{arg2} });
+#return six_UnTeX($$number{arg1}) . '(' . join( ')(', map(&six_UnTeX, @{$$number{arg2}}) ) . ')'; # deep recursion ?
+ }
+ return ($$number{sign} ? UnTeX($$number{sign}) : '')
+ . ($$number{integer} ? UnTeX($$number{integer}) : '')
+ . ($$number{decimal} ? UnTeX($$number{decimal}) : ($$number{fraction} ? '.' : ''))
+ . ($$number{fraction} ? UnTeX($$number{fraction}) : ''); }
+
+# six_format_simplenumber looks at if ( ToString($sign) eq '+' )
+# to determine if an implicit plus should be dropped
+# because ToString(T_CS('\lx@explicit@plus')) eq '\lx@explicit@plus' ne '+',
+# that test fails and the sign is printed
+DefMacroI('\lx@explicit@plus', undef, '+');
+
+sub six_format_asymmetric_uncertainnumber {
+ my ($number, %flags) = @_;
+ my $arg1 = $$number{arg1};
+ my $arg2 = $$number{arg2};
+ my @meaning_args = ({}, I_symbol({ meaning => 'uncertain' }), I_arg(1));
+ my @display_args = ({}, ($flags{bracket} ? six_get('open-bracket') : ()), I_arg(1));
+ my $counter = 1;
+ my @used_args = (six_format_number($arg1));
+ my $seps = six_compute_separate_uncertainties($number);
+
+ foreach my $sep (@{$seps}) {
+ if (($$sep{ainteger} || $$sep{afraction})
+ && (!six_getBool('simplify-uncertainty')
+ || (defined $$sep{integer} xor defined $$sep{ainteger})
+ || (defined $$sep{fraction} xor defined $$sep{afraction})
+ || ($$sep{integer} && ToString($$sep{integer}) ne ToString($$sep{ainteger}))
+ || ($$sep{fraction} && ToString($$sep{fraction}) ne ToString($$sep{afraction})))) {
+ push(@meaning_args, I_arg(++$counter), I_arg($counter + 1));
+ push(@display_args, T_SUPER, T_BEGIN, I_arg($counter), T_END, T_SUB, T_BEGIN, I_arg(++$counter), T_END);
+ push(@used_args,
+ six_format_number({ sign => T_CS('\lx@explicit@plus'), integer => $$sep{integer}, decimal => $$sep{decimal}, fraction => $$sep{fraction} }),
+ six_format_number({ sign => T_OTHER('-'), integer => $$sep{ainteger}, decimal => $$sep{decimal}, fraction => $$sep{afraction} })); }
+ else {
+ $$sep{sign} = T_CS('\pm');
+ push(@meaning_args, I_arg(++$counter));
+ push(@display_args, I_arg($counter));
+ push(@used_args, six_format_number($sep)); } }
+ push(@display_args, ($flags{bracket} ? six_get('close-bracket') : ()));
+ return I_dual({ revert_as => 'presentation' }, I_apply(@meaning_args), I_wrap(@display_args), @used_args); }
sub six_format_uncertainnumber {
- my ($number, $bracket) = @_;
+ my ($number, %flags) = @_;
+ my $op = $$number{operator};
my $arg1 = $$number{arg1};
my $arg2 = $$number{arg2};
- return six_format_number($arg1) if !$arg2 || six_getBool('omit-uncertainty');
- my $sep = six_compute_separate_uncertainty($number);
- if (six_getBool('separate-uncertainty')) {
- my $sign = $$sep{sign};
- $$sep{sign} = undef;
- return I_dual({ revert_as => 'presentation' },
- I_apply({}, I_symbol({ meaning => 'uncertain' }), I_arg(1), I_arg(2)),
- I_wrap({},
- ($bracket > 0 ? six_get('open-bracket') : ()),
- I_arg(1), (six_get('uncertainty-separator') || ()), ($sign || ()), I_arg(2),
- ($bracket > 0 ? six_get('close-bracket') : ())),
- six_format_number($arg1), six_format_number($sep)); }
+ $flags{table_uncertain_checked} = 1;
+ if ($flags{in_table_number} && (!$op || $op ne 'uncertain')) {
+ ${ $flags{pad_right_ref} } .= ('2' x (1 + six_get('table-figures-uncertainty')->ToString)); # () is about the same width as +
+ return I_wrap(undef, six_format_number($number, %flags)); }
+ return six_format_number($arg1, %flags) if (!scalar(@{$arg2}));
+ my $uncertainty_mode = six_getChoice('uncertainty-mode');
+ $uncertainty_mode = 'separate' if (six_getBool('separate-uncertainty'));
+ $flags{bracket} ||= $flags{in_sci_number} && six_getBool('bracket-ambiguous-numbers') && $uncertainty_mode eq 'separate';
+ foreach my $uncertainty (@{$arg2}) {
+ if ($$uncertainty{ainteger} || $$uncertainty{afraction}) {
+ return six_format_asymmetric_uncertainnumber($number, %flags); } }
+ my $seps = six_compute_separate_uncertainties($number);
+ my $rels;
+ $rels = six_compute_relative_uncertainties($number) if $uncertainty_mode ne 'separate'; # detects sign, MODIFIES number!!! # todo upgrdae remove conditional
+ my @meaning_args = map { I_arg($_) } (1 .. scalar(@{$seps}) + 1);
+ my @dual_args = ({ revert_as => 'presentation' },
+ I_apply({}, I_symbol({ meaning => 'uncertain' }), @meaning_args));
+ if ($uncertainty_mode eq 'separate' || scalar(@{$seps}) > 1) {
+ # this does not get descriptors into the meaning
+ my @descriptors = six_get('uncertainty-descriptors') && split /\s*,\s*/, six_get('uncertainty-descriptors')->toString;
+ my $descriptorMode = six_getChoice('uncertainty-descriptor-mode');
+ my @wrap_args = ({}, ($flags{bracket} ? six_get('open-bracket') : ()), I_arg(1));
+ foreach my $i (0 .. $#{$seps}) {
+ my $sign = $$seps[$i]{sign};
+ delete $$seps[$i]{sign};
+ push(@wrap_args, (six_get('uncertainty-separator') || ()), ($sign || ()), I_arg(2 + $i));
+ if (1 < scalar(@descriptors) && $i < scalar(@descriptors) && 1 < scalar(@{$seps})) {
+ push(@wrap_args, six_get('uncertainty-descriptor-separator')) if ($descriptorMode =~ /separator$/);
+ my $text = $descriptors[$i];
+ $text = "($text)" if ($descriptorMode =~ /^bracket/);
+ $text = "_{$text}" if ($descriptorMode eq 'subscript');
+ push(@wrap_args, Tokenize($text)); } }
+ push(@wrap_args, ($flags{bracket} ? six_get('close-bracket') : ()));
+ my $formatted = six_format_number($arg1, %flags, bracket => 0);
+ if ($flags{in_table_number} && six_getBool('table-align-uncertainty') && ${ $flags{pad_right_ref} }) {
+ push(@{$formatted}, six_table_space(${ $flags{pad_right_ref} }));
+ ${ $flags{pad_right_ref} } = ''; }
+ push(@dual_args, I_wrap(@wrap_args), $formatted);
+ foreach (@{$seps}) {
+ push(@dual_args, six_format_number($_, %flags, bracket => 0)); }
+ return I_dual(@dual_args); }
else {
- my $rel = six_compute_relative_uncertainty($number); # detects sign, MODIFIES number!!!
- $$sep{sign} = undef; # AFTER computing relative!!!
- return I_dual({ revert_as => 'presentation' },
- I_apply({}, I_symbol({ meaning => 'uncertain' }), I_arg(1), I_arg(2)),
- I_wrap({}, I_arg(1), (six_get('uncertainty-separator') || ()), I_arg(2)),
- six_format_number($arg1),
- I_wrap({ meaning => six_number_string($sep) },
- six_get('output-open-uncertainty'), six_format_number($rel),
- six_get('output-close-uncertainty'))); } }
+# exactly one symmetric uncertainty in parentheses. but just in case, this works for several symmetric uncertainties
+ my @wrap_args = ({}, ($flags{bracket} ? Tokens(six_get('uncertainty-separator'), six_get('open-bracket')) : ()), I_arg(1));
+ my @dual_tail_args;
+ for my $i (0 .. $#{$seps}) {
+ push(@wrap_args, (six_get('uncertainty-separator') || ()), I_arg(2 + $i));
+ my $sep = $$seps[$i];
+ my $rel = $$rels[$i];
+ delete $$sep{sign}; # AFTER computing relative!!!
+ my $uncert = $uncertainty_mode eq 'compact'
+ || ($uncertainty_mode eq 'compact-marker'
+ && (number_is_zero($$sep{integer}) || number_is_zero($$sep{fraction}))) ? $rel : $sep;
+ if ($flags{in_table_number}) {
+ my $need_digits = six_get('table-figures-uncertainty')->ToString;
+ ${ $flags{pad_right_ref} } .= '2' x ($need_digits - @{ $$uncert{integer} }) if (@{ $$uncert{integer} } < $need_digits); }
+ push(@dual_tail_args,
+ I_wrap({ meaning => six_number_string($sep) },
+ six_get('output-open-uncertainty'), six_format_number($uncert, %flags, in_uncertainty => 1, bracket => 0),
+ six_get('output-close-uncertainty'))); }
+ push(@wrap_args, ($flags{bracket} ? six_get('close-bracket') : ()));
+ push(@dual_args, I_wrap(@wrap_args), six_format_number($arg1, %flags, bracket => 0), @dual_tail_args);
+ return I_dual(@dual_args); } }
sub six_format_complexnumber {
- my ($number, $bracket) = @_;
- my $arg1 = $$number{arg1};
- my $arg2 = $$number{arg2};
- my $real = six_format_number($arg1);
- my $imag = six_format_number($arg2);
+ my ($number, %flags) = @_;
+ my $arg1 = $$number{arg1};
+ my $arg2 = $$number{arg2};
+ my $complexMode = six_getChoice('complex-mode');
+ # we may run into problems that the input was rounded before it got to this point
+ if (exists $$number{input_form} && $$number{input_form} eq 'polar' && $complexMode eq 'cartesian') {
+ my $parsedArg1 = six_UnTeX($arg1) || 0;
+ my $parsedArg2 = six_UnTeX($arg2) || 0;
+ if (six_getChoice('complex-angle-unit') eq 'degrees') {
+ $parsedArg2 %= 360;
+ $parsedArg2 = deg2rad($parsedArg2); }
+ ($parsedArg1, $parsedArg2) = $parsedArg2 == pi ? (-$parsedArg1, 0) : cylindrical_to_cartesian($parsedArg1, $parsedArg2);
+ # or within epsilon of pi?
+ $arg1 = $parsedArg1 ? six_parse_number(undef, Tokenize($parsedArg1)) : undef;
+ $arg2 = $parsedArg2 ? six_parse_number(undef, Tokenize($parsedArg2)) : undef;
+ six_round($arg1);
+ six_round($arg2);
+# $$number{sign} ||= $arg2 && ($$arg2{sign} || ($$arg2{operator} && $$arg2{operator} eq 'exponent' && $$arg2{arg1} && $$arg2{arg1}{sign}))
+# ? undef : T_OTHER('+');
+ $$number{sign} ||= ($arg2 && $$arg2{sign}) || T_OTHER('+'); }
+ elsif ((!exists $$number{input_form}) && $complexMode eq 'polar') {
+ my $parsedArg1 = six_UnTeX($arg1) || 0;
+ my $parsedArg2 = six_UnTeX($arg2) || 0;
+ if ($parsedArg1 == 0 && $parsedArg2 == 0 && $$number{operator} eq 'complex' && $$number{symbol}->ToString eq 'i') {
+ $parsedArg2 = 1; # seems to be a one off case
+ $$number{sign} ||= T_OTHER('+'); }
+ ($parsedArg1, $parsedArg2) = cartesian_to_cylindrical($parsedArg1, $parsedArg2);
+ if (six_getChoice('complex-angle-unit') eq 'degrees') {
+ $parsedArg2 = rad2deg($parsedArg2); }
+ $arg1 = six_parse_number(undef, Tokenize($parsedArg1));
+ $arg2 = six_parse_number(undef, Tokenize($parsedArg2));
+ six_round($arg1);
+ six_round($arg2); }
+ my $real = six_format_number($arg1, %flags, bracket => 0, in_sci_number => 0); # toggle off in_sci_number b/c it's already bracketed
return $real unless $arg2;
- my $i = (six_getBool('copy-complex-root') ? $$number{symbol} : six_get('output-complex-root'));
- $i = I_wrap({ role => 'ID', meaning => 'imaginary-unit' },
- Tokens(T_CS('\text'), T_BEGIN, $i, T_END));
- my $result = six_format_infix(T_CS('\lx@InvisibleTimes'), undef, undef,
- (six_getChoice('complex-root-position') eq 'before-number' ? ($i, $imag) : ($imag, $i)));
- if (!$arg1) { # Force sign on pure imaginary?
- if ((ToString($$number{sign}) eq '+') && six_getBool('retain-explicit-plus')) {
- $result = I_wrap({}, $$number{sign}, $result); } }
- else {
- $result = six_format_infix(
- $$number{sign}, # Hopefully has proper semantics?
- ($bracket > 0 ? six_get('open-bracket') : undef),
- ($bracket > 0 ? six_get('close-bracket') : undef),
- $real, $result); }
- return $result; }
+ if ($complexMode eq 'cartesian' || ($complexMode eq 'input' && !exists $$number{input_form})) { # Cartesian output
+ if (six_getBool('print-complex-unity') && !defined $$arg2{integer} && !defined $$arg2{fraction}) {
+ $$arg2{integer} = T_OTHER(1); }
+ if (!six_getBool('print-complex-unity') && $$arg2{integer} && number_is_zero($$arg2{fraction})
+ && ($$arg2{integer}->ToString eq '1')) {
+ $arg2 = undef; }
+ my $sign = $$arg2{sign} || ($arg1 && T_OTHER('+'));
+ delete $$arg2{sign} if $$arg2{sign};
+ my $imag = %$arg2 ? six_format_number($arg2, %flags, bracket => 0, in_sci_number => 0) : (); # toggle off in_sci_number b/c it's already bracketed
+ my $i = (six_getBool('copy-complex-root') ? $$number{symbol} : six_get('output-complex-root'));
+ $i = I_wrap({ role => 'ID', meaning => 'imaginary-unit' }, Invocation(T_CS('\text'), $i));
+ my $result = six_format_infix(T_CS('\lx@InvisibleTimes'), undef, undef,
+ (six_getChoice('complex-root-position') eq 'before-number' ? ($i, $imag) : ($imag, $i)));
+ $flags{bracket} ||= $flags{in_sci_number} && six_getBool('bracket-ambiguous-numbers');
+ if ($arg1 && keys %$arg1) {
+ $result = six_format_infix(
+ $sign, # Hopefully has proper semantics?
+ ($flags{bracket} ? six_get('open-bracket') : undef),
+ ($flags{bracket} ? six_get('close-bracket') : undef),
+ $real, $result); }
+ else { # Force sign on pure imaginary?
+ if ((ToString($sign) eq '+') && six_getBool('retain-explicit-plus')) {
+ $result = I_wrap({}, $sign, $result); }
+ if ($flags{bracket}) {
+ $result = I_wrap({}, six_get('open-bracket'), $sign || (), $result, six_get('close-bracket')); } }
+ return $result; }
+ else { # polar output
+ my $imag = six_format_number($arg2, %flags, bracket => 0);
+ my $angleMark = six_get('complex-phase-command');
+ my $degreeMark = six_get('complex-symbol-degree');
+ my $result = six_getChoice('complex-angle-unit') eq 'degrees' ?
+ six_format_infix(T_CS('\lx@InvisibleTimes'), undef, undef, $imag, $degreeMark) :
+ $imag;
+ # there should be a better way to do this. but we have
+ # $real $angleMark $$number{sign} $imag
+ # with both middle terms being an infix?
+ if ($$number{sign} && ($$number{sign}->toString ne '+' || six_getBool('retain-explicit-plus'))) {
+ $result = six_format_infix(undef, undef, undef, $$number{sign}, $result); }
+ $result = six_format_infix($angleMark, undef, undef, $real, $result);
+ return $result; } }
sub six_format_scinumber {
- my ($number, $bracket) = @_;
+ my ($number, %flags) = @_;
+ my $op = $$number{operator};
my $arg1 = $$number{arg1};
my $arg2 = $$number{arg2};
+ $flags{table_exponent_checked} = 1;
my $result;
- # NOTE: Not yet handled: retain-unity-mantissa
- if (!six_getBool('retain-zero-exponent')
- && !ToString($$arg2{integer})
- && !ToString($$arg2{fraction})) {
- $result = six_format_number($arg1); }
+ if ($flags{in_table_number} && (!$op || $op ne 'exponent')) {
+ # there's no exponent, but we want space for one. omitting the mathord causes extra space
+ ${ $flags{pad_right_ref} } .= '222' . ('.' x six_get('table-figures-exponent')->ToString); # \mathord x10^{...}
+ return I_wrap(undef, six_format_number($number, %flags)); }
+ if (!six_getBool('print-zero-exponent') && !ToString($$arg2{integer}) && !ToString($$arg2{fraction})) {
+ # there is no arg2. add padding for \times10^{...}
+ ${ $flags{pad_right_ref} } .= '222' . ('.' x six_get('table-figures-exponent')->ToString) if $flags{in_table_number};
+ $result = six_format_number($arg1, %flags, in_sci_number => 1); }
elsif (my $marker = six_get('output-exponent-marker')) {
- $result = six_format_infix(
- T_CS('\lx@InvisibleTimes'),
- ($bracket > 1 ? six_get('open-bracket') : undef),
- ($bracket > 1 ? six_get('close-bracket') : undef),
- six_format_number($arg1, 1),
- I_dual({}, # Means base^arg2, but looks like marker arg2 !!
+# the order of the conditionals is important here - $$arg1{integer} causes an $arg1 undef to become {} which is true
+ if ((six_getBool('print-unity-mantissa') && $arg1)
+ || ($$arg1{integer} && ToString($$arg1{integer}) ne '1')
+ || $$arg1{fraction} || $$arg1{operator}) {
+ my $mantissa = six_format_number($arg1, %flags, in_sci_number => 1); # , bracket => 0 todo upgrade do not need
+ if ($flags{in_table_number} && six_getBool('table-align-exponent')) {
+ push(@{$mantissa}, six_table_space(${ $flags{pad_right_ref} }));
+ ${ $flags{pad_right_ref} } = ''; }
+ $result = six_format_infix(
+ T_CS('\lx@InvisibleTimes'),
+ ($flags{bracket} > 1 ? six_get('open-bracket') : undef),
+ ($flags{bracket} > 1 ? six_get('close-bracket') : undef),
+ $mantissa,
+ I_dual({}, # Means base^arg2, but looks like marker arg2 !!
+ I_apply({}, I_symbol({ meaning => 'power' }), six_get('exponent-base'), I_arg(1)),
+ I_wrap({}, $marker, I_arg(1)),
+ six_format_number($arg2, %flags, in_sci_number => 1, in_exponent => 1, bracket => 0))); }
+ else {
+ $result = I_dual({}, # Means base^arg2, but looks like marker arg2 !!
I_apply({}, I_symbol({ meaning => 'power' }), six_get('exponent-base'), I_arg(1)),
I_wrap({}, $marker, I_arg(1)),
- six_format_number($arg2))); }
+ six_format_number($arg2, %flags, in_sci_number => 1, in_exponent => 1, bracket => 0)); } }
else {
- $result = six_format_infix(
- ($$arg1{integer} || $$arg1{fraction} || $$arg1{operator}
- ? six_get_op({ role => 'MULOP', meaning => 'times' }, 'exponent-product')
- : T_CS('\lx@InvisibleTimes')),
- ($bracket > 1 ? six_get('open-bracket') : undef),
- ($bracket > 1 ? six_get('close-bracket') : undef),
- six_format_number($arg1, 1),
- I_superscript({ operator_meaning => 'power' }, six_get('exponent-base'),
- six_format_number($arg2))); }
+# the order of the conditionals is important here - $$arg1{integer} causes an $arg1 undef to become {} which is true
+ if ((six_getBool('print-unity-mantissa') && $arg1)
+ || ($$arg1{integer} && ToString($$arg1{integer}) ne '1')
+ || $$arg1{fraction} || $$arg1{operator}) {
+ my $mantissa = six_format_number($arg1, %flags, in_sci_number => 1, bracket => 0);
+ if ($flags{in_table_number} && six_getBool('table-align-exponent')) {
+ push(@{$mantissa}, six_table_space(${ $flags{pad_right_ref} }));
+ ${ $flags{pad_right_ref} } = ''; }
+ $result = six_format_infix(
+ ($$arg1{integer} || $$arg1{fraction} || $$arg1{operator}
+ ? six_get_op({ role => 'MULOP', meaning => 'times' }, 'exponent-product')
+ : T_CS('\lx@InvisibleTimes')),
+ ($flags{bracket} > 1 ? six_get('open-bracket') : undef),
+ ($flags{bracket} > 1 ? six_get('close-bracket') : undef),
+ $mantissa,
+ I_superscript({ operator_meaning => 'power' }, six_get('exponent-base'),
+ six_format_number($arg2, %flags, in_sci_number => 1, in_exponent => 1, bracket => 0))); }
+ else {
+ # there is no arg1
+ if ($flags{in_table_number}) {
+ ${ $flags{pad_left_ref} } .= '2' x six_get('table-figures-uncertainty')->ToString;
+ ${ $flags{pad_left_ref} } .= '2' if (six_get('table-figures-uncertainty')->ToString); # for the () or +-
+ ${ $flags{pad_left_ref} } .= (six_getBool('table-sign-mantissa') ? '2' : '')
+ . ('2' x six_get('table-figures-integer')->ToString)
+ . '.'
+ . ('2' x six_get('table-figures-decimal')->ToString) . '2'; } # last 2 is for \times
+ $result = I_superscript({ operator_meaning => 'power' }, six_get('exponent-base'),
+ six_format_number($arg2, %flags, in_sci_number => 1, in_exponent => 1, bracket => 0)); } }
# If mantissa is simple number, use scientific notation for the meaning
# (all the dual cruft above formats appropriately, but is wasted)
if ($arg1 && !$$arg1{operator} && (ToString(six_get('exponent-base')) eq '10')) {
- $result = I_wrap({ meaning => six_number_string($number) }, $result); }
+ $$number{arg1} = $arg1;
+ $$number{arg2} = $arg2;
+ $result = I_wrap({ meaning => six_number_string($number) }, $result); }
return $result; }
sub six_format_compoundnumber {
- my ($number, $bracket) = @_;
+ my ($number, %flags) = @_;
my $op = $$number{operator};
my $arg1 = $$number{arg1};
my $arg2 = $$number{arg2};
+ $flags{table_comparator_checked} = 1;
my $result;
- if ($op eq 'comparator') {
+ if ($flags{in_table_number} && (!$op || $op ne 'comparator')) {
+ # there should be a comparator, but isn't
+ ${ $flags{pad_left_ref} } = '2' . ${ $flags{pad_left_ref} };
+ $result = I_wrap({}, six_format_number($number, %flags)); }
+ elsif ($op eq 'comparator') {
# NOTE: Semantic?
- $result = I_wrap({},
- ($$number{comparator} || ()),
- six_format_number($arg1)); }
+ my $formatted = six_format_number($arg1, %flags);
+ if ($flags{in_table_number} && six_getBool('table-align-comparator')) {
+ unshift(@{$formatted}, six_table_space(${ $flags{pad_left_ref} }));
+ ${ $flags{pad_left_ref} } = ''; }
+ $result = I_wrap({}, ($$number{comparator} || ()), $formatted); }
elsif ($op eq 'product') {
- $result = six_format_infix(
- six_get_op({ role => 'MULOP', meaning => 'times' }, 'output-product'), undef, undef,
- six_format_number($arg1, 1),
- six_format_number($arg2, 1)); }
+ my $times = six_get('product-mode')->toString eq 'symbol'
+ ? six_get_op({ role => 'MULOP', meaning => 'times' }, 'product-symbol')
+ : six_get_op({ role => 'MULOP', meaning => 'times', mode => 'TEXT' }, 'product-phrase');
+ # but bracket?
+ my $bracket = $$arg1{operator} && $$arg1{operator} eq 'uncertain' && six_getBool('separate-uncertainty');
+ $result = six_format_infix($times, undef, undef,
+ six_format_number($arg1, bracket => $bracket), six_format_number($arg2, bracket => $bracket)); }
+# ($flags{bracket} ? six_get('open-bracket') : ()), ($flags{bracket} ? six_get('close-bracket') : ()),
elsif ($op eq 'quotient') {
if (six_getChoice('quotient-mode') eq 'fraction') {
$result = Tokens(
@@ -636,28 +1424,85 @@ sub six_format_compoundnumber {
else {
$result = six_format_infix(
six_get_op({ role => 'MULOP', meaning => 'divide' }, 'output-quotient'), undef, undef,
- six_format_number($arg1, 1),
- six_format_number($arg2, 2)); } }
+ six_format_number($arg1, bracket => 1),
+ six_format_number($arg2, bracket => 2)); } }
else {
Error('unexpected', $op, undef, "Unrecognized operator $op in siunitx number"); }
return $result; }
+# Formats a number, by formatting each of the parts of the number
+# By adjusting the %flags, this also allows us to format a tabular number (adding extra spacing)
sub six_format_number {
- my ($number, $bracket) = @_;
+ my ($number, %flags) = @_;
return unless $number;
return I_wrap({}, $number) unless ref $number eq 'HASH';
- $bracket = 0 unless $bracket && six_getBool('bracket-numbers');
- my @tokens = ();
- if (my $op = $$number{operator}) {
- my $arg1 = $$number{arg1};
- my $arg2 = $$number{arg2};
- if ($op eq 'uncertain') { push(@tokens, six_format_uncertainnumber($number, $bracket)); }
- elsif ($op eq 'complex') { push(@tokens, six_format_complexnumber($number, $bracket)); }
- elsif ($op eq 'exponent') { push(@tokens, six_format_scinumber($number, $bracket)); }
- else { push(@tokens, six_format_compoundnumber($number, $bracket)); } }
+ $flags{bracket} = 0 unless $flags{bracket} && six_getBool('bracket-numbers') && six_getBool('bracket-ambiguous-numbers');
+ my (@tokens, @trailer);
+ my $sign = $$number{sign};
+ my $op = $$number{operator} || 0;
+ my $needs_sign_space = 0;
+ # these are ordered according to how we parse a number
+ # once the number or the table formatter indicate something, we'll take that route
+ if ($flags{in_table_number}) {
+ if (!$flags{table_comparator_checked} && ($op eq 'comparator' || six_getBool('table-comparator'))) {
+ $op = 'comparator'; }
+ elsif (!$flags{table_exponent_checked} && ($op eq 'exponent' || six_get('table-figures-exponent')->toString)) {
+ $op = 'exponent'; }
+ elsif (!$flags{table_uncertain_checked} && ($op eq 'uncertain' || six_get('table-figures-uncertainty')->toString)) {
+ $op = 'uncertain'; } }
+ if ($sign) {
+ delete $$number{sign};
+ if (ToString($sign) eq '-') {
+ if ($op || !number_is_zero($$number{integer}) || !number_is_zero($$number{fraction}) || $$number{rounded_negative}
+ || six_getBool('retain-negative-zero') || ($flags{in_sci_number} && !$flags{in_exponent})) {
+ my $c = six_get('negative-color');
+ if ($c && $op ne 'complex' && !$flags{in_complex_number}) {
+ push(@tokens, T_BEGIN, T_CS('\color'), T_BEGIN, $c->unlist, T_END);
+ unshift(@trailer, T_END); }
+ if (six_getBool('bracket-negative-numbers') && $op ne 'complex' && !$flags{in_complex_number}) {
+ push(@tokens, six_get('open-bracket'));
+ $sign = undef;
+ unshift(@trailer, six_get('close-bracket')); } } }
+ my $mant_exp = $flags{in_exponent} ? 'exponent' : 'mantissa';
+ if ($sign && $sign->ToString eq '+') {
+ if (six_getBool('print-' . $mant_exp . '-implicit-plus', 'retain-explicit-plus') && !$flags{ $mant_exp . '_implicit_plus_present' }) {
+ $flags{ $mant_exp . '_implicit_plus_present' } = 1; }
+ else {
+ $sign = undef } }
+ push(@tokens, $sign) if ($sign); }
+ elsif ($flags{in_exponent}) {
+ if (six_getBool('print-exponent-implicit-plus') && !$flags{exponent_implicit_plus_present}) {
+ $flags{exponent_implicit_plus_present} = 1;
+ push(@tokens, T_OTHER('+')); }
+ elsif ($flags{in_table_number} && six_getBool('table-sign-exponent')) {
+ ${ $flags{pad_right_ref} } .= '.'; } }
+ elsif (!$flags{in_sci_number}) {
+ if (six_getBool('print-mantissa-implicit-plus') && !$flags{mantissa_implicit_plus_present}) {
+ $flags{mantissa_implicit_plus_present} = 1;
+ push(@tokens, T_OTHER('+')); }
+ elsif ($flags{in_table_number} && six_getBool('table-sign-mantissa')) {
+ ${ $flags{pad_left_ref} } .= '2'; } }
+ if ($op) {
+ if ($op eq 'uncertain') { push(@tokens, six_format_uncertainnumber($number, %flags)); }
+ elsif ($op eq 'complex') { push(@tokens, six_format_complexnumber($number, %flags, in_complex_number => 1)); }
+ elsif ($op eq 'exponent') { push(@tokens, six_format_scinumber($number, %flags)); }
+ else { push(@tokens, six_format_compoundnumber($number, %flags)); } }
else {
- push(@tokens, six_format_simplenumber($number, $bracket)); }
- return Tokens(@tokens); }
+ push(@tokens, six_format_simplenumber($number, %flags)); }
+ return Tokens(@tokens, @trailer); }
+
+# we create space with a makebox of the appropriate width (hphantom, ~, and \, didn't work)
+# we would like to use the actual content (similar to hphantom), but it's hard to determine that width (see also #2768)
+# instead, we'll track the content as some count of 2 and ., translating the latter to half the width of the former
+# for debugging, you can change makebox to framebox (but again #2768) or print the spacer in the {}
+my $DIGIT_WIDTH = 200000; # Box('2')->getWidth->[0] is 327681; this is an approximation
+
+sub six_table_space {
+ my ($spacer) = @_;
+ #return Tokenize('\framebox[' . Box($spacer)->getWidth->[0] . 'sp]{'. $spacer .'}');
+ my @twos = $spacer =~ /2/g;
+ my @dots = $spacer =~ /\./g;
+ return Tokenize('\makebox[' . $DIGIT_WIDTH * (2 * @twos + @dots) . 'sp]{}'); } # ' . $spacer .'
# Return the plain-text string for a number, for use in meaning attribute
# Note that the format is pretty ad-hoc, except for simple-numbers
@@ -694,6 +1539,7 @@ sub six_number_string {
'E', six_number_string($arg2),
($bracket > 1 ? ')' : '')); }
else {
+ Error('formatting', 'unknown format', undef, "Unkown number format");
return "Unkown number format"; } }
else {
my $sign = $$number{sign};
@@ -701,8 +1547,7 @@ sub six_number_string {
my $fraction = $$number{fraction};
# Wrong!!!
return join('', ToString($sign), ToString($integer),
- ($fraction ? '.' . ToString($fraction) : ''));
-} }
+ ($fraction ? '.' . ToString($fraction) : '')); } }
#======================================================================
@@ -712,8 +1557,11 @@ sub six_format_range {
six_get_op({ role => 'PUNCT' }, 'range-phrase'),
I_arg(2));
if ($bracketed_p) {
- unshift(@range, six_get_op({ role => 'OPEN' }, 'open-bracket'));
- push(@range, six_get_op({ role => 'CLOSE' }, 'close-bracket')); }
+ unshift(@range, six_get_op({ role => 'OPEN' }, 'range-open-bracket', 'open-bracket'));
+ push(@range, six_get_op({ role => 'CLOSE' }, 'range-close-bracket', 'close-bracket')); }
+ if (six_get('range-open-phrase')) {
+ # todo: Warning:not_parsed:>PUNCT MathParser failed to match rule 'Anything'
+ unshift(@range, six_get_op({ role => 'PUNCT' }, 'range-open-phrase')); }
return I_dual({},
I_apply({}, I_symbol({ meaning => 'range' }), I_arg(1), I_arg(2)),
I_wrap({}, @range),
@@ -737,8 +1585,8 @@ sub six_format_list {
push(@list, six_get_op({ role => 'PUNCT' }, 'list-final-separator'),
I_arg($nitems)); }
if (($nitems > 1) && $bracketed_p) {
- unshift(@list, six_get_op({ role => 'OPEN' }, 'open-bracket'));
- push(@list, six_get_op({ role => 'CLOSE' }, 'close-bracket')); }
+ unshift(@list, six_get_op({ role => 'OPEN' }, 'list-open-bracket', 'open-bracket'));
+ push(@list, six_get_op({ role => 'CLOSE' }, 'list-close-bracket', 'close-bracket')); }
return I_dual({},
I_apply({}, I_symbol({ meaning => 'list' }), map { I_arg($_); } 1 .. $nitems),
I_wrap({}, @list),
@@ -762,29 +1610,133 @@ sub six_wrap {
DefMacro('\num OptionalKeyVals:SIX {}', sub {
my ($gullet, $kv, $number) = @_;
six_begin_processing($gullet, $kv);
- my $result = six_wrap(six_format_number(six_parse_number($gullet, $number)));
+ my $parsed = six_parse_number($gullet, $number);
+ my $product_exponents = six_getChoice('product-exponents');
+ my $min_exp = undef;
+ if ($product_exponents =~ /combine/) {
+ # you are a bad person
+ my $other = $parsed;
+ while ($$other{operator} && $$other{operator} eq 'product') {
+ my $next_exp = six_get_sci_exp($$other{arg2});
+ if (!defined $min_exp || $next_exp < $min_exp) {
+ $min_exp = $next_exp; }
+ $other = $$other{arg1}; }
+ my $next_exp = six_get_sci_exp($other);
+ if (!defined $min_exp || $next_exp < $min_exp) {
+ $min_exp = $next_exp; }
+ $other = $parsed;
+ my $container;
+ while ($$other{operator} && $$other{operator} eq 'product') {
+ $$other{arg2} = six_get_number_with_sci_exp($$other{arg2}, $min_exp);
+ $container = $other;
+ $other = $$other{arg1}; }
+ $$container{arg1} = six_get_number_with_sci_exp($other, $min_exp); }
+ my $formatted = six_format_number($parsed);
+ if ($product_exponents =~ /combine/ && ($min_exp || six_getBool('print-zero-exponent'))) {
+ if ($product_exponents eq 'combine-bracket') {
+ $formatted = six_wrap(six_get('open-bracket'), $formatted, six_get('close-bracket')); }
+ $formatted = six_format_infix(six_get_op({ role => 'MULOP', meaning => 'times' }, 'exponent-product'),
+ undef, undef, $formatted,
+ I_superscript({ operator_meaning => 'power' }, six_get('exponent-base'),
+ six_format_number({ integer => Tokenize($min_exp) }, bracket => 0, in_sci_number => 1))); }
+ my $result = six_wrap($formatted);
six_end_processing();
return $result; });
+Let('\tablenum', '\num'); # was \lx@table@num
+Let(T_CS('\complexnum'), T_CS('\num'));
+Let(T_CS('\numproduct'), T_CS('\num'));
# \numlist[options]{number;number;...}
DefMacro('\numlist OptionalKeyVals:SIX {}', sub {
my ($gullet, $kv, $numbers) = @_;
six_begin_processing($gullet, $kv);
- my @numbers = six_parse_numbers($gullet, $numbers);
- my @formatted = six_wrap(six_format_list(0, map { six_format_number($_); } @numbers));
+ my @numbers = six_parse_numbers($gullet, $numbers);
+ if (six_get('list-input-separator')->toString eq six_get('complex-angle-separator')->toString) {
+ my @new_numbers = ();
+ foreach my $number (@numbers) {
+ if ($$number{arg1} and $$number{arg2} and $$number{operator} eq 'complex' and $$number{input_form} eq 'polar') {
+ push(@new_numbers, $$number{arg1}, $$number{arg2}); }
+ else {
+ push(@new_numbers, $number); } }
+ @numbers = @new_numbers; }
+ my $list_exponents = six_getChoice('list-exponents');
+ my $min_exp;
+ if ($list_exponents =~ /combine/) {
+ $min_exp = six_get_sci_exp($numbers[0]);
+ for my $number (@numbers) {
+ my $this_exp = six_get_sci_exp($number);
+ if ($this_exp < $min_exp) {
+ $min_exp = $this_exp; } }
+ for my $i (0 .. $#numbers) {
+ $numbers[$i] = six_get_number_with_sci_exp($numbers[$i], $min_exp); } }
+ my $formatted = six_format_list($list_exponents eq 'combine-bracket', map { six_format_number($_); } @numbers);
+ if ($list_exponents =~ /combine/ && ($min_exp || six_getBool('print-zero-exponent'))) {
+ $formatted = six_format_infix(six_get_op({ role => 'MULOP', meaning => 'times' }, 'exponent-product'),
+ undef, undef, $formatted,
+ I_superscript({ operator_meaning => 'power' }, six_get('exponent-base'),
+ six_format_number({ integer => Tokenize(abs($min_exp)), sign => Token($min_exp < 0 ? '-' : '') }, bracket => 0, in_sci_number => 1))); }
+ my @wrapped = six_wrap($formatted);
six_end_processing();
- return Tokens(@formatted); });
+ return Tokens(@wrapped); });
# \numrange[options]{first}{last}
DefMacro('\numrange OptionalKeyVals:SIX {}{}', sub {
my ($gullet, $kv, $first, $last) = @_;
six_begin_processing($gullet, $kv);
- my $result = six_wrap(six_format_range(0,
- six_format_number(six_parse_number($gullet, $first)),
- six_format_number(six_parse_number($gullet, $last))));
+ my $first_parsed = six_parse_number($gullet, $first);
+ my $last_parsed = six_parse_number($gullet, $last);
+ my $range_exponents = six_getChoice('range-exponents');
+ my ($first_exp, $last_exp);
+ if ($range_exponents =~ /combine/) {
+ $first_exp = six_get_sci_exp($first_parsed);
+ $last_exp = six_get_sci_exp($last_parsed);
+ if ($last_exp < $first_exp) {
+ $first_parsed = six_get_number_with_sci_exp($first_parsed, $last_exp);
+ $last_parsed = $$last_parsed{arg1}; }
+ elsif ($first_exp < $last_exp) {
+ $first_parsed = $$first_parsed{arg1};
+ $last_parsed = six_get_number_with_sci_exp($last_parsed, $first_exp);
+ # now make $last_exp the smaller
+ $last_exp = $first_exp; }
+ else {
+ $first_parsed = $$first_parsed{arg1};
+ $last_parsed = $$last_parsed{arg1}; } }
+ my $formatted = six_format_range($range_exponents eq 'combine-bracket',
+ six_format_number($first_parsed),
+ six_format_number($last_parsed));
+ if ($range_exponents =~ /combine/ && ($first_exp || six_getBool('print-zero-exponent'))) {
+ $formatted = six_format_infix(six_get_op({ role => 'MULOP', meaning => 'times' }, 'exponent-product'),
+ undef, undef, $formatted,
+ I_superscript({ operator_meaning => 'power' }, six_get('exponent-base'),
+ six_format_number({ integer => Tokenize(abs($last_exp)), sign => Token($last_exp < 0 ? '-' : '') }, bracket => 0, in_sci_number => 1))); }
+ my $result = six_wrap($formatted);
six_end_processing();
return $result; });
+# transform {number} or {arg1=>number, operator=>'exponent', arg2=>number}
+# into a number with a specified exponent
+# only returns the arg1, not the operator nor the arg2 exponent
+sub six_get_number_with_sci_exp {
+ my ($number, $new_exp) = @_;
+ my $old_exp = six_get_sci_exp($number);
+ $number = $$number{arg1} if $$number{operator} && $$number{operator} eq 'exponent';
+ if ($old_exp != $new_exp) {
+ my @intTokens = $$number{integer} ? $$number{integer}->unlist : ();
+ my @fracTokens = $$number{fraction} ? $$number{fraction}->unlist : ();
+ while ($old_exp < $new_exp) {
+ $old_exp++;
+ unshift(@fracTokens, (pop(@intTokens) || T_OTHER('0'))); }
+ while ($new_exp < $old_exp) {
+ $old_exp--;
+ push(@intTokens, (shift(@fracTokens) || T_OTHER('0'))); }
+ $$number{integer} = @intTokens ? Tokens(@intTokens) : undef;
+ if (@fracTokens) {
+ $$number{fraction} = Tokens(@fracTokens); }
+ else {
+ delete $$number{decimal};
+ delete $$number{fraction}; } }
+ return $number; }
+
# These are in serious need of tweaking!
DefMacro('\lx@arcdegreeoverdot', '\lx@stackrel{\SIUnitSymbolDegree}{.}');
DefMacro('\lx@arcminuteoverdot', '\lx@stackrel{{\scriptstyle\prime}}{.}');
@@ -801,74 +1753,102 @@ DefMacro('\ang OptionalKeyVals:SIX {}', sub {
six_begin_processing($gullet, $kv);
# We REALLY should only allow simplenumbers (even without uncertainty!)!
my ($degrees, $minutes, $seconds) = six_parse_numbers($gullet, $expr);
- # Normalize integer/fraction part.
- my $addd0 = !$degrees && six_getBool('add-arc-degree-zero');
- my $addm0 = !$minutes && six_getBool('add-arc-minute-zero')
- && (!$degrees || !$$degrees{fraction});
- my $adds0 = !$seconds && six_getBool('add-arc-second-zero')
- && (!$degrees || !$$degrees{fraction})
- && (!$minutes || !$$minutes{fraction});
- $degrees = { integer => T_OTHER('0') } if $addd0;
- $minutes = { integer => T_OTHER('0') } if $addm0;
- $seconds = { integer => T_OTHER('0') } if $adds0;
-
+ if (six_getChoice('angle-mode') eq 'arc' && $$degrees{fraction}) {
+ my $oldFraction = UnTeX($$degrees{fraction});
+ my $angle = $oldFraction * 60 / (10**scalar(@{ $$degrees{fraction} }));
+ $minutes = six_parse_number(undef, Tokenize($angle));
+ delete $$degrees{decimal};
+ delete $$degrees{fraction};
+ if ($$minutes{fraction}) {
+ $oldFraction = UnTeX($$minutes{fraction});
+ $angle = $oldFraction * 60 / (10**scalar(@{ $$minutes{fraction} }));
+ $seconds = six_parse_number(undef, Tokenize($angle));
+ delete $$minutes{decimal};
+ delete $$minutes{fraction}; } }
+ if (six_getChoice('angle-mode') eq 'decimal' && ($minutes || $seconds)) {
+ my $fraction = six_parse_number(undef, Tokenize((UnTeX($$minutes{integer}) + UnTeX($$seconds{integer}) / 60) / 60));
+ $$degrees{decimal} = $$fraction{decimal};
+ $$degrees{fraction} = $$fraction{fraction};
+ $minutes = undef;
+ $seconds = undef; }
# Pull out the (overall) sign, assuming(!) the first one applies to all components.
my $sign = ($degrees && $$degrees{sign}) || ($minutes && $$minutes{sign})
|| ($seconds && $$seconds{sign});
- $$degrees{sign} = undef if $degrees;
- $$minutes{sign} = undef if $minutes;
- $$seconds{sign} = undef if $seconds;
- my @punctuated = ();
- my $sep1 = six_get('number-angle-product');
- my $sep2 = six_get('arc-separator');
- my $mulop = I_wrap({ role => 'MULOP', meaning => 'times' },
- ($sep1->unlist ? $sep1 : T_CS('\lx@InvisibleTimes')));
- my $addop = I_wrap({ role => 'ADDOP', meaning => 'plus' },
- ($sep2->unlist ? $sep2 : T_CS('\lx@InvisiblePlus')));
- my $above = six_get('angle-symbol-over-decimal');
- my $save = six_get('copy-decimal-marker');
- AssignValue('SIX_copy-decimal-marker' => 'true');
- # Format degrees, if any
- if ($above && $degrees && $$degrees{decimal}) {
- $$degrees{decimal} = T_CS('\lx@arcdegreeoverdot'); }
- my $fdegrees = $degrees && six_format_number($degrees);
- if ($fdegrees && $fdegrees->unlist) {
- push(@punctuated,($above && $$degrees{decimal} ? $fdegrees
- : I_apply({},$mulop,$fdegrees, T_CS('\SIUnitSymbolDegree')))); }
- # Format minues, if any
- if ($above && $minutes && $$minutes{decimal}) {
- $$minutes{decimal} = T_CS('\lx@arcminuteoverdot'); }
- my $fminutes = $minutes && six_format_number($minutes);
- if ($minutes && $fminutes->unlist) {
- push(@punctuated,($above && $$minutes{decimal} ? $fminutes
- : I_apply({},$mulop,$fminutes, T_CS('\SIUnitSymbolArcminute')))); }
-
- # Format seconds, if any
- if ($above && $seconds && $$seconds{decimal}) {
- $$seconds{decimal} = T_CS('\lx@arcsecondoverdot'); }
- my $fseconds = $seconds && six_format_number($seconds);
- if ($seconds && $fseconds->unlist) {
- push(@punctuated,($above && $$seconds{decimal} ? $fseconds
- : I_apply({},$mulop,$fseconds,T_CS('\SIUnitSymbolArcsecond')))); }
- if(scalar(@punctuated) > 1){
- @punctuated = (I_apply({},$addop,@punctuated)); }
- if ($sign) { # Finally, prepend the sign
- @punctuated = I_apply({},$sign,@punctuated); }
- AssignValue('SIX_copy-decimal-marker' => $save);
- my $string = join('',
- ToString($sign),
- ($degrees ? six_number_string($degrees) . "\x{00B0}" : ''),
- ($minutes ? six_number_string($minutes) . "\x{2032}" : ''),
- ($seconds ? six_number_string($seconds) . "\x{2033}" : ''));
- my $result = six_wrap(I_dual({}, I_symbol({ role => 'NUMBER', meaning => $string }),
- I_wrap({}, @punctuated)));
- six_end_processing();
- return $result; });
+ delete $$degrees{sign} if $degrees;
+ delete $$minutes{sign} if $minutes;
+ delete $$seconds{sign} if $seconds;
+ if (!($degrees && %$degrees) || is_explicit_zero($degrees)) {
+ if (six_getBool('fill-angle-degrees')) {
+ $degrees = { integer => T_OTHER('0') }; }
+ else {
+ $degrees = undef; } }
+ if (!($minutes && %$minutes && !is_explicit_zero($minutes))
+ && (!$degrees || !%$degrees || !$$degrees{fraction})) {
+ if (six_getBool('fill-angle-minutes')) {
+ $minutes = { integer => T_OTHER('0') }; }
+ else {
+ $minutes = undef; } }
+ if (!($seconds && %$seconds && !is_explicit_zero($seconds))
+ && (!$degrees || !%$degrees || !$$degrees{fraction})
+ && (!$minutes || !%$minutes || !$$minutes{fraction})) {
+ if (six_getBool('fill-angle-seconds')) {
+ $seconds = { integer => T_OTHER('0') }; }
+ else {
+ $seconds = undef; } }
+ return six_format_angle($sign, $degrees, $minutes, $seconds); });
+
+sub six_format_angle {
+ my ($sign, $degrees, $minutes, $seconds) = @_;
+ my @punctuated = ();
+ six_enableUnitMacros(1);
+ my $sep1 = six_get('number-angle-product');
+ my $sep2 = six_get('angle-separator', 'arc-separator');
+ my $mulop = I_wrap({ role => 'MULOP', meaning => 'times' }, ($sep1->unlist ? $sep1 : T_CS('\lx@InvisibleTimes')));
+ my $addop = I_wrap({ role => 'ADDOP', meaning => 'plus' }, ($sep2->unlist ? $sep2 : T_CS('\lx@InvisiblePlus')));
+ my $above = six_getBool('angle-symbol-over-decimal');
+ my $save = six_get('copy-decimal-marker');
+ AssignValue('SIX_copy-decimal-marker' => 'true') if ($above);
+ # Format degrees, if any
+ if ($above && $degrees && $$degrees{decimal}) {
+ $$degrees{decimal} = T_CS('\lx@arcdegreeoverdot'); }
+ my $fdegrees = $degrees && six_format_number($degrees);
+ if ($fdegrees && $fdegrees->unlist) {
+ push(@punctuated, ($above && $$degrees{decimal} ? $fdegrees
+ : I_apply({}, $mulop, $fdegrees, Expand(six_get('angle-symbol-degree'))))); }
+ # Format minutes, if any
+ if ($above && $minutes && $$minutes{decimal}) {
+ $$minutes{decimal} = T_CS('\lx@arcminuteoverdot'); }
+ my $fminutes = $minutes && six_format_number($minutes);
+ if ($minutes && $fminutes->unlist) {
+ push(@punctuated, ($above && $$minutes{decimal} ? $fminutes
+ : I_apply({}, $mulop, $fminutes, Expand(six_get('angle-symbol-minute'))))); }
+ # Format seconds, if any
+ if ($above && $seconds && $$seconds{decimal}) {
+ $$seconds{decimal} = T_CS('\lx@arcsecondoverdot'); }
+ my $fseconds = $seconds && six_format_number($seconds);
+ if ($seconds && $fseconds->unlist) {
+ push(@punctuated, ($above && $$seconds{decimal} ? $fseconds
+ : I_apply({}, $mulop, $fseconds, Expand(six_get('angle-symbol-second'))))); }
+ if (scalar(@punctuated) > 1) {
+ @punctuated = (I_apply({}, $addop, @punctuated)); }
+ if ($sign && ($sign->toString ne '+' || six_getBool('retain-explicit-plus'))) { # Finally, prepend the sign
+ @punctuated = I_apply({}, $sign, @punctuated); }
+ AssignValue('SIX_copy-decimal-marker' => $save) if ($above);
+ my $string = join('',
+ ToString($sign),
+ ($degrees ? six_number_string($degrees) . "\x{00B0}" : ''),
+ ($minutes ? six_number_string($minutes) . "\x{2032}" : ''),
+ ($seconds ? six_number_string($seconds) . "\x{2033}" : ''));
+ my $result = six_wrap(I_dual({}, I_symbol({ role => 'NUMBER', meaning => $string }),
+ I_wrap({}, @punctuated)));
+ six_end_processing();
+ return $result; }
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Processing Units
# Unit processing macros
+# Converts {first}rest into (Tokens(first),rest), paying attention to grouping
sub six_peel_group {
my (@tokens) = @_;
if (@tokens && $tokens[0]->getCatcode == CC_BEGIN) {
@@ -888,6 +1868,27 @@ sub six_peel_group {
else {
return (undef, @tokens); } }
+# Converts rest{last} into (Tokens(last), rest), paying attention to grouping
+# note that the order switches, because Tokens returns a scalar
+sub six_peel_tail_group {
+ my (@tokens) = @_;
+ if (@tokens && $tokens[-1]->getCatcode == CC_END) {
+ pop(@tokens);
+ my @result = ();
+ my $level = 1;
+ while (@tokens) {
+ my $t = pop(@tokens);
+ my $cc = $t->getCatcode;
+ if ($cc == CC_BEGIN) {
+ $level--;
+ last unless $level; }
+ elsif ($cc == CC_END) {
+ $level++; }
+ unshift(@result, $t); }
+ return (Tokens(@result), @tokens); }
+ else {
+ return (undef, @tokens); } }
+
# Turn all the internal definitions into real macros
AssignValue(siunitx_macros => {});
@@ -916,6 +1917,9 @@ sub six_convertUnits {
($arg, @tokens) = six_peel_group(@tokens);
my $newdefn = { %{ LookupMapping('siunitx_macros', ToString($name)) } };
if (my $argkey = $$newdefn{arg}) {
+ #my $qmode = six_getChoice('qualifier-mode') if ($argkey eq 'qualifier');
+ #$qmode = 'combine' if $qmode eq 'text';
+ #$$newdefn{qmode} = $qmode;
$$newdefn{$argkey} = $arg; }
push(@defns, $newdefn); }
elsif ($tokens[0]->equals(T_SPACE)) {
@@ -951,7 +1955,7 @@ sub six_parse_units {
push(@save, shift(@defns)); } } # Else save for next unit!
}
if ((!keys %$unit) && @defns) {
- Error('unexpected', $defns[0]{name}, undef, "Don't know what to do with si unit.");
+ Error('unexpected', $defns[0]{name}, undef, "Don't know what to do with si unit");
return (); }
# Error if no unit, unless pure prefix(?)
elsif (!$$unit{unit} && !($$unit{prefix} && !$$unit{qualifier} && !$$unit{power})) {
@@ -969,37 +1973,70 @@ sub six_parse_units {
# Format a single unit
sub six_format_1unit {
my ($unit) = @_;
- my $per = $$unit{per};
- my $pre = $$unit{prefix}{presentation};
+ my $prefix = $$unit{prefix}{presentation};
my $u = $$unit{unit}{presentation};
- my $p = $$unit{prepower}{power} || $$unit{postpower}{power};
+ my $power = $$unit{prepower}{power} || $$unit{postpower}{power};
my $q = $$unit{qualifier}{presentation};
- if ($per) { # NOTE: Probably deal with this more semantically (ie "per" for accessibility)?
- $p = ($p ? Tokens(T_OTHER('-'), $p) : Tokens(T_OTHER('-'), T_OTHER('1'))); }
- if ($q) { # Format the qualifier, if any
- my $qmode = six_getChoice('qualifier-mode');
- if ($qmode eq 'subscript') {
- $q = Tokens(T_SUB(), T_BEGIN, T_CS('\mathrm'), T_BEGIN, $q, T_END, T_END); }
- elsif ($qmode eq 'brackets') {
- $q = Tokens(six_get('open-bracket'), T_CS('\mathrm'), T_BEGIN, $q, T_END, six_get('close-bracket')); }
- elsif (($qmode eq 'phrase') || ($qmode eq 'space')) {
- my $sep = ($qmode eq 'phrase' ? six_get('qualifier-phrase') : T_CS('\;'));
- $u = Tokens(($p ? six_get('open-bracket') : ()),
- ($pre ? $pre : ()),
- $u, $sep, $q,
- ($p ? six_get('close-bracket') : ()));
- $q = $pre = undef; }
- elsif ($qmode eq 'text') {
- $q = Tokens(T_CS('\mathrm'), T_BEGIN, $q, T_END) } }
- # Apparently best to treat $pre & $u as a single symbol? AND probably the qualifier?
+ my $meaning = ($prefix ? $$unit{prefix}{name} : '') . ($u ? $$unit{unit}{name} : '') . ($q ? '-' . $$unit{qualifier}{name} : '');
+ if ($$unit{per}) { # NOTE: Probably deal with this more semantically (ie "per" for accessibility)?
+ $power = Tokens(T_OTHER('-'), $power || T_OTHER('1')); }
+ # my $bracket_qualifier = $power;
+ if ($q) { # Format the qualifier, if any
+ my $qmode = six_get('qualifier-mode')->toString;
+ $qmode =~ s/s$//;
+ $qmode = 'combine' if $qmode eq 'text';
+ #$bracket_qualifier &&= ($qmode eq 'phrase') || ($qmode eq 'space') || ($qmode eq 'combine');
+ $q = Invocation(T_CS('\lx@six@of@' . $qmode),
+ $$unit{qualifier}{arg} ? $$unit{qualifier}{ $$unit{qualifier}{arg} } : $q,
+ $qmode eq 'phrase' ? six_get('qualifier-phrase') : ());
+ if ($power && (($qmode eq 'phrase') || ($qmode eq 'space') || ($qmode eq 'combine'))) {
+ $u = Tokens(six_get('open-bracket'), ($prefix || ()), $u, $q, six_get('close-bracket'));
+ $meaning ||= ($prefix ? $$unit{prefix}{name} : '') . ($u ? $$unit{unit}{name} : '')
+ . ($q ? '-' . $$unit{qualifier}{name} : '');
+ $q = $prefix = undef; } }
+# if ($$unit{qualifier}{arg}) {
+# $bracket_qualifier &&= ($$unit{qualifier}{qmode} eq 'phrase') || ($$unit{qualifier}{qmode} eq 'space') || ($$unit{qualifier}{qmode} eq 'combine');
+# $q = Invocation(T_CS($$unit{qualifier}{implementation}->ToString . '@' . $$unit{qualifier}{qmode}), $$unit{qualifier}{ $$unit{qualifier}{arg} }); }
+# else {
+# # we have to determine qmode before ending the group
+# $bracket_qualifier &&= ($qmode eq 'phrase') || ($qmode eq 'space') || ($qmode eq 'combine');
+# $q = Invocation(T_CS('\lx@six@of@'.$qmode), $q); } }
+# if ( $bracket_qualifier ) {
+# $u = Tokens(six_get('open-bracket'), ($prefix||()), $u, $q, six_get('close-bracket'));
+# $meaning ||= ($prefix ? $$unit{prefix}{name} : '') . ($u ? $$unit{unit}{name} : '')
+# . ($q ? '-' . $$unit{qualifier}{name} : '');
+# $q = $prefix = undef; }
+# my $qmode = six_getChoice('qualifier-mode');
+# if ($qmode eq 'subscript') {
+# $q = Tokens(T_SUB, T_BEGIN, T_CS('\mathrm'), T_BEGIN, $q, T_END, T_END); }
+# elsif ($qmode =~ /^brackets?$/) {
+# $q = Tokens(six_get('open-bracket'), T_CS('\mathrm'), T_BEGIN, $q, T_END, six_get('close-bracket')); }
+# elsif (($qmode eq 'phrase') || ($qmode eq 'space') || ($qmode eq 'text')) {
+# my $sep = $qmode eq 'phrase' ? six_get('qualifier-phrase') : ($qmode eq 'space' ? T_CS('\;') : Tokens());
+# $u = Tokens(($power ? six_get('open-bracket') : ()),
+# ($prefix ? $prefix : ()),
+# $u, $sep, $q,
+# ($power ? six_get('close-bracket') : ()));
+# $q = $prefix = undef; }
+# elsif ($qmode eq 'text') {
+# $q = Tokens(T_CS('\mathrm'), T_BEGIN, $q, T_END) } }
+# $meaning ||= ($prefix ? $$unit{prefix}{name} : '') . ($u ? $$unit{unit}{name} : '')
+# . ($q ? '-' . $$unit{qualifier}{name} : '');
+# Apparently best to treat $prefix & $u as a single symbol? AND probably the qualifier?
my $result = Tokens(
T_CS('\lx@unit'),
- T_OTHER(($pre ? $$unit{prefix}{name} : '') . ($u ? $$unit{unit}{name} : '')
- . ($q ? '-' . $$unit{qualifier}{name} : '')),
- T_BEGIN, Invocation(T_CS('\mathrm'), Tokens(($pre ? $pre : ()), ($u ? $u : ()))),
- ($q ? $q : ()), T_END);
- if ($p) {
- $result = Tokens(T_CS('\lx@power'), T_BEGIN, $result, T_END, T_BEGIN, $p, T_END); }
+ T_BEGIN,
+ T_OTHER($meaning),
+ T_END,
+ T_BEGIN,
+ Invocation(T_CS('\mathrm'), Tokens(($prefix ? $prefix : ()), ($u ? $u : ()))),
+ ($q ? $q : ()),
+ T_END);
+ if ($power) {
+ if ($power->toString !~ /-/ && $power->toString == 0.5 && six_getBool('power-half-as-sqrt')) {
+ $result = Tokens(T_CS('\sqrt'), T_BEGIN, $result, T_END); }
+ else {
+ $result = Tokens(T_CS('\lx@power'), T_BEGIN, $result, T_END, T_BEGIN, $power, T_END); } }
if ($$unit{cancel}) {
$result = Tokens(T_CS('\cancel'), T_BEGIN, $result, T_END); }
if (my $color = $$unit{highlight}{color}) {
@@ -1041,28 +2078,33 @@ sub six_format_unitproduct {
# Format multiple (product of) units
sub six_format_units {
my (@units) = @_;
- # Most complexity here is how to deal with "per", negative powers, and grouping of units
- my $p2 = 0;
- my $p10 = 0;
- # This option MODIFIES the units objects, extracts (removing) all prefixes
- # all prefixes are combined into a single common power of 10 or 2, at the front
- if (!six_getBool('prefixes-as-symbols')) {
+ # per-mode options: reciprocal (nee power), fraction, reciprocal-positive-first,
+ # symbol, symbol-or-fraction, single-symbol
+ my $mathstyle = LookupValue('font')->getMathstyle || 'text';
+ my $mode = $mathstyle eq 'display' ? 'display' : 'inline';
+ my $permode = six_getBool('parse-units') ? six_getChoice($mode . '-per-mode') : 'symbol';
+ my $prefixMode = six_getChoice('prefix-mode');
+ if ($permode eq 'symbol-or-fraction') {
+ $permode = $mathstyle eq 'display' ? 'fraction' : 'symbol'; }
+ my $result = Tokens();
+ my $currPower = 0;
+ if (!six_getBool('prefixes-as-symbols')) { # $prefixMode eq 'extract-exponent' ||
+ my $base;
foreach my $unit (@units) {
- if (my $pre = $$unit{prefix}) {
- my $p = ToString($$unit{prepower}{power} || $$unit{postpower}{power} || 1)
- * ($$unit{per} ? -1 : +1);
- if ($$pre{base} == 2) { $p2 += $p * ToString($$pre{power}); }
- else { $p10 += $p * ToString($$pre{power}); }
- $$unit{prefix} = undef;
- if (($$unit{unit}{name} || '') eq 'gram') { # Special case: keep kilograms!
- $$unit{prefix} = LookupMapping('siunitx_macros', 'kilo');
- $p10 -= 3 * $p; } } } }
- # per-mode = reciprocal, fraction, reciprocal-positive-first, symbol, symbol-or-fraction
- my $permode = six_getChoice('per-mode');
- if ($permode eq 'symbol-or-fraction') { # in display use fraction, otherwise symbol
- $permode = ((LookupValue('font')->getMathstyle || 'text') eq 'display' ? 'fraction' : 'symbol'); }
- my $result = Tokens();
- if ($permode eq 'reciprocal') { # Each unit processed, in order, with its own per (if any)
+ my $unitMult = ToString($$unit{prepower}{power} || $$unit{postpower}{power} || 1) * ($$unit{per} ? -1 : +1);
+ if (my $prefix = $$unit{prefix}) {
+ $currPower += $unitMult * ToString($$prefix{power});
+ if ($base) {
+ if ($$unit{prefix}{base} != $base) {
+ Error('unexpected', undef, undef, "Prefix bases do not match: Expected $base, found $$unit{prefix}{base}"); } }
+ else {
+ $base = $$unit{prefix}{base}; }
+ delete $$unit{prefix}; }
+ if (($$unit{unit}{name} || '') eq 'gram' && six_getBool('extract-mass-in-kilograms')) { # Special case: keep kilograms!
+ $currPower -= 3 * $unitMult;
+ $$unit{prefix} = LookupMapping('siunitx_macros', 'kilo'); } }
+ AssignValue('SIX_exponent-base' => Tokenize($base)) if $currPower; }
+ if ($permode eq 'reciprocal' || $permode eq 'power') { # Each unit processed, in order, with its own per (if any)
$result = six_format_unitproduct(0, @units); }
else { # Otherwise, we've got to collect num & denom, possibly into a fraction
my @numer = ();
@@ -1070,15 +2112,19 @@ sub six_format_units {
foreach my $unit (@units) { # Separate into positive & negative powers.
if ($$unit{per}) { push(@denom, $unit); }
else { push(@numer, $unit); } }
- if ($permode eq 'reciprocal-positive-first') { # re-ordered, otherwise each per as-is.
+ if ($permode eq 'single-symbol' && (scalar(@denom) != 1 || scalar(@numer) == 0)) {
+ $result = six_format_unitproduct(0, @units); } # single-symbol fails. repeat $permode eq 'power'
+ elsif ($permode eq 'reciprocal-positive-first' || $permode eq 'power-positive-first') {
+ # re-ordered, otherwise each per as-is.
$result = six_format_unitproduct(0, @numer, @denom); }
- else { # Otherwise, remove per markers from denom.
- map { $$_{per} = undef; } @denom; # MODIFY the denominator units!
+ else { # Otherwise, remove per markers from denom.
+ map { delete $$_{per}; } @denom; # MODIFY the denominator units!
if ($permode eq 'fraction') {
- $result = Tokens(T_CS('\frac'),
+ $result = Tokens(six_get('fraction-command'),
T_BEGIN, six_format_unitproduct(0, @numer), T_END,
T_BEGIN, six_format_unitproduct(0, @denom), T_END); }
- elsif ($permode eq 'repeated-symbol') {
+ elsif ($permode eq 'repeated-symbol' || $permode eq 'single-symbol') {
+ # if single-symbol, then @denom==1 and @numer>0 from above
my $per = six_get_op({ role => 'MULOP', meaning => 'divide' }, 'per-symbol');
$result = six_format_unitproduct(0, @numer);
foreach my $denom (@denom) { # special symbol prefixes each denom unit
@@ -1088,17 +2134,14 @@ sub six_format_units {
$result = six_format_infix(
six_get_op({ role => 'MULOP', meaning => 'divide' }, 'per-symbol'),
undef, undef,
- six_format_unitproduct(0, @numer),
- six_format_unitproduct($bracket, @denom)); }
+ six_format_unitproduct(0, @numer),
+ (@denom ? six_format_unitproduct($bracket, @denom) : ())); }
else {
Error('unexpected', $permode, undef, "Unknown siunitx per-mode $permode"); } } }
- if ($p2 || $p10) {
- $result = six_format_infix(
- six_get_op({ role => 'MULOP', meaning => 'times' }, 'inter-unit-product'),
- undef, undef,
- ($p2 ? Tokens(T_CS('\lx@power'), T_OTHER('2'), T_OTHER($p2)) : ()),
- ($p10 ? Tokens(T_CS('\lx@power'), T_OTHER('10'), T_OTHER($p10)) : ()),
- $result); }
+ if ($currPower) {
+ my $times = six_get_op({ role => 'MULOP', meaning => 'times' }, 'number-unit-product');
+ my $prefix = { operator => 'exponent', arg2 => { integer => Tokenize($currPower) } };
+ $result = six_format_infix($times, undef, undef, six_format_number($prefix), $result); }
return $result; }
# NOTE: This takes units as-is; is it feasable to guess at semantics?
@@ -1133,6 +2176,8 @@ sub six_process_units {
if (my $defns = six_convertUnits($expr)) {
return six_format_units(six_parse_units($defns)); }
else {
+ if (six_getBool('forbid-literal-units') && six_getBool('parse-units')) {
+ Error('unexpected', undef, undef, 'Literal units disabled'); }
return six_parse_literalunits($expr); } }
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1145,64 +2190,311 @@ DefMacro('\si OptionalKeyVals:SIX {}', sub {
my $funits = six_wrap(six_process_units($units));
six_end_processing();
return $funits; });
+Let(T_CS('\unit'), T_CS('\si'));
# \SI [options]{number}{units}
+# \qty, \complexqty, and \qtyproduct also get Let to this
+# This whole thing could be done better. It originally parsed and formatted the units
+# then parsed and formatted the number. But the units can affect the number,
+# depending on the options (and vice versa). So it would be better to parse both, use the
+# options, and then format both. Or use the aforementioned options to change the
+# number and units, and then pass it off to \num and \si.
DefMacro('\SI OptionalKeyVals:SIX {}{}', sub {
my ($gullet, $kv, $number, $units) = @_;
six_begin_processing($gullet, $kv);
# multi-part-units, product-units !!!! BLECH!!!
- my $fnumber = six_format_number(six_parse_number($gullet, $number));
+ my $parsedNumber = six_parse_number($gullet, $number);
six_enableUnitMacros(1);
- my $times = six_get_op({ role => 'MULOP', meaning => 'times' }, 'number-unit-product');
- my $result = six_wrap(six_format_infix($times, undef, undef,
- $fnumber, I_wrap({}, six_process_units($units))));
+ my $expr = ExpandPartially($units); # Apparently only partially here
+ my ($formattedUnits, $result);
+ if (my $defns = six_convertUnits($expr)) {
+ my @parsedUnits;
+ ($parsedNumber, @parsedUnits) = six_process_SI_units($defns, $parsedNumber);
+ $formattedUnits = six_format_units(@parsedUnits);
+ $result = six_format_SI($parsedNumber, $formattedUnits, $defns); }
+ else {
+ if (six_getBool('forbid-literal-units') && six_getBool('parse-units')) {
+ Error('unexpected', undef, undef, 'Literal units disabled'); }
+ $formattedUnits = six_parse_literalunits($expr);
+ $result = six_format_SI($parsedNumber, $formattedUnits); }
six_end_processing();
return $result; });
+Let(T_CS('\qty'), T_CS('\SI'));
+Let(T_CS('\complexqty'), T_CS('\SI'));
+Let(T_CS('\qtyproduct'), T_CS('\SI'));
+
+sub six_get_num_terms {
+ my ($parsedNumber) = @_;
+ if (ref($parsedNumber) eq 'HASH' && $$parsedNumber{operator} && $$parsedNumber{operator} eq 'product') {
+ return six_get_num_terms($$parsedNumber{arg1}) + six_get_num_terms($$parsedNumber{arg2}); }
+ else {
+ return 1; } }
+
+sub six_process_SI_units {
+ my ($defns, $parsedNumber) = @_;
+ my $numTerms = six_get_num_terms($parsedNumber);
+ my $prefixMode = six_getChoice('prefix-mode');
+ my $currPower = six_get_sci_exp($parsedNumber);
+ my $productUnits = six_getChoice('product-units');
+ $productUnits =~ s/^brackets/bracket/;
+ $numTerms = 1 unless ($productUnits =~ /power$/);
+ my @parsedUnits = six_parse_units($defns);
+
+ if ($numTerms > 1) {
+ foreach my $unit (@parsedUnits) {
+ if ($$unit{prepower}) {
+ my $oldExp = $$unit{prepower}{power}->toString;
+ $$unit{prepower} = { %{ LookupMapping('siunitx_macros', 'raiseto') } };
+ $$unit{prepower}{power} = Tokenize($oldExp * $numTerms); }
+ elsif ($$unit{postpower}) {
+ my $oldExp = $$unit{postpower}{power}->toString;
+ $$unit{postpower} = { %{ LookupMapping('siunitx_macros', 'tothe') } };
+ $$unit{postpower}{power} = Tokenize($oldExp * $numTerms); }
+ else {
+ $$unit{postpower} = { %{ LookupMapping('siunitx_macros', 'tothe') } };
+ $$unit{postpower}{power} = Tokenize($numTerms); } } }
+ if ($prefixMode eq 'combine-exponent' && $currPower) {
+ # the power of 10 (or 2?) (if present) is absorbed into the first unit's prefix, modifying it
+ six_adjust_unit_prefix($parsedUnits[0], $currPower);
+ $parsedNumber = six_get_number_with_sci_exp($parsedNumber, $currPower); }
+ elsif ($prefixMode eq 'extract-exponent' || !six_getBool('prefixes-as-symbols')) {
+ # all prefixes are combined into a single common power of 10 or 2, at the front
+ # This option MODIFIES the units objects, extracts (removing) all prefixes
+ # Most complexity here is how to deal with "per", negative powers, and grouping of units
+ foreach my $unit (@parsedUnits) {
+ my $p = ToString($$unit{prepower}{power} || $$unit{postpower}{power} || 1) * ($$unit{per} ? -1 : +1);
+ if (my $pre = $$unit{prefix}) {
+ $currPower += $p * ToString($$pre{power}); }
+ delete $$unit{prefix};
+ if (($$unit{unit}{name} || '') eq 'gram' && six_getBool('extract-mass-in-kilograms')) { # Special case: keep kilograms!
+ $$unit{prefix} = LookupMapping('siunitx_macros', 'kilo');
+ $currPower -= 3 * $p; } }
+ if ($$parsedNumber{operator} && $$parsedNumber{operator} eq 'exponent') {
+ if ($currPower) {
+ $$parsedNumber{arg2}{sign} = T_OTHER('-') if ($currPower < 0);
+ $$parsedNumber{arg2}{integer} = Tokenize(abs($currPower)); }
+ else {
+ $$parsedNumber = $$parsedNumber{arg1}; } }
+ elsif ($currPower) {
+ my %exponent = (integer => Tokenize(abs($currPower)));
+ $exponent{sign} = T_OTHER('-') if ($currPower < 0);
+ $parsedNumber = { arg1 => $parsedNumber, arg2 => \%exponent, operator => 'exponent' }; } }
+ return ($parsedNumber, @parsedUnits); }
+
+sub six_format_SI {
+ my ($parsedNumber, $processedUnits, $defns) = @_;
+ my $result;
+ my $productUnits = six_getChoice('product-units');
+ my $wrappedUnits = I_wrap({}, $processedUnits);
+ my $times = six_get_op({ role => 'MULOP', meaning => 'times' }, 'number-unit-product');
+ if (ref($parsedNumber) eq 'HASH' && $$parsedNumber{operator}
+ && $$parsedNumber{operator} eq 'uncertain' && six_getChoice('separate-uncertainty-units') eq 'repeat') {
+ my $main = $$parsedNumber{arg1};
+ my $fmain = six_format_number($main);
+ my @mainresult = six_format_infix($times, undef, undef, $fmain, $wrappedUnits);
+ my @uncertainties = @{ $$parsedNumber{arg2} };
+ my @funcertainties = map { six_format_number($_) } @uncertainties;
+ my @uncertainResults = map { six_format_infix($times, undef, undef, $_, $wrappedUnits) } @funcertainties;
+ $result = six_wrap(six_format_infix(T_CS('\pm'), undef, undef, @mainresult, @uncertainResults)); }
+ else {
+ if (!six_getBool('parse-numbers')
+ || ($$parsedNumber{integer} && ToString($$parsedNumber{integer}) ne '1')
+ || (six_getBool('print-unity-mantissa'))
+ || $$parsedNumber{sign} || $$parsedNumber{decimal} || $$parsedNumber{fraction} || $$parsedNumber{operator}) {
+ if (ref($parsedNumber) eq 'HASH' && $$parsedNumber{operator} && $$parsedNumber{operator} eq 'product') {
+ my @terms = ($$parsedNumber{arg1}, $$parsedNumber{arg2});
+ while ($terms[0]{operator} && $terms[0]{operator} eq 'product') {
+ my $arg1 = shift(@terms);
+ unshift(@terms, $$arg1{arg1}, $$arg1{arg2}); }
+ my @fterms;
+ if (six_getBool('exponent-to-prefix') && $defns) {
+ if (six_getBool('product-independent-prefix')) {
+ foreach my $parsedNum (@terms) {
+ my @units = six_parse_units($defns); # do this again
+ if (my $exp = six_get_sci_exp($parsedNum)) {
+ six_adjust_unit_prefix($units[0], $exp);
+ $parsedNum = six_get_number_with_sci_exp($parsedNum, $exp); }
+ my $funits = six_format_units(@units);
+ push(@fterms, six_format_infix($times, undef, undef, six_format_number($parsedNum), $funits)); } }
+ else {
+ my @units = six_parse_units($defns);
+ my $min_exp;
+ foreach my $parsedNum (@terms) {
+ my $next_exp = six_get_sci_exp($parsedNum);
+ if (!defined $min_exp || $next_exp < $min_exp) {
+ $min_exp = $next_exp; } }
+ @terms = map { six_get_number_with_sci_exp($_, $min_exp) } @terms;
+ six_adjust_unit_prefix($units[0], $min_exp);
+ my $funits = six_format_units(@units);
+ my @formatted = map { six_format_number($_); } @terms;
+ @fterms = map { six_format_infix($times, undef, undef, $_, $funits) } @formatted; }
+ $result = six_wrap(six_format_infix(six_get_op({ role => 'MULOP', meaning => 'times' }, 'output-product'),
+ undef, undef, @fterms)); }
+ else {
+ if ($productUnits eq 'repeat') {
+ @fterms = map { six_format_infix($times, undef, undef, six_format_number($_), $wrappedUnits) } @terms; }
+ else {
+ @fterms = map { six_format_number($_) } @terms; }
+ $result = six_wrap(six_format_infix(six_get_op({ role => 'MULOP', meaning => 'times' }, 'output-product'),
+ undef, undef, @fterms));
+ if ($productUnits =~ /^bracket/) {
+ $result = six_wrap(six_get('product-open-bracket', 'open-bracket'), $result,
+ six_get('product-close-bracket', 'close-bracket')); }
+ $result = six_wrap(six_format_infix($times, undef, undef, $result, $wrappedUnits)) unless ($productUnits eq 'repeat'); } }
+ else {
+ my $fnumber = six_format_number($parsedNumber);
+ if (ref($parsedNumber) eq 'HASH' && $$parsedNumber{operator}
+ && $$parsedNumber{operator} eq 'uncertain' && six_getChoice('separate-uncertainty-units') eq 'bracket') {
+ $result = I_wrap({}, T_OTHER('('), $fnumber, T_OTHER(')')); }
+ else {
+ $result = $fnumber; }
+ $result = six_wrap(six_format_infix($times, undef, undef, $result, $wrappedUnits)); } }
+ else {
+ $result = six_wrap($wrappedUnits); } }
+ return $result; }
+
+# get a prefix corresponding to a given power (and base)
+# if more than one prefix has that power, returns one of them arbitrarily
+sub six_get_prefix_from_power {
+ my ($power, $base) = @_;
+ $base ||= 10;
+ my @units = LookupMappingKeys('siunitx_macros');
+ foreach my $unit (@units) {
+ my $prefix = LookupMapping('siunitx_macros', $unit);
+ if ($$prefix{type} eq 'prefix' && $$prefix{base} == $base) {
+ my $prefixPower = ToString($$prefix{power});
+ $prefixPower =~ s/\s+//g;
+ if ($prefixPower eq $power) {
+ return $prefix; } } }
+ Error('power', 'unknown', undef, "Exponent '$power' cannot be converted into a symbolic prefix (base $base)");
+ return; }
+
+# adjust a unit according to a given exponential. modifies the unit in place.
+sub six_adjust_unit_prefix {
+ my ($unit, $adjustment) = @_;
+ my $pre = $$unit{prefix};
+ my $prePostPower = ToString(($$unit{prepower} && $$unit{prepower}{power})
+ || ($$unit{postpower} && $$unit{postpower}{power}) || 1);
+ if ($pre) {
+ $adjustment += $prePostPower * ($$unit{per} ? -1 : 1) * ToString($$pre{power}); }
+ if ($adjustment) {
+ $adjustment /= $prePostPower * ($$unit{per} ? -1 : 1);
+ $$unit{prefix} = six_get_prefix_from_power($adjustment); }
+ else {
+ delete $$unit{prefix}; }
+ return; }
# \SIlist[options]{number;number;...}{units}
DefMacro('\SIlist OptionalKeyVals:SIX {}{}', sub {
my ($gullet, $kv, $numbers, $units) = @_;
six_begin_processing($gullet, $kv);
- my $times = six_get_op({ role => 'MULOP', meaning => 'times' }, 'number-unit-product');
- my $mode = six_getChoice('list-units'); # brackets, repeat, single.
- my @items = six_parse_numbers($gullet, $numbers);
- @items = map { six_format_number($_); } @items; # Format (semantically) each number
+ my $times = six_get_op({ role => 'MULOP', meaning => 'times' }, 'number-unit-product');
+ my $mode = six_getChoice('list-units'); # brackets, repeat, single.
+ my @parsed = six_parse_numbers($gullet, $numbers);
+ my ($result, $funits, @qtys, $defns);
six_enableUnitMacros(1);
- my $funits = six_process_units($units);
- my $result;
+ my $expr = ExpandPartially($units); # Apparently only partially here
- if ($mode eq 'repeat') { # make product of units with each number
- $result = six_format_list(($mode eq 'brackets'),
- map { six_format_infix($times, undef, undef, $_, $funits); } @items); }
+ if ($defns = six_convertUnits($expr)) {
+ $funits = six_format_units(six_parse_units($defns)); }
+ else {
+ if (six_getBool('forbid-literal-units') && six_getBool('parse-units')) {
+ Error('unexpected', undef, undef, 'Literal units disabled'); }
+ $funits = six_parse_literalunits($expr); }
+
+ if ($mode eq 'repeat') { # make product of units with each number
+ if ($defns && six_getBool('exponent-to-prefix')) {
+ if (six_getBool('list-independent-prefix')) {
+ foreach my $parsedNum (@parsed) {
+ my @units = six_parse_units($defns); # do this again
+ if (my $exp = six_get_sci_exp($parsedNum)) {
+ six_adjust_unit_prefix($units[0], $exp);
+ $parsedNum = six_get_number_with_sci_exp($parsedNum, $exp); }
+ $funits = six_format_units(@units);
+ push(@qtys, six_format_infix($times, undef, undef, six_format_number($parsedNum), $funits)); }
+ $result = six_format_list(0, @qtys); }
+ else {
+ my @units = six_parse_units($defns);
+ my $min_exp = undef;
+ foreach my $parsedNum (@parsed) {
+ my $next_exp = six_get_sci_exp($parsedNum);
+ if (!defined $min_exp || $next_exp < $min_exp) {
+ $min_exp = $next_exp; } }
+ @parsed = map { six_get_number_with_sci_exp($_, $min_exp) } @parsed;
+ six_adjust_unit_prefix($units[0], $min_exp);
+ $funits = six_format_units(@units);
+ my @formatted = map { six_format_number($_); } @parsed; # Format (semantically) each number
+ $result = six_format_list(0, map { six_format_infix($times, undef, undef, $_, $funits); } @formatted); } }
+ else {
+ my @formatted = map { six_format_number($_); } @parsed; # Format (semantically) each number
+ $result = six_format_list(0, map { six_format_infix($times, undef, undef, $_, $funits); } @formatted); } }
else {
+ my @formatted = map { six_format_number($_); } @parsed; # Format (semantically) each number
$result = six_format_infix($times, undef, undef,
- six_format_list(($mode eq 'brackets'), @items), $funits); }
+ six_format_list(scalar($mode =~ /^brackets?$/), @formatted), $funits); }
$result = six_wrap($result);
six_end_processing();
return $result; });
+Let(T_CS('\qtylist'), T_CS('\SIlist'));
+
+# if a number has the form {arg1, operator=>'exponent', arg2}, extract, parse, and return the exponent
+sub six_get_sci_exp {
+ my ($parsedNum) = @_;
+ if (ref($parsedNum) eq 'HASH' && $$parsedNum{operator} && $$parsedNum{operator} eq 'exponent') {
+ return six_UnTeX($$parsedNum{arg2}) || 0; }
+ return 0; }
# \SIrange[options]{number}{first}{last}
DefMacro('\SIrange OptionalKeyVals:SIX {}{}{}', sub {
my ($gullet, $kv, $first, $last, $units) = @_;
six_begin_processing($gullet, $kv);
- my $times = six_get_op({ role => 'MULOP', meaning => 'times' }, 'number-unit-product');
- my $mode = six_getChoice('range-units'); # brackets, repeat, single.
- my $fnumber = six_format_number(six_parse_number($gullet, $first));
- my $lnumber = six_format_number(six_parse_number($gullet, $last));
+ my $times = six_get_op({ role => 'MULOP', meaning => 'times' }, 'number-unit-product');
+ my $mode = six_getChoice('range-units'); # brackets, repeat, single.
+ my @parsed = map { six_parse_number($gullet, $_) } ($first, $last);
+ my @formatted = map { six_format_number($_) } @parsed;
six_enableUnitMacros(1);
- my $result;
- my $funits = six_process_units($units);
+ my ($result, $defns, $funits);
+ my $expr = ExpandPartially($units); # Apparently only partially here
- if ($mode eq 'repeat') { # repeat the units on each number
- $result = six_format_range(($mode eq 'brackets'),
- six_format_infix($times, undef, undef, $fnumber, $funits),
- six_format_infix($times, undef, undef, $lnumber, $funits)); }
- else { # put the units after the range
+ if ($defns = six_convertUnits($expr)) {
+ $funits = six_format_units(six_parse_units($defns)); }
+ else {
+ if (six_getBool('forbid-literal-units') && six_getBool('parse-units')) {
+ Error('unexpected', undef, undef, 'Literal units disabled'); }
+ $funits = six_parse_literalunits($expr); }
+
+ if ($mode eq 'repeat') { # repeat the units on each number
+ if (six_getBool('exponent-to-prefix')) {
+ @formatted = ();
+ if (six_getBool('range-independent-prefix')) {
+ foreach my $parsedNum (@parsed) {
+ my $exp = six_get_sci_exp($parsedNum);
+ $parsedNum = six_get_number_with_sci_exp($parsedNum, $exp);
+ my @units = six_parse_units($defns); # do this again
+ six_adjust_unit_prefix($units[0], $exp);
+ push(@formatted, six_format_infix($times, undef, undef, six_format_number($parsedNum), six_format_units(@units))); }
+ $result = six_format_range(0, @formatted); }
+ else {
+ my @exps = map { six_get_sci_exp($_) } @parsed;
+ my $min_exp = $exps[$exps[0] < $exps[1] ? 0 : 1];
+ @parsed = map { six_get_number_with_sci_exp($_, $min_exp) } @parsed;
+ @formatted = map { six_format_number($_) } @parsed;
+ my @units = six_parse_units($defns);
+ six_adjust_unit_prefix($units[0], $min_exp);
+ $funits = six_format_units(@units);
+ my @infixed = map { six_format_infix($times, undef, undef, $_, $funits) } @formatted;
+ $result = six_format_range(0, @infixed); } }
+ else {
+ $result = six_format_range(0,
+ six_format_infix($times, undef, undef, $formatted[0], $funits),
+ six_format_infix($times, undef, undef, $formatted[1], $funits)); } }
+ else { # put the units after the range
$result = six_format_infix($times, undef, undef,
- six_format_range(($mode eq 'brackets'), $fnumber, $lnumber), $funits); }
+ six_format_range(scalar($mode =~ /brackets?/), @formatted), $funits); }
$result = six_wrap($result);
six_end_processing();
return $result; });
+Let(T_CS('\qtyrange'), T_CS('\SIrange'));
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1277,6 +2569,7 @@ DefPrimitive('\DeclareSIPrefix OptionalKeyVals:SIX SkipSpaces DefToken {}{}', su
DefMacroI($newcs, undef,
'\lx@six@unitobject@collapsible{' . $name . '}{' . ToString($presentation) . '}');
return; });
+
# \NewDocumentCommand \DeclareSIPrePower { m m } {
# \__siunitx_declare_power_before:Nn #1 {#2} }
# Prefix operator, applies a power
@@ -1305,11 +2598,19 @@ DefPrimitive('\DeclareSIPostPower OptionalKeyVals:SIX SkipSpaces DefToken {}', s
DefMacroI($newcs, undef, '\lx@six@unitobject{' . $name . '}');
return; });
+DefMacro('\DeclareSIPower DefToken DefToken {}', sub {
+ my ($gullet, $csPre, $csPost, $power) = @_;
+ return Tokens(T_CS('\DeclareSIPrePower'), $csPre, T_BEGIN, $power, T_END,
+ T_CS('\DeclareSIPostPower'), $csPost, T_BEGIN, $power, T_END); });
+
# Special builtins: \tothe{}, \raiseto{}
AssignMapping('siunitx_macros',
tothe => { name => 'tothe', cs => T_CS('\tothe'), implementation => T_CS('\lx@six@tothe'),
keyvals => undef, type => 'postpower', arg => 'power', presentation => T_SUPER });
-DefMacro('\lx@six@tothe{}', '\lx@six@unitobject@arg{tothe}{#1}');
+#DefMacro('\lx@six@tothe{}', '\lx@six@unitobject@arg{tothe}{#1}');
+DefMacro('\lx@six@tothe{}', sub {
+ my ($gullet, $arg) = @_;
+ return Invocation(T_CS('\lx@six@unitobject@arg'), 'tothe', $arg); });
AssignMapping('siunitx_macros',
raiseto => { name => 'raiseto', cs => T_CS('\raiseto'), implementation => T_CS('\lx@six@raiseto'),
@@ -1332,8 +2633,35 @@ DefPrimitive('\DeclareSIQualifier OptionalKeyVals:SIX SkipSpaces DefToken {}', s
# Special builtin: \of{}
AssignMapping('siunitx_macros',
of => { name => 'of', cs => T_CS('\of'), implementation => T_CS('\lx@six@of'),
- keyvals => undef, type => 'qualifier', arg => 'qualifier', presentation => T_CS('\mathrm') });
+ keyvals => undef, type => 'qualifier', arg => 'qualifier', presentation => T_CS('\lx@six@of@aux') });
+# todo delete comments
+# presentation should be T_CS('\lx@six@of@aux')
+# presentation => Tokens()
+# and \lx@six@of should become \lx@six@unitobject@arg{of}. But then it doesn't get the next argument
DefMacro('\lx@six@of{}', '\lx@six@unitobject@arg{of}{#1}');
+#DefMacro('\lx@six@of{}', '\lx@six@of@aux {#1}');
+
+# todo delete this?
+DefMacro('\lx@six@of@aux {}', sub {
+ my ($gullet, $arg) = @_;
+ # this is usually too late - the group for the \si or \SI has closed
+ my $qmode = six_get('qualifier-mode')->toString;
+ $qmode =~ s/s$//;
+ return Invocation(T_CS('\lx@six@of@' . $qmode), $arg); });
+DefMacro('\lx@six@of@subscript{}', sub {
+ my ($gullet, $arg) = @_;
+ return Tokens(T_SUB, T_BEGIN, T_CS('\mathrm'), T_BEGIN, $arg, T_END, T_END);
+ #return Tokens(six_get('text-subscript-command'), T_BEGIN, $arg, T_END);
+ # switch to this once #2781 fixed
+});
+DefMacro('\lx@six@of@bracket{}', sub {
+ my ($gullet, $arg) = @_;
+ Tokens(six_get('open-bracket'), Invocation(T_CS('\mathrm'), $arg), six_get('close-bracket')); });
+DefMacro('\lx@six@of@combine{}', '\mathrm{#1}');
+DefMacro('\lx@six@of@phrase{}{}', sub {
+ my ($gullet, $qual, $phrase) = @_;
+ Tokens($phrase, Invocation(T_CS('\mathrm'), $qual)); });
+DefMacro('\lx@six@of@space{}', '\;\mathrm{#1}');
# \NewDocumentCommand \DeclareSIUnit { O {} m m } {
# \__siunitx_declare_unit:Nnn #2 {#3} {#1} }
@@ -1344,6 +2672,20 @@ DefPrimitive('\DeclareSIUnit OptionalKeyVals:SIX SkipSpaces DefToken {}', sub {
my ($stomach, $kv, $cs, $presentation) = @_;
my $name = $cs->getCSName; $name =~ s/^\\//;
my $newcs = T_CS('\lx@six@' . $name);
+ # siunitx no longer allows prefixes without units. The recommended alternative is
+ # \DeclareSIUnit\noop{\relax}
+ # This becomes the xml
+ #
+ # and then the html
+ #
+ # we can fix this by replacing the $presentation, but we have to know when to do so
+ # \char, \text, and the various \SIUnitSymbol commands are Primitives, but they're ok
+ # explicitly looking for \relax feels hacky, but gets the job done
+ $presentation = Tokenize('') if ($presentation->equals(Tokenize('\relax')));
AssignMapping('siunitx_macros',
$name => { name => $name, cs => $cs, implementation => $newcs,
keyvals => $kv, type => 'unit', presentation => $presentation });
@@ -1375,7 +2717,7 @@ AssignMapping('siunitx_macros',
DefMacro('\lx@six@highlight{}', '\lx@six@unitobject@arg{highlight}{#1}');
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-# Tables
+# Tables - \lx@six@initialize also sets up column types passed to the key table-column-type (default S)
DefColumnType('s Optional', sub {
my ($gullet, $kv) = @_;
$LaTeXML::BUILD_TEMPLATE->addColumn(
@@ -1387,17 +2729,6 @@ DefColumnType('s Optional', sub {
);
return; });
-DefColumnType('S Optional', sub {
- my ($gullet, $kv) = @_;
- $LaTeXML::BUILD_TEMPLATE->addColumn(
- before => Tokens(T_BEGIN,
- T_CS('\lx@si@column@prep'), ($kv ? (T_OTHER('['), $kv, T_OTHER(']')) : ()),
- T_CS('\lx@SI@column@parse')),
- after => Tokens(T_CS('\lx@si@column@end'), T_END),
- # align => 'char:' . ToString(Digest(T_CS('\nprt@decimal'))));
- );
- return; });
-
DefMacro('\lx@si@column@prep OptionalKeyVals:SIX', sub {
my ($gullet, $kv) = @_;
six_begin_processing($gullet, $kv);
@@ -1412,14 +2743,26 @@ DefPrimitive('\lx@si@column@end', '');
# color treated a bit differently?
DefMacro('\lx@SI@column@parse XUntil:\lx@si@column@end', sub {
- my ($gullet, $number) = @_;
- my @tokens = $number->unlist;
+ my ($gullet, $entry) = @_;
+ my @tokens = $entry->unlist;
my $doparse = six_getBool('parse-numbers');
+ if (six_getBool('table-auto-round')) {
+ AssignValue('SIX_round-precision' => six_get('table-figures-decimal'));
+ AssignValue('SIX_round-mode' => Tokenize('places')); }
# Deal with recognizing "surrounding material"
- my @pre = ();
- my @post = ();
- my $color = six_get('color');
- my $result;
+ my ($result, @pre, @post, $save_trimright); # , $pretoks, $posttoks
+ $save_trimright = @tokens && Equals($tokens[-1], T_CS('\lx@column@trimright'));
+ if ($save_trimright) {
+ pop(@tokens);
+ pop(@tokens) while (@tokens && Equals($tokens[-1], T_SPACE)); }
+ my $color = six_get('number-color');
+ # todo upgrade - just delete these 4 lines?
+ #($pretoks, @tokens) = six_peel_group(@tokens);
+ #($posttoks, @tokens) = six_peel_tail_group(@tokens);
+ #push(@pre, T_BEGIN, $pretoks->unlist, T_END) if $pretoks;
+ #unshift(@post, T_BEGIN, $posttoks->unlist, T_END) if $posttoks;
+ # move spaces, commands, and groups from the table entry to @pre or @post
+ # this may fail if \command{arg} doesn't bracket an argument
while (@tokens) {
my $cc = $tokens[0]->getCatcode;
if (($cc == CC_SPACE)
@@ -1434,21 +2777,75 @@ DefMacro('\lx@SI@column@parse XUntil:\lx@si@column@end', sub {
push(@pre, T_BEGIN, $p, T_END); }
else {
last; } }
- # six_parse_begin($gullet, $kv);
- if ($doparse) {
+ while (@tokens) { # move spaces and commands from the table entry to @post
+ my $cc = $tokens[-1]->getCatcode;
+ if (($cc == CC_SPACE)
+ || ($doparse && ($cc == CC_CS)
+ && !six_match1($tokens[-1], 'input-protect-tokens', 'input-symbols'))) {
+ unshift(@post, pop(@tokens)); }
+ elsif ($doparse && $cc == CC_END) {
+ my $p;
+ ($p, @tokens) = six_peel_tail_group(@tokens);
+ unshift(@post, T_BEGIN, $p, T_END); }
+ else {
+ last; } }
+ my ($pad_left, $pad_right) = ('', '');
+ if ($doparse and @tokens) {
my $tokens = [six_apply_mathligatures(@tokens)];
my $parsed = six_match_number($tokens);
- @post = @$tokens; # Save what's left
- $result = six_format_number(six_postprocess($parsed)); }
+ if (@pre || six_get('table-space-text-pre')) {
+ my $prewidth = Digest(six_get('table-space-text-pre') || '')->getWidth->[0];
+ @pre = (T_CS('\makebox'), ExplodeText('[' . $prewidth . 'sp][r]'), T_BEGIN, @pre, T_END); }
+ if (@post || six_get('table-space-text-post')) {
+ my $postwidth = Digest(six_get('table-space-text-post') || '')->getWidth->[0];
+ @post = (T_CS('\makebox'), ExplodeText('[' . $postwidth . 'sp][l]'), T_BEGIN, @post, T_END); }
+ if (@$tokens) {
+ # how does siunitx tell that something isn't a number?
+ #Error('unexpected', undef, undef, 'Unexpected tokens remain in table cell');
+ unshift(@post, @$tokens); }
+# we pass references to pad_left / right so that we can update them and have that pass back up the recursion
+ $result = $parsed
+ ? six_format_number(six_postprocess($parsed), bracket => 0, pad_left_ref => \$pad_left, pad_right_ref => \$pad_right,
+ in_table_number => (six_getChoice('table-alignment-mode') ne 'none'))
+ : (); }
else {
$result = Tokens(@tokens); }
+ if ($pad_left) {
+ my $spacing = six_table_space($pad_left);
+ if (six_getBool('table-align-text-before')) {
+ push(@pre, $spacing); }
+ else {
+ unshift(@pre, $spacing); } }
+ if ($pad_right) {
+ my $spacing = six_table_space($pad_right);
+ if (six_getBool('table-align-text-after')) {
+ unshift(@post, $spacing); }
+ else {
+ push(@post, $spacing); } }
if ($color) {
push(@pre, T_BEGIN, T_CS('\color'), T_BEGIN, $color, T_END);
unshift(@post, T_END); }
+ # get this before closing the group in end_postprocessing
+ # but it's not used, because alignment is set when the column is initially parsed
+ # todo upgrade delete?
+ #my $align = six_getChoice('table-'.(@tokens?'number':'text').'-alignment');
six_end_processing();
- return Tokens(@pre,
- ($result ? six_wrap($result) : ()),
- @post); });
+ push(@post, T_SPACE, T_CS('\lx@column@trimright')) if ($save_trimright);
+ if (@tokens) {
+ $result = Tokens(@pre, six_wrap($result), @post);
+ # todo upgrade delete?
+ # if ( @pre && $pre[0]->getCatcode == CC_BEGIN && ! $color ) {
+ # $result = Tokens(@pre, six_wrap($result, @post)); }
+ # else {
+ # $result = six_wrap(@pre?@pre:(), $result, @post?@post:()); }
+ }
+ else {
+ $result = Tokens(@pre, $result, @post); }
+ return $result;
+ # todo upgrade delete
+ #return @tokens ? six_wrap($result) : $result;
+ #return Tokens(@pre, ( $result && @{$result} ? six_wrap($result) : ()), @post);
+});
# similar to \si
DefMacro('\lx@si@column@parse XUntil:\lx@si@column@end', sub {
@@ -1456,7 +2853,7 @@ DefMacro('\lx@si@column@parse XUntil:\lx@si@column@end', sub {
my @tokens = $units->unlist;
my @pre = ();
my @post = ();
- my $color = six_get('color');
+ my $color = six_get('color', 'unit-color');
my $result;
while (@tokens) {
@@ -1473,6 +2870,8 @@ DefMacro('\lx@si@column@parse XUntil:\lx@si@column@end', sub {
if (my $defns = six_convertUnits(Tokens(@tokens))) {
$result = six_format_units(six_parse_units($defns)); }
else {
+ if (six_getBool('forbid-literal-units') && six_getBool('parse-units')) {
+ Error('unexpected', undef, undef, 'Literal units disabled'); }
## Info('unexpected', 'whatever', undef,
## "Don't yet know how to parse non-macro unit expressions");
$result = Tokens(@tokens); }
@@ -1484,93 +2883,86 @@ DefMacro('\lx@si@column@parse XUntil:\lx@si@column@end', sub {
($result ? six_wrap($result) : ()),
@post); });
-# ?
-#Let('\tablenum', '\lx@table@num');
-Let('\tablenum', '\num');
-
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
RawTeX(<<'EoTeX');
+
\sisetup{
abbreviations,
- binary-units,
+ binary-units ,
- math-rm = \mathrm,
- math-sf = \mathsf,
- math-tt = \mathtt,
- mode = math,
+ math-rm = \mathrm ,
+ math-sf = \mathsf ,
+ math-tt = \mathtt ,
+ mode = math ,
text-rm = \rmfamily,
text-sf = \sffamily,
text-tt = \ttfamily,
input-product = x,
input-quotient = /,
-%(
+ input-open-uncertainty = (,
input-close-uncertainty = ),
input-complex-roots = ij,
input-comparators = {<=>\approx\ge\geq\gg\le\leq\ll\sim},
input-decimal-markers = {.,},
input-digits = 0123456789,
input-exponent-markers = dDeE,
- input-open-uncertainty = (, % )
input-protect-tokens = \approx\dots\ge\geq\gg\le\leq\ll\mp\pi\pm\sim,
input-signs = +-\mp\pm,
input-symbols = \dots\pi,
input-uncertainty-signs = \pm,
- add-decimal-zero = true,
add-integer-zero = true,
retain-unity-mantissa = true,
- round-half = up,
- round-minimum = 0,
- round-precision = 2,
+ round-half = up ,
+ round-minimum = 0 ,
+ round-precision = 2 ,
- bracket-numbers = true , % (
+ bracket-numbers = true ,
+ open-bracket = ( ,
close-bracket = ) ,
complex-root-position = after-number ,
copy-decimal-marker = false ,
exponent-base = 10 ,
exponent-product = \times ,
- group-digits = true ,
+ group-digits = all ,
group-minimum-digits = 5 ,
group-separator = \, ,
- open-bracket = ( , % ) (
+ output-open-uncertainty = ( ,
output-close-uncertainty = ) ,
output-complex-root = \ensuremath { \mathrm { i } } ,
output-decimal-marker = . ,
- output-open-uncertainty = (, % )
fraction-function = \frac ,
output-product = \times ,
output-quotient = / ,
parse-numbers = true ,
- quotient-mode = symbol,
-
+ quotient-mode = symbol ,
forbid-literal-units = false,
- parse-units = true,
+ parse-units = true ,
prefixes-as-symbols = true,
- bracket-unit-denominator = true,
- inter-unit-product = \,,
- literal-superscript-as-power = true,
- per-mode = reciprocal,
- per-symbol = /,
- power-font = number,
- qualifier-mode = subscript,
- qualifier-phrase = { ~ of ~ },
+ bracket-unit-denominator = true ,
+ inter-unit-product = \, ,
+ literal-superscript-as-power = true ,
+ per-mode = power , % same behavior of v2 recipricol
+ per-symbol = / ,
+ power-font = number ,
+ qualifier-mode = subscript ,
multi-part-units = brackets,
number-unit-product = \, ,
- product-units = repeat,
+ product-units = repeat ,
list-final-separator = { and } ,
list-pair-separator = { and } ,
- list-separator = {, } ,
- list-units = repeat,
+ list-separator = {, } ,
+ list-units = repeat ,
range-phrase = { to },
- range-units = repeat,
+ range-units = repeat ,
table-unit-alignment = center,
@@ -1581,10 +2973,7 @@ RawTeX(<<'EoTeX');
table-align-uncertainty = true,
table-omit-exponent = false,
table-parse-only = false,
- table-number-alignment = center-decimal-marker,
table-text-alignment = center,
- table-figures-decimal = 2,
- table-figures-integer = 3,
redefine-symbols = true,
@@ -1599,8 +2988,7 @@ RawTeX(<<'EoTeX');
text-angstrom = \AA,
text-arcminute = \ensuremath { { } ^ { \prime } },
text-arcsecond = \ensuremath { { } ^ { \prime \prime } },
- text-celsius =
- \ensuremath { { } ^ { \circ } } \kern -\scriptspace C ,
+ text-celsius = \ensuremath { { } ^ { \circ } } \kern -\scriptspace C ,
text-degree = \ensuremath { { } ^ { \circ } },
text-micro = \c__siunitx_mu_tl ,
text-ohm = \ensuremath { \c__siunitx_omega_tl },
@@ -1614,26 +3002,23 @@ RawTeX(<<'EoTeX');
% multi-part-units = brackets,
% parse-numbers = true,
% parse-units = true,
-% product-units = repeat,
% otherwise
- bracket-numbers ,
+% bracket-numbers ,
detect-family ,
detect-italic ,
detect-mode ,
detect-shape ,
detect-weight ,
multi-part-units ,
- parse-numbers ,
- parse-units ,
- product-units,
+% parse-numbers ,
+% parse-units ,
number-angle-product=,
number-angle-separator=,
arc-separator=,
}
-\DeclareSIUnit \kilogram { \kilo \gram }
-%%%\DeclareSIUnit \metre { m }
-%%%\DeclareSIUnit \meter { \metre }
+%%%\DeclareSIUnit \metre { m }
+%%%\DeclareSIUnit \meter { \metre }
%%% Swapped to be more NIST consistent.
\DeclareSIUnit \meter { m }
\DeclareSIUnit \metre { \meter }
@@ -1643,6 +3028,8 @@ RawTeX(<<'EoTeX');
\DeclareSIUnit \kelvin { K }
\DeclareSIUnit \candela { cd }
\DeclareSIUnit \gram { g }
+\DeclareSIPrefix \quecto { q } { -30 }
+\DeclareSIPrefix \ronto { r } { -27 }
\DeclareSIPrefix \yocto { y } { -24 }
\DeclareSIPrefix \zepto { z } { -21 }
\DeclareSIPrefix \atto { a } { -18 }
@@ -1664,6 +3051,9 @@ RawTeX(<<'EoTeX');
\DeclareSIPrefix \exa { E } { 18 }
\DeclareSIPrefix \zetta { Z } { 21 }
\DeclareSIPrefix \yotta { Y } { 24 }
+\DeclareSIPrefix \ronna { R } { 27 }
+\DeclareSIPrefix \quetta { Q } { 30 }
+\DeclareSIUnit \kilogram { \kilo \gram }
\DeclareSIUnit \becquerel { Bq }
\DeclareSIUnit \celsius { \SIUnitSymbolCelsius }
\DeclareSIUnit \degreeCelsius { \SIUnitSymbolCelsius }
@@ -1688,46 +3078,31 @@ RawTeX(<<'EoTeX');
\DeclareSIUnit \watt { W }
\DeclareSIUnit \weber { Wb }
\DeclareSIUnit [ number-unit-product = ] \arcmin { \arcminute }
-\DeclareSIUnit [ number-unit-product = ]
- \arcminute { \SIUnitSymbolArcminute }
-\DeclareSIUnit [ number-unit-product = ]
- \arcsecond { \SIUnitSymbolArcsecond }
+\DeclareSIUnit [ number-unit-product = ] \arcminute { \SIUnitSymbolArcminute }
+\DeclareSIUnit [ number-unit-product = ] \arcsecond { \SIUnitSymbolArcsecond }
\DeclareSIUnit \day { d }
\DeclareSIUnit[ number-unit-product = ] \degree { \SIUnitSymbolDegree }
\DeclareSIUnit \hectare { ha }
\DeclareSIUnit \hour { h }
-\DeclareSIUnit \litre { l }
-\DeclareSIUnit \liter { L }
+\DeclareSIUnit \litre { l } % siunitx uses L
+\DeclareSIUnit \liter { L } % siunitx uses \litre
\DeclareSIUnit \minute { min }
\DeclareSIUnit \percent { \char 37 }
\DeclareSIUnit \tonne { t }
-\DeclareSIUnit \astronomicalunit { ua }
+\DeclareSIUnit \astronomicalunit { au }
\DeclareSIUnit \atomicmassunit { u }
\DeclareSIUnit \electronvolt { eV }
\DeclareSIUnit \dalton { Da }
-
-%\group_begin:
-%\cs_set_eq:NN \endgroup \group_end:
-%\char_set_catcode_math_subscript:N \_
-%\use:n
-% {
-% \endgroup
- \DeclareSIUnit \clight { \text { \ensuremath { c _ { 0 } } } }
- \DeclareSIUnit \electronmass
- { \text { \ensuremath { m _ { \textup { e } } } } }
-% }
-\DeclareSIUnit \planckbar { \text { \ensuremath { \hbar } } }
+\DeclareSIUnit \clight { \text { \( c \sb { 0 } \) } }
+%\DeclareSIUnit \clight { \text { \ensuremath { c \char_generate:nn { `\_ } { 8 } { 0 } } } }
+\DeclareSIUnit \electronmass { \text { \( m \sb { \textup { e } } \) } }
+%\DeclareSIUnit \electronmass { \text { \ensuremath { m \char_generate:nn { `\_ } { 8 } { \textup { e } } } } }
+\DeclareSIUnit \planckbar { \text { \ensuremath { \hbar } } }
\DeclareSIUnit \elementarycharge { \text { \ensuremath { e } } }
-%\group_begin:
-%\cs_set_eq:NN \endgroup \group_end:
-%\char_set_catcode_math_subscript:N \_
-%\use:n
-% {
-% \endgroup
- \DeclareSIUnit \bohr { \text { \ensuremath { a _ { 0 } } } }
- \DeclareSIUnit \hartree
- { \text { \ensuremath { E _ { \textup { h } } } } }
-% }
+\DeclareSIUnit \bohr { \text { \( a \sb { 0 } \) } }
+%\DeclareSIUnit \bohr { \text { \ensuremath { a \char_generate:nn { `\_ } { 8 } { 0 } } } }
+\DeclareSIUnit \hartree { \text { \( E \sb { \textup { h } } \) } }
+%\DeclareSIUnit \hartree { \text { \ensuremath { E \char_generate:nn { `\_ } { 8 } { \textup { h } } } } }
\DeclareSIUnit \angstrom { \SIUnitSymbolAngstrom }
\DeclareSIUnit \bar { bar }
\DeclareSIUnit \barn { b }
@@ -1742,8 +3117,126 @@ RawTeX(<<'EoTeX');
\DeclareSIPrePower \cubic { 3 }
\DeclareSIPostPower \cubed { 3 }
+\DeclareSIUnit \nW { \nano \watt }
+\DeclareSIUnit \nF { \nano \farad }
+\DeclareSIUnit \uF { \micro \farad }
+\DeclareSIUnit \mF { \milli \farad }
+\DeclareSIUnit \H { \henry }
+\DeclareSIUnit \fH { \femto \henry }
+\DeclareSIUnit \pH { \pico \henry }
+\DeclareSIUnit \nH { \nano \henry }
+\DeclareSIUnit \uH { \micro \henry }
+\DeclareSIUnit \mH { \milli \henry }
+\DeclareSIUnit \nC { \nano \coulomb }
+\DeclareSIUnit \uC { \micro \coulomb }
+\DeclareSIUnit \mC { \milli \coulomb }
+\DeclareSIUnit \C { \coulomb }
+\DeclareSIUnit \uT { \micro \tesla }
+\DeclareSIUnit \mT { \milli \tesla }
+\DeclareSIUnit \T { \tesla }
+
+\sisetup{
+ % 4.2 printing
+ number-mode = math ,
+ reset-math-version = true ,
+ reset-text-family = true ,
+ reset-text-series = true ,
+ reset-text-shape = true ,
+ text-subscript-command = \textsubscript ,
+ text-superscript-command = \textsuperscript,
+ unit-mode = math ,
+ % 4.3 parsing numbers (input-uncertainty-divider below)
+ expression = #1 ,
+ % 4.4 post-processing numbers (exponent-thresholds below)
+ add-decimal-zero = true ,
+ drop-zero-decimal = false ,
+ exponent-mode = input ,
+ fixed-exponent = 0 ,
+ minimum-integer-digits = 0 ,
+ minimum-decimal-digits = 0 ,
+ round-direction = nearest,
+ round-mode = none ,
+ round-pad = true ,
+ round-zero-positive = true ,
+ uncertainty-round-direction = nearest,
+ % 4.5 printing numbers
+ bracket-ambiguous-numbers = true ,
+ digit-group-size = 3 ,
+ digit-group-first-size = 3 ,
+ digit-group-other-size = 3 ,
+ print-zero-integer = true ,
+ print-unity-mantissa = true ,
+ uncertainty-descriptor-mode = bracket-separator,
+ uncertainty-descriptor-separator = ~ , % regular space because expl3
+ uncertainty-separator =,
+ uncertainty-mode = compact ,
+ zero-symbol = {\mbox{---}} ,
+ % 4.6
+ list-open-bracket = ( ,
+ list-close-bracket = ) ,
+ list-exponents = individual ,
+ product-open-bracket = ( ,
+ product-close-bracket = ) ,
+ product-exponents = individual ,
+ product-mode = symbol ,
+ product-phrase = { \text{by} },
+ product-symbol = \times ,
+ range-open-bracket = ( ,
+ range-close-bracket = ) ,
+ range-exponents = individual ,
+ % 4.7
+ complex-angle-unit = degrees ,
+ complex-mode = input ,
+ complex-phase-command = \angle ,
+ complex-symbol-degree = \SIUnitSymbolDegree, %\degree,
+ input-complex-root = ij ,
+ % 4.8
+ angle-mode = input,
+ angle-symbol-degree = \degree,
+ angle-symbol-minute = \arcminute,
+ angle-symbol-second = \arcsecond,
+ % 4.10
+ fraction-command = \frac,
+ per-symbol-script-correction = \!,
+ unit-font-command = \mathrm,
+ % 4.11
+ extract-mass-in-kilograms = true,
+ prefix-mode = input,
+ quantity-product = \,,
+ separate-uncertainty-units = bracket,
+ % 4.12
+ table-align-text-after = true,
+ table-align-text-before = true,
+ table-alignment = center,
+ table-column-width = 0pt,
+ table-format=2.2,
+ % 4.14: preamble only
+ list-input-separator = ;,
+ product-input-separator = x,
+ table-column-type = S,
+}
+
+% can't seem to get :_12 in expl3
+% : becomes :_11
+% \c_colon_str becomes the \cs_16
+% \char_generate:nn { `: } { 12 } becomes the 11 item token list
+\sisetup{
+ % 4.3 parsing numbers
+ input-uncertainty-divider =:,
+ % 4.4 post-processing numbers
+ exponent-thresholds ={-3:3},
+ % these don't exist in siunitx, but using them simplifies our code
+ angle-input-separator =;,
+ complex-angle-separator =:,
+ exponent-threshold-separator =:,
+}
+%\sisetup{table-format=2.2}
+
EoTeX
+AssignValue('SIX_retain-explicit-decimal-marker' => Tokenize($VERSION_TWO ? 'true' : 'false'));
+AssignValue('SIX_qualifier-phrase' => Tokenize($VERSION_TWO ? 'of' : ''));
+
sub six_load_compat1 {
RawTeX(<<'EoTeX');
\DeclareSIPrePower \Square { 2 }
diff --git a/lib/LaTeXML/Util/Test.pm b/lib/LaTeXML/Util/Test.pm
index 43be8d555..4c8359ab2 100644
--- a/lib/LaTeXML/Util/Test.pm
+++ b/lib/LaTeXML/Util/Test.pm
@@ -31,6 +31,9 @@ sub latexml_tests {
if ($options{texlive_min} && (texlive_version() < $options{texlive_min})) {
plan skip_all => "Requirement minimal texlive $options{texlive_min} not met.";
return done_testing(); }
+ if ($options{texlive_max} && ($options{texlive_max} < texlive_version() )) {
+ plan skip_all => "Requirement maximal texlive $options{texlive_max} not met.";
+ return done_testing(); }
if (!opendir($DIR, $directory)) {
# Can't read directory? Fail (assumed single) test.
return do_fail($directory, "Couldn't read directory $directory:$!"); }
@@ -86,6 +89,7 @@ sub check_requirements {
next unless $reqmts;
my @required_packages = ();
my $texlive_min = 0;
+ my $texlive_max = 0;
my $required_env;
if (!(ref $reqmts)) {
@required_packages = ($reqmts); }
@@ -94,7 +98,8 @@ sub check_requirements {
elsif (ref $reqmts eq 'HASH') {
@required_packages = (ref $$reqmts{packages} eq 'ARRAY' ? @{ $$reqmts{packages} } : $$reqmts{packages});
$required_env = $$reqmts{env};
- $texlive_min = $$reqmts{texlive_min} || 0; }
+ $texlive_min = $$reqmts{texlive_min} || 0;
+ $texlive_max = $$reqmts{texlive_max} || 0; }
foreach my $reqmt (@required_packages) {
if (pathname_kpsewhich($reqmt) || pathname_find($reqmt)) { }
else {
@@ -108,6 +113,11 @@ sub check_requirements {
diag("Skip: $message");
skip($message, $ntests);
return 0; }
+ if ($texlive_max && ($texlive_max < texlive_version() )) {
+ my $message = "Maximal texlive $texlive_max requirement not met for $test";
+ diag("Skip: $message");
+ skip($message, $ntests);
+ return 0; }
elsif ($required_env && !$ENV{$required_env}) {
my $message = "$test is only checked in continuous integration. (use make test CI=true)";
diag("Skip: $message");
diff --git a/t/80_complex.t b/t/80_complex.t
index 901d04a18..a2f84e5a6 100644
--- a/t/80_complex.t
+++ b/t/80_complex.t
@@ -13,4 +13,19 @@ latexml_tests("t/complex",
texlive_min => 2021},
si => {
env=>'CI', # only runs in continuous integration
- packages => 'siunitx.sty', texlive_min => 2015 } });
+ packages => 'siunitx.sty', texlive_min => 2022 }, # should be 2015
+ si_preamble => {
+ env=>'CI', # only runs in continuous integration
+ packages => 'siunitx.sty', texlive_min => 2015 },
+ siV2 => {
+ env=>'CI', # only runs in continuous integration
+ packages => 'siunitx.sty', texlive_min => 2015, texlive_max => 2020 },
+ # siV3 is triggered if \fmtversion >= 2021,
+ # lib/LaTeXML/Engine/latex_base.pool.ltxml sets \fmtversion to 2018/12/01
+ # if `make formats` is called, blib/lib/LaTeXML/Engine/latex_dump.pool.ltxml
+ # overrides it to the actual value
+ # GitHub actions call `make formats` only for 2023 onward
+ # this also means `make test CI=true` will fail without `make formats`
+ siV3 => {
+ env=>'CI', # only runs in continuous integration
+ packages => 'siunitx.sty', texlive_min => 2023 } });
diff --git a/t/complex/si.pdf b/t/complex/si.pdf
index d7fa36ff4..802b48068 100644
Binary files a/t/complex/si.pdf and b/t/complex/si.pdf differ
diff --git a/t/complex/si.tex b/t/complex/si.tex
index f94d9c5cb..2aa1ab8d0 100644
--- a/t/complex/si.tex
+++ b/t/complex/si.tex
@@ -1,10 +1,17 @@
\documentclass{article}
+
+%\usepackage[binary-units]{siunitx}[=v2]
+\usepackage{siunitx}
+
\usepackage{xcolor}
\usepackage{cancel}
-\usepackage{siunitx}
-\usepackage{float}
\usepackage{multirow}
+%\usepackage{sansmath} % not in LaTeXML
+
\usepackage{booktabs}
+\usepackage{etoolbox}
+
+\usepackage{hyperref}
\DeclareSIQualifier\polymer{pol}
\DeclareSIQualifier\catalyst{cat}
@@ -22,44 +29,64 @@
\def\mabbra{a}
\DeclareSIUnit\mabbrb{\mabbra}
-\DeclareSIPrefix\killer{\kilo}{3}
+\DeclareSIPrefix\killer{k}{3}
+% so six_get_prefix_from_power(3) -> \killer, not \kilo
+% but they have the same abbreviation, so it works out
+
+\DeclareSIUnit\noop{\relax}
+
+\DeclareSIPower\quartic\tothefourth{4}
-\usepackage{amsmath}
\begin{document}
+
+{\sloppy
+\tableofcontents
+}
+
+\section{Introduction}
+
+This tries to follow the package documentation released 2025-07-09, with additional tests. Items remaining (generally ordered from hardest to easiest) are
+\begin{itemize}
+\item error: evaluate-expression, expression % 4 occurrences
+\item sansmath: package needed to evaluate two more tests
+\item The spacing in tables is not quite right. See the comment on \&six\_table\_space
+\item font and spacing: several instances where we should make a font change or adjust the spacing, but don't. % 10 of each
+\item detect if loaded with \verb|[=v2]| or \verb|[=2021-04-09]| or earlier % see issue 2719
+\end{itemize}
+
+\section{siunitx for the impatient}
+\num{12345,67890}\\
+\num{.3e45}\\
+\complexnum{1+-2i}\\
+\numproduct{1.654 x 2.34 x 3.430}
+
+\unit{kg.m.s^{-1}} \\
+\unit{\kilogram\metre\per\second} \\
+\unit[per-mode = symbol]{\kilogram\metre\per\second} \\
+\unit[per-mode = symbol]{\kilogram\metre\per\ampere\per\second}
+
+\numlist{10;20;30} \\
+\qtylist{0.13;0.67;0.80}{\milli\metre} \\
+\numrange{10}{20} \\
+\qtyrange{0.13}{0.67}{\milli\metre}
+
+\subsection*{Other tests}
%\ang{1.2e10;2 x 3;4}
+\numlist{10 ; 20 ; 30} \\
\ang{1.2;3;4}
-{
-\color{red}
-Some text \\
-\SI{4}{\metre\per\sievert} \\
-More text \\
-\SI[color = blue]{4}{\metre\per\sievert} \\
-Still red here!
-\numlist[color = blue]{1;2;3;4}\\
-Still red here!\\
-}
Unsemantic:
-\si{m^2.s}\\
-\si{\mu m^2}\\
+\unit{m^2.s}\\
+\unit{\mu m^2}
Semantic again:
-\SI{0.094}{\pi . \milli\meter . \milli\radian}\\
+\qty{0.094}{\pi . \milli\meter . \milli\radian}\\
+\qty{0.094}{\frac{1}{3} . \milli\meter . \milli\radian}\\
+\qty{0.094}{\pi \per \milli\meter . \milli\radian\tothe{3}}
-\SI{0.094}{\frac{1}{3} . \milli\meter . \milli\radian}\\
+\section{Using the siunitx package}
-\SI{0.094}{\pi \per \milli\meter . \milli\radian\tothe{3}}\\
-\section{Numbers}
-\subsection{General}
-%\num{\sqrt{2}}\\
-%\num{{a word}}\\
-\num{12345,67890}\\
-\num{1+-2i}\\
-\num{.3e45}\\
-\num{1.654 x 2.34 x 3.430}\\
-\num{\pi}\\
-\num{2\pi}\\
-\num{\pi/3}\\
+\subsection{Numbers}
\num{123}\\
\num{1234}\\
@@ -68,101 +95,818 @@ \subsection{General}
\num{0,1234}\\
\num{.12345}\\
\num{3.45d-4}\\
-\num{-e10}\\
+\num{-e10}
+
+\numlist{10;30;50;70}
+
+\numproduct{10 x 30}
+
+\numrange{10}{30}
+
+\subsubsection{Other Tests}
\num{123e4}\\
%\num{123e4(3)}\\
-\num{123(3)e4}\\
+\num{123(3)e4}
\num{123\pm2}\\
-\num{123\pm2i}\\
-\num{123+234i}\\
+\complexnum{123\pm2i}\\
+\complexnum{123+234i}\\
+\complexnum{123(1)+234(1)ie3}\\
+\complexnum{234(1)ie3}
%\num{123+234}\\
%\num{123e2+234e3i}\\
-\num{123+234ie3}\\
-\num{123(1)+234(1)ie3}\\
-%\num{123e2(1)+234e3i(1)}\\
-%\num{+234(1)ie3}\\ %comes out weird?
-\num{+3i}\\
-\num{+3ie4}\\
+
+\def\dig{1234}
+\def\odd{\xi}
+\def\odder{\odd}
Pretty nonsensical stuff?
-\num{1.\pi e+3}\\
-\def\dig{1234}\\
+\num[parse-numbers=false]{1.\pi e+3}\\
\num{\dig.\dig}\\
-\def\odd{\xi}\\
-\def\odder{\odd}\\
-\num[input-symbols=\xi]{3\xi}\\
-\num[input-symbols=\xi]{3\odd}\\
-%\num[input-symbols=\odd]{3\odd}\\
-\num[input-symbols=\odd, input-protect-tokens=\odd]{3\odd}\\
-\num[input-symbols=\odd, input-protect-tokens=\odd]{3\odder}\\
-\num[input-symbols=\odder, input-protect-tokens=\odder]{3\odder}\\
-%\num[input-symbols=\odder, input-protect-tokens=\odd]{3\odder}\\
-%\num[input-symbols=\odd, input-protect-tokens=\odder]{3\odder}\\
+\num[input-digits=\xi,parse-numbers=false]{3\xi}\\
+\num[input-digits=\xi,parse-numbers=false]{3\odd}\\
+%\num[input-digits=\odd]{3\odd}\\
+\num[input-digits=\odd, parse-numbers=false]{3\odd}\\
+\num[input-digits=\odd, parse-numbers=false]{3\odder}\\
+\num[input-digits=\odder, parse-numbers=false]{3\odder}
\num{1.23(1)}\\
%\num{1.23(0.01)}
\num{1.23\pm0.01}\\
%\num{1.23(1.0)}\\
%\num{1.23(\pm1)}\\
-\num{1.23(\pi)}\\
+\num[parse-numbers=false]{1.23(\pi)}
+
+\num[parse-numbers=false]{1.234(5) x \pi} \\
+\num[uncertainty-mode = separate, parse-numbers=false]{1.234(5) x \pi}
+
+\subsubsection{Lists and ranges of numbers}
+
+\qtylist{10;30;45}{\metre}\\
+\qtyproduct{10 x 30 x 45}{\metre}\\
+\qtyrange{10}{30}{\metre}
+
+\subsection{Angles}
+
+\ang{10} \\
+\ang{12.3} \\
+\ang{4,5} \\
+\ang{1;2;3} \\
+\ang{;;1} \\
+\ang{+10;;} \\
+\ang{-0;1;}
+
+\subsection{Units}
+
+\subsubsection{unit}
+\unit{kg.m/s^2} \\
+\unit{g_{polymer}~mol_{cat}.s^{-1}} \\
+\unit{\kilo\gram\metre\per\square\second} \\
+\unit{\gram\per\cubic\centi\metre} \\
+\unit{\square\volt\cubic\lumen\per\farad} \\
+\unit{\metre\squared\per\gray\cubic\lux} \\
+\unit{\henry\second}
+
+\subsubsection{qty}
+\qty[mode = text]{1.23}{J.mol^{-1}.K^{-1}} \\
+\qty{.23e7}{\candela} \\
+\qty[per-mode = symbol]{1.99}{\per\kilogram} \\
+\qty[per-mode = fraction]{1,345}{\coulomb\per\mole}
+
+\subsubsection{qtylist}
+\qtylist{10;30;45}{\metre}
+
+\subsubsection{qtyproduct}
+\qtyproduct{10 x 30 x 45}{\metre}
+
+\subsubsection{qtyrange}
+\qtyrange{10}{30}{\metre}
+
+
+\subsection{Complex numbers and quantities}
+
+\complexnum{123+234ie3}\\
+\complexnum{123(1)+234(1)ie3}\\
+%\num{123e2(1)+234e3i(1)}\\
+%\num{+234(1)ie3}\\ %comes out weird?
+\complexnum{+3i}\\
+\complexnum{+3ie4}
+
+\subsection{The unit macros}
+\newcommand{\showunit}[1]{#1 & \texttt{\Backslash#1} & \unit{\csname#1\endcsname}}
+
+The SI base units:
+\begin{center}
+\begin{tabular}{lll}\toprule
+Unit & Macro & Symbol \\\midrule
+\showunit{ampere}\\
+\showunit{candela}\\
+\showunit{kelvin}\\
+\showunit{kilogram}\\
+\showunit{metre}\\
+\showunit{mole}\\
+\showunit{second}\\\bottomrule
+\end{tabular}
+\end{center}
+
+The coherent derived units:
+\begin{center}
+\begin{tabular}{@{}llc llc@{}}\toprule
+Unit & Macro & Symb &
+Unit & Macro & Symb \\\cmidrule(r){1-3}\cmidrule(l){4-6}
+\showunit{becquerel} & \showunit{newton} \\
+\showunit{degreeCelsius} & \showunit{ohm} \\
+\showunit{coulomb} & \showunit{pascal} \\
+\showunit{farad} & \showunit{radian} \\
+\showunit{gray} & \showunit{siemens} \\
+\showunit{hertz} & \showunit{sievert} \\
+\showunit{henry} & \showunit{steradian} \\
+\showunit{joule} & \showunit{tesla} \\
+\showunit{katal} & \showunit{volt} \\
+\showunit{lumen} & \showunit{watt} \\
+\showunit{lux} & \showunit{weber} \\\bottomrule
+\end{tabular}
+\end{center}
+
+The non-SI units:
+\begin{center}
+\begin{tabular}{lll}\toprule
+Unit & Macro & Symbol \\\midrule
+\showunit{astronomicalunit}\\
+\showunit{bel}\\
+\showunit{dalton}\\
+\showunit{day}\\
+\showunit{decibel}\\
+\showunit{degree}\\
+\showunit{electronvolt}\\
+\showunit{hectare}\\
+\showunit{hour} \\
+\showunit{litre}\\
+\showunit{liter}\\
+\showunit{arcminute}\\
+\showunit{minute}\\
+\showunit{arcsecond}\\
+\showunit{neper}\\
+\showunit{tonne}\\\bottomrule
+\end{tabular}
+\end{center}
+
+%\ExplSyntaxOn
+% this accesses siunitx internals, and may therefore break
+\newcommand{\showprefix}[1]{
+ #1 & \texttt{\Backslash#1} & \unit{ \csname #1\endcsname \noop } &
+ \qty[prefix-mode=extract-exponent, print-unity-mantissa=false]{1}{\csname#1\endcsname\noop}%
+% the following
+% \str_if_eq:nnTF { #1 } { micro } { -6 } { \__ltxml_power_from_text:n { #1 } }
+% works in LaTeX, but not LaTeXML (b/c it doesn't have the internals)
+}
+%\cs_new:Npn \__ltxml_prefix_from_text:n #1 {
+% \use:c { __siunitx_unit_ \token_to_str:c { #1 } :w }
+%}
+%\cs_new:Npn \__ltxml_power_from_prefix:n #1 {
+% \prop_get:NeNTF
+% \l__siunitx_unit_prefixes_forward_prop
+% { #1 }
+% \l_tmpa_tl
+% { \l_tmpa_tl }
+% { \qty [ prefix-mode=extract-exponent, print-unity-mantissa=false ] { 1 } { \use:c { #1 } \noop } }
+%}
+%\cs_new:Npn \__ltxml_power_from_text:n #1 {
+% {
+% \bool_set_false:N \l__siunitx_unit_test_bool
+% \bool_set_false:N \l__siunitx_unit_parsing_bool
+% \exp_args:Ne \__ltxml_power_from_prefix:n { \__ltxml_prefix_from_text:n { #1 } }
+% }
+%}
+%\__ltxml_prefix_from_text:n { kibi }
+%\ExplSyntaxOff
+
+SI prefixes:
+\begin{center}
+\begin{tabular}{@{}llcl llcl@{}}\toprule
+Prefix & Cmd & Symb & Power &
+Prefix & Cmd & Symb & Power \\\cmidrule(r){1-4}\cmidrule(l){5-8}
+\showprefix{quecto} & \showprefix{deca}\\
+\showprefix{ronto} & \showprefix{hecto}\\
+\showprefix{yocto} & \showprefix{kilo}\\
+\showprefix{zepto} & \showprefix{mega}\\
+\showprefix{atto} & \showprefix{giga}\\
+\showprefix{femto} & \showprefix{tera}\\
+\showprefix{pico} & \showprefix{peta}\\
+\showprefix{nano} & \showprefix{exa}\\
+\showprefix{micro} & \showprefix{zetta}\\
+\showprefix{milli} & \showprefix{yotta}\\
+\showprefix{centi} & \showprefix{ronna}\\
+\showprefix{deci} & \showprefix{quetta}\\\bottomrule
+\end{tabular}
+\end{center}
+
+\unit{\square\becquerel} \\
+\unit{\joule\squared\per\lumen} \\
+\unit{\cubic\lux\volt\tesla\cubed} \\
+\unit{\henry\tothe{5}} \\
+\unit{\raiseto{4.5}\radian} \\
+\unit{\joule\per\mole\per\kelvin} \\
+\unit{\joule\per\mole\kelvin} \\
+\unit{\per\henry\tothe{5}} \\
+\unit{\per\square\becquerel} \\
+\unit{\kilogram\of{metal}} \\
+\qty[qualifier-mode = bracket]{0.1}{\milli\mole\of{cat}\per\kilogram\of{prod}} \\
+\unit[per-mode = fraction]{\cancel\kilogram\metre\per\cancel\kilogram\per\second} \\
+\unit{\highlight{red}\kilogram\metre\per\second} \\
+\unit[unit-color = purple]{\highlight{blue}\kilogram\metre\per\second} % todo font
+
+\subsection{Unit abbreviations}
+
+Abbreviated units
+
+\begin{center}
+\newcommand{\showunitabbrev}[4]{#1#3 & \texttt{\Backslash#2#4} & \unit{\csname#2#4\endcsname}}
+\ExplSyntaxOn
+\newcommand{\showunitabbrevs}[3]{
+% \midrule
+ \clist_map_variable:nNn { #3 } { \l_tmpa_tl } {
+ \exp_after:wN \showunitabbrev \l_tmpa_tl { #1 } { #2 } \\
+ }
+}
+\ExplSyntaxOff
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{gram}{g}{{femto}{f},{pico}{p},{nano}{n},{micro}{u},{milli}{m},{}{},{kilo}{k}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{metre}{m}{{pico}{p},{nano}{n},{micro}{u},{milli}{m},{centi}{c},{deci}{d},{}{},{kilo}{k}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{meter}{m}{{pico}{p},{nano}{n},{micro}{u},{milli}{m},{centi}{c},{deci}{d},{}{},{kilo}{k}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{second}{s}{{atto}{a},{femto}{f},{pico}{p},{nano}{n},{micro}{u},{milli}{m},{}{}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{mole}{mol}{{femto}{f},{pico}{p},{nano}{n},{micro}{u},{milli}{m},{}{},{kilo}{k}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{ampere}{A}{{pico}{p},{nano}{n},{micro}{u},{milli}{m},{}{},{kilo}{k}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{litre}{l}{{micro}{u},{milli}{m},{}{},{hecto}{h}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{liter}{L}{{micro}{u},{milli}{m},{}{},{hecto}{h}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{hertz}{Hz}{{milli}{m},{}{},{kilo}{k},{mega}{M},{giga}{G},{tera}{T}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{newton}{N}{{milli}{m},{}{},{kilo}{k},{mega}{M}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{pascal}{Pa}{{}{},{kilo}{k},{mega}{M},{giga}{G}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{ohm}{ohm}{{milli}{m},{kilo}{k},{mega}{M}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{volt}{V}{{pico}{p},{nano}{n},{micro}{u},{milli}{m},{}{},{kilo}{k}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{watt}{W}{{nano}{n},{micro}{u},{milli}{m},{}{},{kilo}{k},{mega}{M},{giga}{G}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{joule}{J}{{micro}{u},{milli}{m},{}{},{kilo}{k}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{electronvolt}{eV}{{milli}{m},{}{},{kilo}{k},{mega}{M},{giga}{G},{tera}{T}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{watt hour}{Wh}{{kilo}{k}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{farad}{F}{{femto}{f},{pico}{p},{nano}{n},{micro}{u},{milli}{m},{}{}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{henry}{H}{{femto}{f},{pico}{p},{nano}{n},{micro}{u},{milli}{m},{}{}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{coulomb}{C}{{nano}{n},{micro}{u},{milli}{m},{}{}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{tesla}{T}{{micro}{u},{milli}{m},{}{}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{kelvin}{K}{{}{}}
+\bottomrule
+\end{tabular}
+
+\begin{tabular}{lcc}\toprule
+Unit & Abbreviation & Symbol \\\midrule
+\showunitabbrevs{bel}{B}{{deci}{d}}
+\bottomrule
+\end{tabular}
+\end{center}
+
+Binary prefixes:
+\newcounter{tablerow}
+\begin{center}
+% the documentation numbers are hardcoded in
+% /usr/local/texlive/YYYY/texmf-dist/source/latex/siunitx/siunitx.tex
+% the powers are not stored in siunitx. see siunitx-binary.dtx
+\newcommand{\showBinaryPrefix}[1]{%
+ \stepcounter{tablerow}%
+ #1 & \texttt{\Backslash#1} & \unit{\csname #1\endcsname\noop} & \arabic{tablerow}0
+}
+\begin{tabular}{cccc}\toprule
+Prefix & Command & Symbol & Power \\\midrule
+\showBinaryPrefix{kibi} \\
+\showBinaryPrefix{mebi} \\
+\showBinaryPrefix{gibi} \\
+\showBinaryPrefix{tebi} \\
+\showBinaryPrefix{pebi} \\
+\showBinaryPrefix{exbi} \\
+\showBinaryPrefix{zebi} \\
+\showBinaryPrefix{yobi} \\\bottomrule
+\end{tabular}
+\end{center}
+
+\subsection{Creating new macros}
+
+\qty{67890}{\degree} \\
+\qty[quantity-product = \,]{67890}{\degree}
+
+\unit{\kilogram\tothefourth}\\
+\unit{\quartic\metre}
+
+\qty{1.234}{\gram\polymer\per\mole\catalyst\per\hour}
+
+\subsubsection{Other Tests}
+% Note that \kilogram is defined as \kilo\gram, and acts sorta like a macro
+\unit{\kilogram}
+\unit{\kilo\gram}
+% Not allowed.
+%\unit{\kilo\kilogram}
+%\unit{\kilo\kilo\gram}
+{
+% and note that regular macros ARE expanded!
+\def\killermeater{\kilo\meter}
+\unit{\killermeater}
+}
+{
+% BUT, it prefers its OWN definitions!
+\def\gram{Grr}
+\unit{\kilo\gram}
+}
+
+% However, it's happy with various indirect definitions.
+% Ie. units, prefixes (at least) act like regular macros within \si.
+\unit{\abbra}\\
+\unit{\abbrb}\\
+\unit{\abbrc}\\
+\unit{\abbrd}\\
+\unit{\abbre}
+
+\unit{\mabbra}\\
+\unit{\mabbrb}
+
+\unit{\killer\meter}
+
+% Where can highlight go?
+\unit{\highlight{red}\kilo\gram\metre\per\second} \\
+\unit{\kilo\highlight{red}\gram\metre\per\second} \\
+\unit{\kilo\gram\highlight{red}\metre\per\second} \\
+\unit{\kilo\gram\metre\highlight{red}\per\second} \\
+\unit{\kilo\gram\metre\per\highlight{red}\second}
+
+\unit{\cancel\kilo\gram\metre\per\second} \\
+\unit{\kilo\cancel\gram\metre\per\second} \\
+\unit{\kilo\gram\cancel\metre\per\second} \\
+\unit{\kilo\gram\metre\cancel\per\second} \\
+\unit{\kilo\gram\metre\per\cancel\second}
+
+
+\subsection{Tabular material}
+
+Standard behaviour of the \texttt{S} column type
+\begin{center}
+\begin{tabular}{S}
+\toprule
+{Some Values} \\
+\midrule
+2.3456 \\
+34.2345 \\
+-6.7835 \\
+90.473 \\
+5642.5 \\
+1.2e3 \\
+e4 \\
+\bottomrule
+\end{tabular}
+\end{center}
+
+Detection of surrounding material in an \texttt{S} column
+\begin{center}
+\begin{tabular}{S[color=orange]}
+\toprule
+{Some Values} \\
+\midrule
+12.34 \\
+\color{purple} 975,31 \\
+44.268 \textsuperscript{\emph{a}} \\
+\bottomrule
+\end{tabular}
+\end{center}
+
+Text before and after numbers
+\begin{center}
+\sisetup{table-format = {now }2.4{\textsuperscript{\emph{a}}}}
+\begin{tabular}{@{}S@{}}
+\toprule
+{Values} \\
+\midrule
+2.3456 \\
+34.2345 \textsuperscript{\emph{a}}\\
+34.2345 {\textsuperscript{\emph{a}}}\\
+56.7835 \\
+%now~ 90.473 \\ % todo error
+{now~} 90.473 \\ % todo incorrect
+\bottomrule
+\end{tabular}
+\end{center}
+
+Controlling complex alignment with the tablenum macro
+\begin{center}
+\begin{tabular}{lr}
+\toprule
+Heading & Heading \\
+\midrule
+Info & More info \\
+Info & More info \\
+\multicolumn{2}{c}{\tablenum[table-format = 4.4]{12,34}} \\
+\multicolumn{2}{c}{\tablenum[table-format = 4.4]{333.5567}} \\
+\multicolumn{2}{c}{\tablenum[table-format = 4.4]{4563.21}} \\
+\bottomrule
+\end{tabular}
+\qquad
+\begin{tabular}{lr}
+\toprule
+Heading & Heading \\
+\midrule
+\multirow{2}*{\tablenum{88,999}} & aaa \\
+& bbb \\
+\multirow{2}*{\tablenum{33,435}} & ccc \\
+& ddd \\
+\bottomrule
+\end{tabular}
+\end{center}
+
+\section{Package control options}
+
+\stepcounter{subsection}
+
+\subsection{Printing}
+\subsubsection{mode, number-mode, unit-mode}
+\subsubsection{reset-text-[family\textbar series\textbar shape]} % todo font
+{
+ \sisetup{mode = text}
+ {\itshape \num{1234}}\\
+ {\bfseries \num{1234}}\\
+ {\sffamily \num{1234}}\\
+ \sisetup{
+ reset-text-family = false ,
+ reset-text-series = false ,
+ reset-text-shape = false
+ }
+ {\itshape \num{1234}}\\
+ {\bfseries \num{1234}}\\
+ {\sffamily \num{1234}}
+}
+
+\subsubsection{propagate-math-font, reset-math-version}
+{
+ {\boldmath \unit{\kilogram}}\\ % todo font
+ %{\sansmath $\unit{\kilogram}$}\\ % todo usepackage sansmath
+ {$\mathsf{\unit{\kilogram}}$}\\
+ \sisetup{
+ propagate-math-font = true ,
+ reset-math-version = false
+ }
+ {\boldmath \unit{\kilogram}}\\
+ %{\sansmath $\unit{\kilogram}$}\\ % todo usepackage sansmath
+ {$\mathsf{\unit{\kilogram}}$}
+}
+
+\subsubsection{text-[family\textbar series]-to-math} % todo font
+{
+ {\sffamily \unit{\kilogram}}\\
+ {\bfseries $\unit{\kilogram}$}\\
+ \sisetup{
+ text-family-to-math = true ,
+ text-series-to-math = true
+ }
+ {\sffamily \unit{\kilogram}}\\
+ {\bfseries $\unit{\kilogram}$}
+}
+
+\subsubsection{text-font-command}
+{
+ \sisetup{number-mode = text}
+ \qty{123456789}{\kilo\volt\per\centi\metre} \\
+ \sisetup{text-font-command = \fontfamily{pplx}\selectfont}
+ \qty{123456789}{\kilo\volt\per\centi\metre} % todo font
+}
+
+\subsubsection{text-[sub\textbar super]script-command}
+{
+ \sisetup{unit-mode = text}
+ \unit{\kg\of{polymer}} \\
+ \newcommand*\mysubscript[1]{\textsubscript{\textcolor{blue}{#1}}}
+ \newcommand\mysuperscript[1]{\textsuperscript{\textcolor{green}{#1}}}
+ \sisetup{text-subscript-command = \mysubscript}
+ \unit{\kg\of{polymer}} % todo font
+}
+
+\subsubsection{color, number-color, unit-color} % todo font
+{
+ \color{red}%
+ Some text \\
+ \qty{4}{\kilogram} \\
+ More text \\
+ \qty[color = blue]{4}{\kilogram} \\
+ \qty[number-color = blue]{4}{\kilogram} \\
+ \qty[unit-color = blue]{4}{\kilogram} \\
+ Still red here!
+}
+
+\qty[number-color=blue,color=green]{1}{\gram} \\
+\qty[color=green,number-color=blue]{1}{\gram} \\
+\qty[unit-color=blue,color=green]{1}{\gram} \\
+\qty[color=green,unit-color=blue]{1}{\gram} \\
+\ang[number-color=green,unit-color=blue]{-1;2;3} \\
+\numlist{1;2;3}\\
+\numproduct[product-mode=phrase]{1x2}\\
+\numrange{1}{2}
+
+\numproduct{1.234(5) x 6.78(9)}
\subsection{Parsing numbers}
-\subsubsection{input-digits, input-decimal-markers, input-signs, input-exponent-markers}
+%\subsubsection{input-digits, input-decimal-markers, input-signs, input-exponent-markers}
+\subsubsection{input-digits, input-ignore}
+\num[input-ignore=3a\pi]{1a234\pi5}
-\subsubsection{input-symbols, input-ignore}
\subsubsection{input-comparators}
\num{< 10} \\
-%\SI{>> 5}{\metre} \\
-\num{\le 0.12}\\
+\qty{>> 5}{\metre} \\
+\num{\le 0.12}
-\subsubsection{input-open-uncertainty, input-close-uncertainty, input-uncertainty-signs}
+\subsubsection{input-[open\textbar close]-uncertainty, input-uncertainty-signs}
\num{9.99(9)}\\
\num{9.99 +- 0.09}\\
\num{9.99 \pm 0.09}\\
\num{123 +- 4.5}\\
\num{12.3 +- 6}\\
+\num{123.4(12)} \\
+\num{123.4(1.2)}\\
+\num{123.4(12)(45)} \\
+\num{123.4 \pm 1.2 \pm 4.5}
-\subsubsection{input-complex-roots}
-\num{9.99 + 88.8i} \\
-\num{9.99 + i88.8}\\
+\subsubsection{input-uncertainty-divider}
+\num{10.56(12:34)} \\
+\num{123.4(4.6:7.8)}\\
+\num{10.56(1:2)(3)}\\
+\num{6.45(2)(3:4)}
+
+Also:\\
+\num{5.6(1.2:3.4)} \\
+\num{78.56(12:34)} \\
+\num{123.4(12)(45)(67)} \\
+\num{123.4 \pm 1.2 \pm 4.5 \pm 6.7}
-\subsubsection{input-protect-tokens}
\subsubsection{parse-numbers}
\num[parse-numbers = false]{\sqrt{2}}\\
+\qty[parse-numbers = false]{\sqrt{3}}{\metre}
+
+\subsubsection*{Other tests}
+\num[parse-numbers=false]{\pi}\\
+\num[parse-numbers=false]{2\pi}\\
+\num[parse-numbers=false]{\pi/3}
+
+% todo error
+%\subsubsection{evaluate-expression, expression}
+%{
+%\sisetup{evaluate-expression}%
+%\qty{2 + 4 * 3}{\joule} \\
+%\qty[expression = 10 * (#1)]{2 + 4 * 3}{\joule}
+%}
+
+\subsubsection{retain-explicit-decimal-marker, retain-explicit-plus, retain-negative-zero, retain-zero-uncertainty}
+%\num[retain-explicit-decimal-marker]{10.} \\ % v2 or v3
+\num{+345} \\
+\num[retain-explicit-plus]{+345} \\
+\num{-0} \\
+\num[retain-negative-zero]{-0} \\
+\num{12.3(0)} \\
+\num[retain-zero-uncertainty]{12.3(0)}
+
+{
+\num{456} \\
+\num{.789} \\
+\sisetup{
+ add-decimal-zero = false,
+ add-integer-zero = false,
+}%
+\num{456} \\
+\num{.789} % v2: .789; v3: 0.789. But add-integer-zero is v2
+}
\subsection{Post-processing numbers}
-\subsubsection{round-mode, round-precision}
+
+\subsubsection{exponent-mode, fixed-exponent}
+{
+\num{0.001} \\
+\num{0.0100} \\
+\num{1200} \\
+\sisetup{exponent-mode = scientific}%
+\num{0.001} \\
+\num{0.0100} \\
+\num{1200} \\
+\sisetup{exponent-mode = engineering}%
+\num{0.001} \\
+\num{0.0100} \\
+\num{1200} \\
+\sisetup{
+exponent-mode = fixed,
+fixed-exponent = 2,
+}%
+\num{0.001} \\
+\num{0.0100} \\
+\num{1200} \\
+}
+\num{1.23e4} \\
+\num[exponent-mode = fixed, fixed-exponent = 0]{1.23e4}
+
+\complexnum[exponent-mode=engineering]{0.5+1000i}
+% todo tex has (0.000 500+1.000i)x10^3 (see siunitx#813)
+
+\subsubsection{exponent-thresholds}
+This is unique to version 3.
+
+\subsubsection{drop-exponent, drop-uncertainty}
+\num{0.01(2)} \\
+\num[drop-uncertainty]{0.01(2)} \\
+\num{0.01e3} \\
+\num[drop-exponent]{0.01e3} % causes warning: Potentially ambiguous dropping of exponent
+
+\subsubsection{round-mode, round-precision, round-pad}
+
{
\num{1.23456} \\
\num{14.23} \\
\num{0.12345(9)} \\
+\num{0.0012345}\\
\sisetup{
round-mode = places,
round-precision = 3
}%
+places:\\
\num{1.23456} \\
\num{14.23} \\
\num{0.12345(9)} \\
+\num{0.0012345}\\
\sisetup{
round-mode = figures,
round-precision = 3
}%
+figures:\\
\num{1.23456} \\
\num{14.23} \\
-\num{0.12345(9)}\\
+\num{0.12345(9)} \\
+\num{9999}\\
+\num{0.0012345}\\
+\sisetup{
+round-mode = uncertainty,
+round-precision = 1
+}%
+uncertainty:\\
+\num{0.12345(9)} \\
+\num{0.12345(23)} \\
+\num{0.12345(234)}\\
+\num{12345(123)}\\
+\num{123.456(012)}\\
+\num{1234.5(12.3)}\\
+\num{1234.56(98)}\\
+\num{9.99(99)}\\
+\num{99.99(999)}\\
+}%
+{
+\sisetup{round-mode = figures, round-precision = 4}%
+figures:\\
+\num{12.3} \\
+\num{012.34} \\
+\num[round-pad = false]{12.3}\\
+}
+Also\\
+\numproduct[round-mode=places]{.123 x .456 x .789}
+
+\subsubsection{round-direction, round-half}
+{
+\sisetup{round-mode = places}
+\num{0.054} \\
+\num{0.046} \\
+\sisetup{round-direction = down}%
+\num{0.054} \\
+\num{0.046} \\
+\sisetup{round-direction = up}%
+\num{0.054} \\
+\num{0.046} \\
+}
+{
+\sisetup{
+round-mode = figures,
+round-precision = 1,
+round-half = up
+}%
+\num{0.055} \\
+\num{0.045} \\
+\sisetup{round-half = even}%
+\num{0.055} \\
+\num{0.045}
}
-\subsubsection{round-integer-to-decimal}
+
+\subsubsection{uncertainty-round-direction}
{
-\num[round-mode = figures]{1} \\
-\num[round-mode = places]{1} \\
-\sisetup{round-integer-to-decimal}
-\num[round-mode = figures]{1} \\
-\num[round-mode = places]{1}\\
+\sisetup{round-mode = uncertainty}
+\num{0.123(41)} \\
+\sisetup{uncertainty-round-direction = up}%
+\num{0.123(41)} \\ % lets try again
+\sisetup{round-precision=1}%
+\num{0.123(41)}
}
\subsubsection{round-minimum}
@@ -170,251 +914,535 @@ \subsubsection{round-minimum}
\sisetup{round-mode = places}%
\num{0.0055} \\
\num{0.0045} \\
+\num{-0.0045} \\
\sisetup{round-minimum = 0.01}%
\num{0.0055} \\
-\num{0.0045}\\
+\num{0.0045} \\
+\num{-0.0045}\\
+\sisetup{round-minimum = {0,01}}%
+\num{0,0045}
}
-\subsubsection{round-half}
+\subsubsection{round-zero-positive}
{
-\sisetup{round-mode = places, round-half = up}%
-\num{0.055} \\
-\num{0.045} \\
-\sisetup{round-half = even}%
-\num{0.055} \\
-\num{0.045}
+\sisetup{round-mode = places}%
+\num{-0.001} \\
+\sisetup{round-zero-positive = false}%
+\num{-0.001}
}
-\subsubsection{add-decimal-zero, add-integer-zero}
+\subsubsection{drop-zero-decimal}
+\num{2.0} \\
+\num{2.1} \\
{
-\num{123.} \\
-\num{456} \\
-\num{.789} \\
-\sisetup{
- add-decimal-zero = false,
- add-integer-zero = false,
-}%
-\num{123.} \\
-\num{456} \\
-\num{.789}\\
+\sisetup{drop-zero-decimal}%
+\num{2.0} \\
+\num{2.1}
}
-\subsubsection{minimum-integer-digits}
+\subsubsection{minimum-decimal-digits, minimum-integer-digits}
\num{123} \\
-\num[minimum-integer-digits = 1]{123} \\
\num[minimum-integer-digits = 2]{123} \\
-\num[minimum-integer-digits = 3]{123} \\
-\num[minimum-integer-digits = 4]{123}\\
-
-\subsubsection{explicit-sign, retain-explicit-plus}
-\num{+345} \\
-\num[retain-explicit-plus]{+345} \\
-\num[explicit-sign = -]{345}\\
-\num[explicit-sign = -]{+345}\\
-
-\subsubsection{retain-unity-mantissa, retain-zero-exponent}
-\num{1e4} \\
-\num[retain-unity-mantissa = false]{1e4} \\
-\num{444e0} \\
-\num[retain-zero-exponent = true]{444e0}\\
+\num[minimum-integer-digits = 4]{123} \\
+\num{0.123} \\
+\num[minimum-decimal-digits = 2]{0.123} \\
+\num[minimum-decimal-digits = 4]{0.123}
-\subsubsection{scientific-notation, fixed-exponent}
+\subsubsection{Other Tests}
{
-\num{0.001}\\
-\num{0.0100} \\
-\num{1200}\\
-\sisetup{scientific-notation = true}%
-\num{0.001}\\
-\num{0.0100} \\
-\num{1200}\\
-\sisetup{scientific-notation = engineering}%
-\num{0.001}\\
-\num{0.0100} \\
-\num{1200}\\
+\num{001} \\
+%\num{123.} \\ % v2 or v3
+\num{456} \\
+\num{.789} \\
\sisetup{
-fixed-exponent = 2,
-scientific-notation = fixed,
+ minimum-decimal-digits = 0,
+ print-zero-integer = false,
}%
-\num{0.001}\\
-\num{0.0100} \\
-\num{1200}\\
+%\num{123.} \\ % v2 or v3
+\num{456} \\
+\num{.789} \\
}
+Also: \\
+\num[minimum-decimal-digits=2]{1} \\
+\num[minimum-decimal-digits=1, drop-zero-decimal]{1.00} \\
+\num[minimum-decimal-digits=2, drop-zero-decimal]{1}
-\subsubsection{omit-uncertainty}
-\num{0.01(2)} \\
-\num[omit-uncertainty]{0.01(2)}\\
-
-\subsection{Printing numbers}
-\subsubsection{group-digits, group-four-digits,group-seperator}
-\num{12345.67890}\\
-\num[group-digits= false]{12345.67890}\\
-\num[group-digits= decimal]{12345.67890} \\
-\num[group-digits= integer]{12345.67890}\\
+% an example from https://github.com/josephwright/siunitx/issues/756
+\num[separate-uncertainty=true, round-mode=uncertainty, drop-zero-decimal=true, round-precision=1]{-52.01(43)}
-\num[group-digits= false]{12345.67890}\\
-\num[group-digits= decimal]{12345.67890} \\
-\num[group-digits= integer]{12345.67890}\\
-
-\num{1234567890.1234567890}\\
-\num[group-four-digits]{1234567890.1234567890}\\
+\subsection{Printing Numbers}
+\subsubsection{group-digits, group-separator}
+\num{12345.67890} \\
+\num[group-digits = none]{12345.67890} \\
+\num[group-digits = decimal]{12345.67890} \\
+\num[group-digits = integer]{12345.67890}\\
\num{12345} \\
\num[group-separator = {,}]{12345} \\
\num[group-separator = \text{~}]{12345}\\
+\num[group-separator = \ ]{12345}
\subsubsection{group-minimum-digits}
\num{1234} \\
+\num{12345} \\
+\num[group-minimum-digits = 5]{1234} \\
+\num[group-minimum-digits = 5]{12345} \\
+\num{1234.5678} \\
+\num{12345.67890} \\
+\num[group-minimum-digits = 5]{1234.5678} \\
+\num[group-minimum-digits = 5]{12345.67890}
+
+\subsubsection*{Other tests}
+\num{1234} \\
\num[group-minimum-digits = 4]{1234} \\
\num{1234.5678} \\
-\num[group-minimum-digits = 4]{1234.5678}\\
+\num[group-minimum-digits = 4]{1234.5678}
-\subsubsection{output-complex-root,output-decimal-marker,copy-complex-root,copy-decimal-marker}
-\num{1.23} \\
-\num[output-decimal-marker = {,}]{1.23} \\
-\num{1+2i} \\
-\num[output-complex-root = \text{\ensuremath{i}}]{1+2i} \\
-\num[output-complex-root = j]{1+2i} \\
-\num[copy-complex-root]{1+2j} \\
-\num[copy-decimal-marker]{555,555}\\
+\subsubsection{digit-group-size, digit-group-first-size, digit-group-other-size}
+\num{1234567890} \\
+\num[digit-group-size = 5]{1234567890} \\
+\num[digit-group-other-size = 2]{1234567890}
-\subsubsection{complex-root-position}
-\num{67-0.9i} \\
-\num[complex-root-position = before-number]{67-0.9i} \\
-\num[complex-root-position = after-number]{67-0.9i}\\
+\subsubsection*{Other tests}
+\num{1234567890.1234567890}\\
+\num[digit-group-size=4]{1234567890.1234567890}\\
+\num[digit-group-size = 5, digit-group-other-size = 2]{1234567890} \\
+\num[digit-group-other-size = 2, digit-group-size = 5]{1234567890} \\
+\num[digit-group-size = 5, digit-group-first-size = 2]{1234567890} \\
+\num[digit-group-first-size = 2, digit-group-size = 5]{1234567890}
+
+\subsubsection{output-decimal-marker}
+\num{1.23} \\
+\num[output-decimal-marker = {,}]{1.23} % todo spacing: the , gets interpreted as punctuation
\subsubsection{exponent-base, exponent-product}
\num[exponent-product = \times]{1e2} \\
\num[exponent-product = \cdot]{1e2} \\
-\num[exponent-base = 2]{1e2}\\
+\num[exponent-base = 2]{1e2}
\subsubsection{output-exponent-marker}
+\num[output-exponent-marker = e]{1e2} \\
\num[output-exponent-marker = \text{e}]{1e2} \\
-\num[output-exponent-marker = \ensuremath{\mathrm{E}}]{1e2}\\
+\num[output-exponent-marker = \mathrm{E}]{1e2}\\
+\num[output-exponent-marker = \ensuremath{\mathrm{E}}]{1e2}
-\subsubsection{separate-uncertainty,uncertainty-separator,output-open-uncertainty,output-close-uncertainty}
+\subsubsection{uncertainty-[mode\textbar separator], output-[open\textbar close]-uncertainty}
{
+\num{123.45(120)} \\
+\num{0.035(14)} \\
+\sisetup{uncertainty-mode = full}
+\num{123.45(120)} \\
+\num{0.035(14)} \\
+\sisetup{uncertainty-mode = compact-marker}
+\num{123.45(120)} \\
+\num{0.035(14)} \\
+%\sisetup{uncertainty-mode = separate}
+\sisetup{
+output-open-uncertainty = [,
+output-close-uncertainty = ],
+uncertainty-separator = \,
+}%
\num{1.234(5)} \\
\num{1.234\pm 0.005} \\
-\num[separate-uncertainty = true]{1.234(5)} \\
-\num[separate-uncertainty = true]{1.234\pm 0.005} \\
-\sisetup{
- output-open-uncertainty = [,
- output-close-uncertainty = ],
- uncertainty-separator= {\,}
-}
-\num{1.234(5)}\\
-}
+\num[uncertainty-mode = separate]{1.234(5)} \\
+\num[uncertainty-mode = separate]{1.234\pm 0.005} \\
\num{8.2(13)} \\
\num{8.2\pm1.3} \\
-\num[separate-uncertainty]{8.2(13)}\\
-\num[separate-uncertainty]{8.2\pm1.3} \\
-
-\num{1.234(5) x \pi} \\
-\num[separate-uncertainty = true]{1.234(5) x \pi} \\
-
+\num[uncertainty-mode = separate]{8.2(13)}\\
+\num[uncertainty-mode = separate]{8.2\pm1.3} \\
\num{1.2 +- 0.001}\\
-\num[separate-uncertainty]{1.2 +- 0.001}\\
+\num[uncertainty-mode = separate]{1.2 +- 0.001}\\
+}
+\fbox{\parbox{4em}{%
+\sisetup{uncertainty-mode = separate}%
+\num{67890+-12345}\\
+\num[allow-uncertainty-breaks=false]{67890+-12345}% todo spacing
+}}
-\subsubsection{bracket-numbers, open-bracket, close-bracket}
+\subsubsection{uncertainty-descriptors, uncertainty-descriptor-mode, uncertainty-descriptor-separator} % todo spacing, todo font
{
+\num{1.2(3)(4)} \\
+\sisetup{uncertainty-descriptors = {sys, stat}}
+\num{1.2(3)(4)} \\ % default uncertainty-descriptor-mode = bracket-separator
+\num[uncertainty-descriptor-mode = subscript]{1.2(3)(4)}\\
+\num[uncertainty-descriptor-mode = bracket]{1.2(3)(4)}\\
+\num[uncertainty-descriptor-mode = separator]{1.2(3)(4)}\\
+\num[uncertainty-mode=separate]{1.2(3)}\\
+\num[uncertainty-mode=separate]{1.2(3)(4)(5)}\\
+}
+\num[uncertainty-mode=separate, uncertainty-descriptors={sys}]{1.2(3)}\\
+\num[uncertainty-mode=separate, uncertainty-descriptors={sys,stat,third}]{1.2(3)(4)}
+
+\subsubsection{simplify-uncertainty}
+\num{10.56(2:2)} \\
+\num[simplify-uncertainty]{10.56(2:2)}
+
+\subsubsection{bracket-ambiguous-numbers}
\num{1 e10} \\
-\num{2i e10} \\
-\num{1+2i e10} \\
-\num[bracket-numbers = false]{1+2i e10} \\
-\sisetup{
- open-bracket = \{,
- close-bracket = \},
+\complexnum{2i e10} \\
+\complexnum{1+2i e10} \\
+\complexnum[bracket-ambiguous-numbers = false]{1+2i e10} \\
+{
+\sisetup{uncertainty-mode = separate}
+\num{1.2(3)e4} \\
+\num[bracket-ambiguous-numbers = false]{1.2(3)e4} \\
}
-\num{1+2i e10}\\
+{
+\sisetup{uncertainty-mode = separate, bracket-ambiguous-numbers = false}%
+\num{1.234(5)e-4} %\\
+%\qty{1.234(5)e-4}{\metre}
+% "This option only applies to pure numbers: when formatting quantities,
+% the need for brackets also depends on the placement of units, so is controlled by separate-uncertainty-units."
}
\subsubsection{negative-color}
\num{-15673} \\
\num[negative-color = red]{-15673}
+also:\\
+\num[negative-color = red]{-0}\\
+\num[negative-color = red, retain-negative-zero]{-0}\\
+\num[color=green,negative-color=red]{-10}\\
+\num[negative-color=red,color=green]{-10}
+
+{\sisetup{negative-color=red}
+\complexnum{1}\\
+\complexnum{-1}\\
+\complexnum{3i}\\
+\complexnum{-3i}\\
+\complexnum{1+3i}\\
+\complexnum{-1+3i}\\
+\complexnum{1-3i}\\
+\complexnum{-1-3i}
+}
+
\subsubsection{bracket-negative-numbers}
\num{-15673} \\
\num[bracket-negative-numbers]{-15673} \\
-%\SI{-10}{\metre} \\
-%\SI[bracket-negative-numbers]{-10}{\metre}
+\qty{-10}{\metre} \\
+\qty[bracket-negative-numbers]{-10}{\metre}
-\subsection{Multi-part Numbers}
+{\sisetup{bracket-negative-numbers}
+\num{<<-5}\\
+\num{-13(1)}\\
+\num{-13+-1}\\
+\num{-e10}\\
+\num{-2e10}\\
+\complexnum{1}\\
+\complexnum{-1}\\
+\complexnum{3i}\\
+\complexnum{-3i}\\
+\complexnum{1+3i}\\
+\complexnum{-1+3i}\\
+\complexnum{1-3i}\\
+\complexnum{-1-3i}
+}
-\subsubsection{input-product,input-quotient}
-\num{1 x 2 x 3} \\
-\num{1e4 x 2(3) x 3/4} \\
-\num[input-product=*]{4 * 5 * 6} \\
-\num{ 1 / 2e4 } \\
-\num{ 1e2 / 3e4 }\\
+\subsubsection{tight-spacing} % todo spacing
+\num{2e3} \\
+\num[tight-spacing = true]{2e3}
+
+\subsubsection{print-implicit-plus, print-[exponent\textbar mantissa]-implicit-plus}
+a\\
+\num{345} \\
+\num[print-implicit-plus]{345} \\
+\num[print-mantissa-implicit-plus]{345} \\
+\num[print-exponent-implicit-plus]{345} \\
+b\\
+\num{1e2} \\
+\num[print-implicit-plus]{1e2} \\
+\num[print-mantissa-implicit-plus]{1e2} \\
+\num[print-exponent-implicit-plus]{1e2} \\
+c\\
+\num{1e+2} \\
+\num[print-implicit-plus]{1e+2} \\
+\num[print-mantissa-implicit-plus]{1e+2} \\
+\num[print-exponent-implicit-plus]{1e+2} \\
+d\\
+\num{-1e2} \\
+\num[print-implicit-plus]{-1e2} \\
+\num[print-mantissa-implicit-plus]{-1e2} \\
+\num[print-exponent-implicit-plus]{-1e2} \\
+e\\
+\num{1e-2} \\
+\num[print-implicit-plus]{1e-2} \\
+\num[print-mantissa-implicit-plus]{1e-2} \\
+\num[print-exponent-implicit-plus]{1e-2}
+
+\subsubsection{print-unity-mantissa, print-zero-[exponent\textbar integer]}
+\num{1e4} \\
+\num[print-unity-mantissa = false]{1e4} \\
+\num{444e0} \\
+\num[print-zero-exponent = true]{444e0} \\
+\num{0.123} \\
+\num[print-zero-integer = false]{0.123}
-\subsubsection{output-product, output-quotient}
-\num[output-product = \cdot]{4.87 x 5.321 x 6.90545} \\
-\num[output-quotient = \text{ div }]{1 / 2}\\
+\num{e4}\\
+%\num[print-unity-mantissa]{e4}\\
+\num[print-unity-mantissa=false]{e4}
-\subsubsection{quotient-mode}
-\num{1 / 2e4} \\
-\num[quotient-mode = fraction]{1 / 2e4}\\
+\num[]{1e0}\\
+\num[print-unity-mantissa=false]{1e0}\\ % 1 should leak through as per documentation b/c print-zero-exponent=false
+\num[print-zero-exponent=true]{1e0}\\
+\num[print-unity-mantissa=false, print-zero-exponent=true]{1e0}
-\subsubsection{fraction-function}
+\subsubsection{zero-decimal-as-symbol, zero-symbol}
+\num{123.00} \\
{
-\sisetup{quotient-mode= fraction}
-\num{1 / 1}\\
-\num[fraction-function= \dfrac]{1 / 2}\\
-%\num[fraction-function= \sfrac]{1 / 3}\\
-\num[fraction-function= \tfrac]{1 / 4}\\
+\sisetup{zero-decimal-as-symbol}
+\num{123.00} \\
+%\num{123.} \\ % v2 or v3
+\num{123} \\
+\num[zero-symbol = \text{[{---}]}]{123.00}
}
-\subsection{Lists and ranges of numbers}
-\subsubsection{list-final-separator,list-pair-separator,list-separator}
+\subsection{Lists, products and ranges}
+\subsubsection{list-[final\textbar pair]-separator, list-separator}
\numlist{0.1;0.2;0.3}\\
-\numlist[color=blue]{0.1;0.2;0.3}\\
\numlist[list-separator = {; }]{0.1;0.2;0.3}\\
\numlist[list-final-separator = {, }]{0.1;0.2;0.3} \\
\numlist[
-list-separator
-= { and },
+list-separator = { and },
list-final-separator = { and finally }
]{0.1;0.2;0.3} \\
\numlist{0.1;0.2} \\
-\numlist[list-pair-separator = {, and }]{0.1;0.2}\\
+\numlist[list-pair-separator = {, and }]{0.1;0.2} \\
+\numlist[color=blue]{0.1;0.2;0.3}
-\subsection{range-phrase}
+\subsubsection{product-[mode\textbar phrase\textbar symbol]}
+\numproduct{5 x 100 x 2} \\
+\numproduct[product-symbol = \ensuremath{\cdot}]{5 x 100 x 2} \\
+{
+\sisetup{product-mode = phrase}%
+\numproduct{5 x 100 x 2}\\
+\numproduct[product-phrase = { BY }]{5 x 100 x 2}
+}
+\subsubsection{range-open-phrase, range-phrase}
\numrange{5}{100} \\
\numrange[range-phrase = --]{5}{100}\\
-\numrange[color=blue,range-phrase = --]{5}{100}\\
+\numrange{10}{12} \\
+\numrange[range-open-phrase = {\text{from} }]{5}{100}\\
+\numrange[color=blue,range-phrase = --]{5}{100}
+
+\subsubsection{[list\textbar product\textbar range]-exponents}
+\numlist{5e3;7e3;9e3;1e4} \\
+\numproduct{5e3 x 7e3 x 9e3 x 1e4} \\
+\numrange{5e3}{7e3} \\
+{
+\sisetup
+{
+list-exponents = combine-bracket ,
+product-exponents = combine-bracket ,
+range-exponents = combine-bracket
+}
+\numlist{5e3;7e3;9e3;1e4} \\
+\numproduct{5e3 x 7e3 x 9e3 x 1e4} \\
+\numrange{5e3}{7e3} \\
+\numrange[range-open-phrase={\text{from }}]{5e3}{7e3} \\
+\sisetup
+{
+list-exponents = combine ,
+product-exponents = combine ,
+range-exponents = combine
+}
+\numlist{5e3;7e3;9e3;1e4} \\
+\numproduct{5e3 x 7e3 x 9e3 x 1e4} \\
+\numrange{5e3}{7e3}
+}
+
+\subsubsection{[list\textbar product\textbar range]-units}
+\qtylist{2;4;6;8}{\tesla} \\
+\qtylist[list-units = bracket]{2;4;6;8}{\tesla} \\
+\qtylist[list-units = repeat]{2;4;6;8}{\tesla} \\
+\qtylist[list-units = single]{2;4;6;8}{\tesla} \\
+\qtyrange{2}{4}{\degreeCelsius} \\
+\qtyrange[range-units = bracket]{2}{4}{\degreeCelsius} \\
+\qtyrange[range-units = repeat]{2}{4}{\degreeCelsius} \\
+\qtyrange[range-units = single]{2}{4}{\degreeCelsius}\\
+\qtyproduct{2 x 3 x 4}{\metre} \\
+\qtyproduct[product-units = bracket]{2 x 3 x 4}{\metre}\\
+\qtyproduct[product-units = repeat]{2 x 3 x 4}{\metre}\\
+\qtyproduct[product-units = single]{2 x 3 x 4}{\metre}\\
+\qtyproduct[product-units = bracket-power]{2 x 3 x 4}{\metre}\\
+\qtyproduct[product-units = power]{2 x 3 x 4}{\metre}
+
+\subsubsection{[list\textbar product\textbar range]-[close\textbar open]-bracket}
+{
+\sisetup{
+list-units = bracket ,
+list-open-bracket = [ ,
+list-close-bracket = ]
+}
+\qtylist{5e3;7e3;9e3;1e4}{\second} \\
+\sisetup{
+product-units = bracket ,
+product-open-bracket = \{ ,
+product-close-bracket = \}
+}
+\qtyproduct{5e3 x 7e3 x 9e3 x 1e4}{\metre} \\
+\sisetup{
+range-units = bracket ,
+range-open-bracket = \langle,
+range-close-bracket = \rangle
+}
+\qtyrange{2}{4}{\degreeCelsius}\\
+\sisetup{
+list-units = bracket ,
+list-open-bracket = ( ,
+list-close-bracket = )
+}
+\numlist{5e3;7e3;9e3;1e4} \\
+\sisetup{
+product-units = bracket ,
+product-open-bracket = ( ,
+product-close-bracket = )
+}
+\numproduct{5e3 x 7e3 x 9e3 x 1e4} \\
+\sisetup{
+range-units = bracket ,
+range-open-bracket = ( ,
+range-close-bracket = )
+}
+\qtyrange{2}{4}{\degreeCelsius}
+}
+
+\subsubsection{[list\textbar product\textbar range]-independent-prefix}
+{
+a\\
+\qtyrange{1e3}{1e9}{\watt}\\
+\qtylist{1e3;1e9}{\watt}\\
+\qtyproduct{1e3 x 1e9}{\watt}\\
+b\\
+\qtyrange[range-independent-prefix]{1e3}{1e9}{\watt}\\
+\qtylist[list-independent-prefix]{1e3;1e9}{\watt}\\
+$\qtyproduct[product-independent-prefix]{1e3 x 1e9}{\watt}$\\
+\sisetup{exponent-to-prefix}%
+c\\
+\qtyrange{1000}{1e9}{\watt}\\
+\qtyrange{1e3}{1e9}{\watt}\\
+\qtylist{1000;1e9}{\watt}\\
+\qtylist{1e3;1e9}{\watt}\\
+\qtyproduct{1000 x 1e9}{\watt}\\
+\qtyproduct{1e3 x 1e9}{\watt}\\
+d\\
+\qtyrange[range-independent-prefix]{1e3}{1e9}{\watt}\\
+\qtylist[list-independent-prefix]{1e3;1e9}{\watt}\\
+$\qtyproduct[product-independent-prefix]{1e3 x 1e9}{\watt}$
+}
+
+
+\subsubsection{prefix-mode = combine-exponent}
+\qty{1700}{\g} \\
+\qty{1.7e3}{\g} \\
+{
+\sisetup{prefix-mode = combine-exponent}%
+\qty{1700}{\g} \\
+\qty{1.7e3}{\g} \\
+}
+{
+\sisetup{fixed-exponent = 3, exponent-mode = fixed}%
+\qty{1700}{\g} \\
+\qty{1.7e3}{\g}
+}
+
+
+\subsection{Complex Numbers}
+\subsubsection{complex-mode}
+\complexnum{1 + i} \\
+\complexnum{1:45} \\
+\complexnum[complex-mode = cartesian]{1 + i} \\
+\complexnum[complex-mode = cartesian, round-mode = places]{1:45} \\
+\complexnum[complex-mode = polar]{1 + i} \\
+\complexnum[complex-mode = polar]{1:45}
+
+\subsubsection{input-complex-roots}
+\complexnum{9.99 + 88.8i} \\
+\complexnum{9.99 + i88.8}
+
+\subsubsection{output-complex-root} % todo font
+\complexnum{1+2i} \\
+\complexnum[output-complex-root = i]{1+2i} \\
+\complexnum[output-complex-root = \text{\ensuremath{i}}]{1+2i} \\
+\complexnum[output-complex-root = j]{1+2i}
+
+\subsubsection{complex-root-position}
+\complexnum{67-0.9i} \\
+\complexnum[complex-root-position = before-number]{67-0.9i} \\
+\complexnum[complex-root-position = after-number]{67-0.9i}
+
+\subsubsection{complex-angle-unit, complex-symbol-angle, complex-symbol-degree}
+\complexqty{1:1}{\ohm} \\
+\complexqty[complex-angle-unit = radians]{1:1}{\ohm} \\
+\complexqty[complex-symbol-degree = d]{1:1}{\ohm}
+%\complexqty[complex-phase-command = \phase]{1:1}{\ohm} % needs steinmetz package
+
+\subsubsection{print-complex-unity}
+\complexqty{1i}{\ohm} \\
+\complexqty[print-complex-unity]{1i}{\ohm}\\
+\complexqty[print-complex-unity]{i}{\ohm}
+
+\subsubsection{Other tests}
+Real numbers as complex:\\
+\complexnum{1}\\
+\complexnum[print-complex-unity]{1}\\
+\complexnum[complex-mode=cartesian]{1:180}\\
+\complexnum[complex-mode=cartesian]{1:360}\\
+\complexnum[complex-mode=cartesian]{1:540}\\
+\complexnum[complex-mode=cartesian]{1:720}\\
+\complexnum[complex-mode=cartesian, complex-angle-unit=radians]{1:3.14159265359}\\ % Perl uses sci notation for these two
+\complexnum[complex-mode=cartesian, complex-angle-unit=radians]{1:6.28318530718} % TeX doesn't
+
+\complexnum[complex-mode=polar, complex-angle-unit=radians]{i}
\subsection{Angles}
-\subsubsection{number-angle-product}
+\subsubsection{angle-mode}
\ang{2.67} \\
-\ang[number-angle-product = \,]{2.67}
-\subsubsection{arc-separator}
-\ang{6;7;6.5} \\
-\ang[arc-separator = \,]{6;7;6.5}
-\subsubsection{add-arc-degree-zero,add-arc-minute-zero,add-arc-second-zero}
+\ang{2;3;4} \\
+\ang[angle-mode = arc]{2.67} \\
+\ang[angle-mode = arc]{2;3;4} \\
+\ang[angle-mode = decimal]{2.67} \\
+\ang[angle-mode = decimal]{2;3;4}
+
+\subsubsection{angle-separator}
+\ang{6;7;6.5} \\ % todo spacing
+\ang[angle-separator = \,]{6;7;6.5}
+
+\subsubsection{fill-angle-[degrees\textbar minutes\textbar seconds]}
\ang{-1;;} \\
\ang{;-2;} \\
\ang{;;-3} \\
{
-\sisetup{add-arc-degree-zero}
+\sisetup{fill-angle-degrees}
\ang{-1;;} \\
\ang{;-2;} \\
\ang{;;-3} \\
}
{
-\sisetup{add-arc-minute-zero}
+\sisetup{fill-angle-minutes}
\ang{-1;;} \\
\ang{;-2;} \\
\ang{;;-3} \\
}
{
-\sisetup{add-arc-second-zero}
+\sisetup{fill-angle-seconds}
\ang{-1;;} \\
\ang{;-2;} \\
-\ang{;;-3}\\
+\ang{;;-3} \\
+}
+\ang[fill-angle-minutes,fill-angle-seconds]{45.697}\\
+\ang[color=blue,fill-angle-minutes,fill-angle-seconds]{45.697}
+
+\subsubsection{angle-symbol-[degree\textbar minute\textbar second]}
+{
+\ang{6;7;6.5} \\
+\sisetup{
+angle-symbol-degree = d ,
+angle-symbol-minute = m ,
+angle-symbol-second = s
+}
+\ang{6;7;6.5}
}
-\ang[add-arc-minute-zero,add-arc-second-zero]{45.697}\\
-\ang[color=blue,add-arc-minute-zero,add-arc-second-zero]{45.697}\\
\subsubsection{angle-symbol-over-decimal}
\ang{45.697} \\
@@ -430,559 +1458,218 @@ \subsubsection{angle-symbol-over-decimal}
% \ang{+10;;}\\
% \ang{-0;1;}\\
+\stepcounter{subsection}
-\section{Units}
-\def\showunit#1{#1 & \texttt{\Backslash#1} & \si{\csname#1\endcsname}}
-\begin{table}
-\caption{SI base units}
-\centering
-\begin{tabular}{lll}\hline\hline
-Unit & Macro & Symbol \\\hline
-\showunit{ampere}\\
-\showunit{candela}\\
-\showunit{kelvin}\\
-\showunit{kilogram}\\
-\showunit{metre}\\
-\showunit{mole}\\
-\showunit{second}\\
-\end{tabular}
-\end{table}
-
-\begin{table}
-\caption{Coherent derived units}
-\centering
-\begin{tabular}{llllll}\hline\hline
-Unit & Macro & Symbol &
-Unit & Macro & Symbol \\\hline
-\showunit{becquerel} & \showunit{newton} \\
-\showunit{degreeCelsius} & \showunit{ohm} \\
-\showunit{coulomb} & \showunit{pascal} \\
-\showunit{farad} & \showunit{radian} \\
-\showunit{gray} & \showunit{siemens} \\
-\showunit{hertz} & \showunit{sievert} \\
-\showunit{henry} & \showunit{steradian} \\
-\showunit{joule} & \showunit{tesla} \\
-\showunit{katal} & \showunit{volt} \\
-\showunit{lumen} & \showunit{watt} \\
-\showunit{lux} & \showunit{weber} \\
-\end{tabular}
-\end{table}
-
-\begin{table}
-\caption{Non-SI units}
-\centering
-\begin{tabular}{lll}\hline\hline
-Unit & Macro & Symbol \\\hline
-\showunit{day}\\
-\showunit{degree}\\
-\showunit{hectare}\\
-\showunit{hour} \\
-\showunit{litre}\\
-\showunit{liter}\\
-\showunit{arcminute}\\
-\showunit{minute}\\
-\showunit{arcsecond}\\
-\showunit{tonne}\\
-\end{tabular}
-\end{table}
-
-\begin{table}
-\caption{Expermental Non-SI units}
-\centering
-\begin{tabular}{lll}\hline\hline
-Unit & Macro & Symbol \\\hline
-\showunit{astronomicalunit}\\
-\showunit{atomicmassunit}\\
-\showunit{bohr}\\
-\showunit{clight}\\
-\showunit{dalton}\\
-\showunit{electronmass}\\
-\showunit{electronvolt}\\
-\showunit{elementarycharge}\\
-\showunit{hartree}\\
-\showunit{planckbar}\\
-\end{tabular}
-\end{table}
-
-\begin{table}
-\caption{Other non-SI units}
-\centering
-\begin{tabular}{lll}\hline\hline
-Unit & Macro & Symbol \\\hline
-\showunit{angstrom}\\
-\showunit{bar}\\
-\showunit{barn}\\
-\showunit{bel}\\
-\showunit{decibel}\\
-\showunit{knot}\\
-\showunit{mmHg}\\
-\showunit{nauticalmile}\\
-\showunit{neper}\\
-\end{tabular}
-\end{table}
-
-\def\showprefix#1{#1 & \texttt{\Backslash#1} & \si{\csname#1\endcsname} & \si[prefixes-as-symbols=false]{\csname#1\endcsname}}
-\begin{table}
-\caption{Other non-SI units}
-\centering
-\begin{tabular}{llllllll}\hline\hline
-Unit & Macro & Symbol & Power &
-Unit & Macro & Symbol & Power \\\hline
-\showprefix{yocto} & \showprefix{deca}\\
-\showprefix{zepto} & \showprefix{hecto}\\
-\showprefix{atto} & \showprefix{kilo}\\
-\showprefix{femto} & \showprefix{mega}\\
-\showprefix{pico} & \showprefix{giga}\\
-\showprefix{nano} & \showprefix{tera}\\
-\showprefix{micro} & \showprefix{peta}\\
-\showprefix{milli} & \showprefix{exa}\\
-\showprefix{centi} & \showprefix{zetta}\\
-\showprefix{deci} & \showprefix{yotta}\\
-\end{tabular}
-\end{table}
-
-
-\begin{table}
-\caption{Abbreviated units}
-\centering
-\begin{tabular}{lll}\hline\hline
-Unit & Macro & Symbol \\\hline
-\showunit{fg}\\
-\showunit{pg}\\
-\showunit{ng}\\
-\showunit{ug}\\
-\showunit{mg}\\
-\showunit{g}\\
-\showunit{kg}\\
-\showunit{amu}\\\hline
-\showunit{pm}\\
-\showunit{nm}\\
-\showunit{um}\\
-\showunit{mm}\\
-\showunit{cm}\\
-\showunit{dm}\\
-\showunit{m}\\
-\showunit{km}\\\hline
-\showunit{as}\\
-\showunit{fs}\\
-\showunit{ps}\\
-\showunit{ns}\\
-\showunit{us}\\
-\showunit{ms}\\
-\showunit{s}\\\hline
-\showunit{fmol}\\
-\showunit{pmol}\\
-\showunit{nmol}\\
-\showunit{umol}\\
-\showunit{mmol}\\
-\showunit{mol}\\
-\showunit{kmol}\\\hline
-\showunit{pA}\\
-\showunit{nA}\\
-\showunit{uA}\\
-\showunit{mA}\\
-\showunit{A}\\
-\showunit{kA}\\\hline
-\showunit{ul}\\
-\showunit{ml}\\
-\showunit{l}\\
-\showunit{hl}\\
-\showunit{uL}\\
-\showunit{mL}\\
-\showunit{L}\\
-\showunit{hL}\\\hline
-\showunit{mHz}\\
-\showunit{Hz}\\
-\showunit{kHz}\\
-\showunit{MHz}\\
-\showunit{GHz}\\
-\showunit{THz}\\\hline
-\showunit{mN}\\
-\showunit{N}\\
-\showunit{kN}\\
-\showunit{MN}\\\hline
-\showunit{Pa}\\
-\showunit{kPa}\\
-\showunit{MPa}\\
-\showunit{GPa}\\\hline
-\showunit{mohm}\\
-\showunit{kohm}\\
-\showunit{Mohm}\\\hline
-\showunit{pV}\\
-\showunit{nV}\\
-\showunit{uV}\\
-\showunit{mV}\\
-\showunit{V}\\
-\showunit{kV}\\\hline
-\showunit{W}\\
-\showunit{uW}\\
-\showunit{mW}\\
-\showunit{kW}\\
-\showunit{MW}\\
-\showunit{GW}\\\hline
-\showunit{J}\\
-\showunit{kJ}\\\hline
-\showunit{eV}\\
-\showunit{meV}\\
-\showunit{keV}\\
-\showunit{MeV}\\
-\showunit{GeV}\\
-\showunit{TeV}\\\hline
-\showunit{kWh}\\\hline
-\showunit{F}\\
-\showunit{fF}\\
-\showunit{pF}\\\hline
-\showunit{K}\\\hline
-\showunit{dB}\\\hline
-\end{tabular}
-\end{table}
-
-\begin{table}
-\caption{Binary prefixes}
-\centering
-\begin{tabular}{llllllll}\hline\hline
-Unit & Macro & Symbol & Power \\\hline
-\showprefix{kibi} \\
-\showprefix{mebi} \\
-\showprefix{gibi} \\
-\showprefix{tebi} \\
-\showprefix{pebi} \\
-\showprefix{exbi} \\
-\showprefix{zebi} \\
-\showprefix{yobi} \\
-\end{tabular}
-\end{table}
+\subsection{Using Units}
+\subsubsection{inter-unit-product}
+\unit{\farad\squared\lumen\candela} \\
+\unit[inter-unit-product = \ensuremath{{}\cdot{}}]{\farad\squared\lumen\candela}\\
+\unit[color=blue,inter-unit-product = \ensuremath{{}\cdot{}}]{\farad\squared\lumen\candela}
-%\subsection{Creating Units}
-%\subsection{Loading additional units}
+\subsubsection{per-mode, [display\textbar inline]-per-mode, per-symbol, fraction-command, bracket-unit-denominator}
+\unit{\joule\per\mole\per\kelvin} \\
+\unit{\metre\per\second\squared} \\
+\unit[per-mode=fraction]{\joule\per\mole\per\kelvin} \\
+\unit[per-mode=fraction]{\joule\raiseto{-1}\mole\per\kelvin} \\
+\unit[per-mode=fraction]{\metre\per\second\squared}\\
+\unit{\ampere\per\mole\second} \\
+\unit[per-mode = power-positive-first]{\ampere\per\mole\second}
-\subsection{Using units}
-% Note that \kilogram is defined as \kilo\gram, and acts sorta like a macro
-\si{\kilogram}
-\si{\kilo\gram}
-% Not allowed.
-%\si{\kilo\kilogram}
-%\si{\kilo\kilo\gram}
-{
-% and note that regular macros ARE expanded!
-\def\killermeater{\kilo\meter}
-\si{\killermeater}
-}
{
-% BUT, it prefers its OWN definitions!
-\def\gram{Grr}
-\si{\kilo\gram}
+\sisetup{per-mode = symbol}%
+\unit{\joule\per\mole\per\kelvin} \\
+\unit{\metre\per\second\squared} \\
+\unit[per-symbol = \ \text{div}\ ]{\joule\per\mole\per\kelvin} \\
+\unit[bracket-unit-denominator = false]{\joule\per\mole\per\kelvin}\\
}
-
-% However, it's happy with various indirect definitions.
-% Ie. units, prefixes (at least) act like regular macros within \si.
-\si{\abbra}\\
-\si{\abbrb}\\
-\si{\abbrc}\\
-\si{\abbrd}\\
-\si{\abbre}\\
-
-\si{\mabbra}\\
-\si{\mabbrb}\\
-
-\si{\killer\meter}\\
-
-% Where can highlight go?
-\si{\highlight{red}\kilo\gram\metre\per\second} \\
-\si{\kilo\highlight{red}\gram\metre\per\second} \\
-\si{\kilo\gram\highlight{red}\metre\per\second} \\
-\si{\kilo\gram\metre\highlight{red}\per\second} \\
-\si{\kilo\gram\metre\per\highlight{red}\second} \\
-
-\si{\cancel\kilo\gram\metre\per\second} \\
-\si{\kilo\cancel\gram\metre\per\second} \\
-\si{\kilo\gram\cancel\metre\per\second} \\
-\si{\kilo\gram\metre\cancel\per\second} \\
-\si{\kilo\gram\metre\per\cancel\second} \\
-
-\subsubsection{forbid-literal-units, inter-unit-product}
-\si{\farad\squared\lumen\candela} \\
-\si[inter-unit-product = \ensuremath{{}\cdot{}}]{\farad\squared\lumen\candela}\\
-\si[color=blue,inter-unit-product = \ensuremath{{}\cdot{}}]{\farad\squared\lumen\candela}\\
-\subsubsection{per-mode, per-symbol, bracket-unit-denominator}
-\si{\joule\per\mole\per\kelvin} \\
-\si{\metre\per\second\squared} \\
-\si[per-mode=fraction]{\joule\per\mole\per\kelvin} \\
-\si[per-mode=fraction]{\joule\raiseto{-1}\mole\per\kelvin} \\
-\si[per-mode=fraction]{\metre\per\second\squared}\\
-\si{\ampere\per\mole\second} \\
-\si[per-mode = reciprocal-positive-first]{\ampere\per\mole\second}
+\unit[per-mode=repeated-symbol]{\joule\per\mole\per\kelvin}
{
-\sisetup{per-mode = symbol}%
-\si{\joule\per\mole\per\kelvin} \\
-\si{\metre\per\second\squared} \\
-\si[per-symbol = \text{~div~}]{\joule\per\mole\per\kelvin} \\
-\si[bracket-unit-denominator = false]{\joule\per\mole\per\kelvin}\\
+\sisetup{per-mode = single-symbol}
+\qty{10}{\per\metre} \\
+\qty{20}{\metre\per\second} \\
+\qty{30}{\joule\per\mole\per\kelvin}
}
-\si[per-mode=repeated-symbol]{\joule\per\mole\per\kelvin}\\
+
{
-\sisetup{per-mode = symbol-or-fraction}%
-\( \si{\joule\per\mole\per\kelvin} \)
-\[ \si{\joule\per\mole\per\kelvin} \]
-\si{\joule\per\mole\per\kelvin} \\
+\sisetup{
+display-per-mode = fraction ,
+inline-per-mode = symbol
+}%
+\( \unit{\joule\per\mole\per\kelvin} \)
+\[ \unit{\joule\per\mole\per\kelvin} \]
+\unit{\joule\per\mole\per\kelvin} \\
\(
\displaystyle
-\si{\joule\per\mole\per\kelvin}
+\unit{\joule\per\mole\per\kelvin}
\)
\[
\textstyle
-\si{\joule\per\mole\per\kelvin}
+\unit{\joule\per\mole\per\kelvin}
\]
\[
\textstyle
-\si[color=blue]{\joule\per\mole\per\kelvin}
+\unit[color=blue]{\joule\per\mole\per\kelvin}
\]
}
-\subsubsection{sticky-per}
-\si{\pascal\per\gray\henry} \\
-\si[sticky-per]{\pascal\per\gray\henry}\\
-
-\subsubsection{power-font}
-\si{\metre\per\second\squared} \\
-\si[power-font = unit]{\metre\per\second\squared}\\
-
-\subsubsection{literal-superscript-as-power}
-\si{m.s^{2}} \\
-\si[literal-superscript-as-power = false]{m.s^{2}}\\
-
-\subsubsection{qualifier-mode, qualifier-phrase}
-\si{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
-\si[qualifier-mode = brackets]
-{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
-\si[qualifier-mode = subscript]
-{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
-\si[qualifier-mode = space]
-{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
-\si[qualifier-mode = text]
-{\deci\bel\isotropic}\\
-
-\si[qualifier-mode = phrase]
-{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
-\si[qualifier-mode = phrase, qualifier-phrase = { by }]
-{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
-
-\subsubsection{prefixes-as-symbols}
-\si{\milli\litre\per\mole\deci\ampere} \\
-%%\SI{10}{\kilo\gram\squared\deci\second} \\
-\si[prefixes-as-symbols=false]{\milli\litre\per\mole\deci\ampere}\\
-\si[prefixes-as-symbols=false]{\kilo\gram\squared\deci\second}\\
-\si{\mega\gram\squared\deci\second}\\
-\si[prefixes-as-symbols=false]{\mega\gram\squared\deci\second}\\
-\si{\micro\gram\squared\deci\second}\\
-\si[prefixes-as-symbols=false]{\micro\gram\squared\deci\second}\\
-\si{\per\mega\gram\squared\deci\second}\\
-\si[prefixes-as-symbols=false]{\per\mega\gram\squared\deci\second}\\
-\si{\per\micro\gram\squared\deci\second}\\
-\si[prefixes-as-symbols=false]{\per\micro\gram\squared\deci\second}\\
-%%\SI[prefixes-as-symbols=false]{10}{\kilo\gram\squared\deci\second}\\
-
-\subsubsection{parse-units}
-%%\SI{300}{\MHz} \\
-%%\SI[parse-units = false]{300}{\MHz}\\
-
-\subsection{Numbers with units}
-\subsubsection{allow-number-unit-breaks}
-\subsubsection{number-unit-product}
-\SI{2.67}{\farad} \\
-\SI[number-unit-product = \ ]{2.67}{\farad} \\
-\SI[number-unit-product = ]{2.67}{\farad}\\
-\SI[number-unit-product = \,]{2.67}{\farad}\\
-\SI[number-unit-product = \;]{2.67}{\farad}\\
-\SI[number-unit-product = $\times$]{2.67}{\farad}\\
-\SI[color=blue,number-unit-product = $\times$]{2.67}{\farad}\\
-\subsubsection{multi-part-units}
-{
-\sisetup{separate-uncertainty}%
-\SI{12.3(4)}{\kilo\gram} \\
-\SI[multi-part-units = brackets]{12.3(4)}{\kilo\gram} \\
-\SI[multi-part-units = repeat]{12.3(4)}{\kilo\gram}\\
-\SI[multi-part-units = single]{12.3(4)}{\kilo\gram}\\
-}
-
-{
-\sisetup{separate-uncertainty,bracket-numbers = false}%
-\num{1.234(5)e-4} \\
-\SI{1.234(5)e-4}{\metre}\\
-}
-\subsubsection{product-units}
-\SI{2 x 3 x 4}{\metre} \\
-\SI[product-units = brackets]{2 x 3 x 4}{\metre}\\
-\SI[product-units = brackets-power]{2 x 3 x 4}{\metre}\\
-\SI[product-units = power]{2 x 3 x 4}{\metre}\\
-\SI[product-units = repeat]{2 x 3 x 4}{\metre}\\
-\SI[product-units = single]{2 x 3 x 4}{\metre}\\
-
-\subsubsection{list-units,range-units}
-\SIlist{2;4;6;8}{\tesla} \\
-\SIlist[list-units = brackets]{2;4;6;8}{\tesla} \\
-\SIlist[list-units = repeat]{2;4;6;8}{\tesla} \\
-\SIlist[list-units = single]{2;4;6;8}{\tesla} \\
-\SIrange{2}{4}{\degreeCelsius} \\
-\SIrange[range-units = brackets]{2}{4}{\degreeCelsius} \\
-\SIrange[range-units = repeat]{2}{4}{\degreeCelsius} \\
-\SIrange[range-units = single]{2}{4}{\degreeCelsius}\\
-
-\subsubsection{exponent-to-prefix}
-\SI{1700}{\g} \\
-\SI{1.7e3}{\g} \\
-{
-\sisetup{exponent-to-prefix}%
-\SI{1700}{\g} \\
-\SI{1.7e3}{\g} \\
-}
-{
-\sisetup{fixed-exponent = 3, scientific-notation = fixed}%
-\SI{1700}{\g} \\
-\SI{1.7e3}{\g}\\
-}
-
-
-\section{Tabular material}
-% \begin{tabular}{S}
-% 2.3456 \\
-% \end{tabular}
-% \end{document}
-
-
-\begin{table}[H]
-\caption{Standard behaviour of the \texttt{S} column type.}
-\label{tab:S:standard}
-\centering
-\begin{tabular}{S}
-\toprule
-{Some Values} \\
-\midrule
-2.3456 \\
-34.2345 \\
--6.7835 \\
-90.473 \\
-5642.5 \\
-1.2e3 \\
-e4 \\
-\bottomrule
-\end{tabular}
-\end{table}
+\newcommand{\myfraction}[2]{#1~\text{over }#2}
+Other tests:\\
+\unit[per-mode=single-symbol]{\meter\per\second\liter}\\
+\unit[per-mode=fraction, fraction-command=\myfraction]{\joule\per\mole\per\kelvin}
+\subsubsection{per-symbol-script-correction}
+{
+\sisetup{per-mode = symbol}%
+\unit{\cm\cubed\per\gram} \\
+\unit[per-symbol-script-correction = ]{\cm\cubed\per\gram} % todo spacing
+}
-%\end{document}
+\subsubsection{sticky-per}
+\unit{\pascal\per\gray\henry} \\
+\unit[sticky-per]{\pascal\per\gray\henry}
+
+\subsubsection{qualifier-[mode\textbar phrase]}
+\unit{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
+\unit[qualifier-mode = bracket]{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
+\unit[qualifier-mode = subscript]{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
+\unit[qualifier-mode = phrase, qualifier-phrase=\ ]{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
+\unit[qualifier-mode = combine]{\deci\bel\isotropic}\\
+\unit{\kilogram\of{pol}\squared\per\mole\of{cat}\per\hour} \\
+\unit[qualifier-mode = bracket]{\kilogram\of{pol}\squared\per\mole\of{cat}\per\hour} \\
+\unit[qualifier-mode = combine]{\deci\bel\of{i}} \\
+{
+\sisetup{qualifier-mode = phrase, qualifier-phrase = \ }%
+\unit{\kilogram\of{pol}\squared\per\mole\of{cat}\per\hour} \\
+\sisetup{qualifier-phrase = \ \mbox{of}\ }%
+\unit{\kilogram\of{pol}\squared\per\mole\of{cat}\per\hour}
+}
+%\unit[qualifier-mode = phrase]{\kilogram\polymer\squared\per\mole\catalyst\per\hour} \\
+\unit[qualifier-mode = phrase, qualifier-phrase = { by }]{\kilogram\polymer\squared\per\mole\catalyst\per\hour}
+% keep an eye on siunitx#854
+\unit[qualifier-mode = bracket]{\kilogram\polymer\squared} \\ % v2 invalid; v3 kg(pol)^2
+\unit[qualifier-mode = brackets]{\kilogram\polymer\squared} \\ % kg(pol)^2
+\unit[qualifier-mode = phrase, qualifier-phrase = {\;}]{\kilogram\polymer\squared} \\ % v2 (kg pol)^2; v3 kg pol^2
+\unit[qualifier-mode = space]{\kilogram\polymer\squared} \\ % v2 (kg pol)^2; v3 kg pol^2
+\unit[qualifier-mode = combine]{\kilogram\polymer\squared} \\ % v2 invalid; v3 kgpol^2
+\unit[qualifier-mode = text]{\kilogram\polymer\squared} % kgpol^2
+\subsubsection{power-half-as-sqrt}
+\unit{\Hz\tothe{0.5}} \\
+\unit[power-half-as-sqrt]{\Hz\tothe{0.5}}
-\begin{table}[H]
-\caption{Detection of surrounding material in an \texttt{S} column.}
-\label{tab:S:extras}
-\centering
-\begin{tabular}{S[color=orange]}
-\toprule
-{Some Values} \\
-\midrule
-12.34 \\
-\color{purple} 975,31 \\
-44.268 \textsuperscript{\emph{a}} \\
-\bottomrule
-\end{tabular}
-\end{table}
+\subsubsection{parse-units}
+\qty{300}{\MHz} \\
+\qty[parse-units = false]{300}{\MHz} \\
+\unit{\meter\per\second} \\
+\unit[parse-units=false]{\meter\per\second}
+% Setting parse-units=false speeds things up by making many default assumptions.
+% It's not clear which assumptions we miss.
+% per-mode is the only one I know of.
+% see siunitx#840
+
+\subsubsection{forbid-literal-units}
+\unit{m/s}\\
+%\unit[forbid-literal-units]{m/s}\\ % raise an error
+\unit[forbid-literal-units]{\meter\per\second}
+
+\subsubsection{unit-font-command} % todo font
+\unit{\lumen} \\
+\unit[unit-font-command = \mathit]{\lumen}
+
+\subsection{Quantities}
+\subsubsection{allow-quantity-breaks}
+\begin{minipage}{3cm}
+% Gives an underfull hbox
+X\hspace{2.4cm}\qty{10}{\metre} \\
+X\hspace{2.1cm}\qty{10}{\metre} \\
+\sisetup{allow-quantity-breaks}
+X\hspace{2.4cm}\qty{10}{\metre} \\ % disagrees with texdoc? % todo spacing
+X\hspace{2.1cm}\qty{10}{\metre} % todo spacing
+\end{minipage}
+
+\subsubsection{quantity-product} % todo spacing
+\qty{2.67}{\farad} \\
+\qty[quantity-product = \ ]{2.67}{\farad} \\
+\qty[quantity-product = ]{2.67}{\farad}\\
+\qty[quantity-product = \,]{2.67}{\farad}\\
+\qty[quantity-product = \;]{2.67}{\farad}\\
+\qty[quantity-product = $\times$]{2.67}{\farad}\\
+\qty[color=blue,quantity-product = $\times$]{2.67}{\farad}\\
+also\\
+\qty[quantity-product = $\times$, separate-uncertainty]{3.7+-4.5}{\farad}
+
+
+\subsubsection{prefix-mode, extract-mass-in-kilograms}
+\qty{1e3}{\metre\second} \\
+\qty[prefix-mode = combine-exponent]{1e3}{\metre\second} \\
+\qty{10}{\kilo\gram\squared\deci\second} \\
+\qty[prefix-mode = extract-exponent]{10}{\kilo\gram\squared\deci\second}\\
+\qty[prefix-mode = extract-exponent]{7.5}{\gram} \\
+{
+\sisetup{extract-mass-in-kilograms = false}
+\qty{10}{\kilo\gram\squared\deci\second} \\
+\qty[prefix-mode = extract-exponent]{10}{\kilo\gram\squared\deci\second} \\
+\qty[prefix-mode = extract-exponent]{7.5}{\gram}
+}
-\begin{table}[H]
-\caption{Controlling complex alignment with the tablenum macro.}
-\label{tab:tablenum}
-\centering
-\begin{tabular}{lr}
-\toprule
-Heading & Heading \\
-\midrule
-Info & More info \\
-Info & More info \\
-\multicolumn{2}{c}{\tablenum[table-format = 4.4]{12,34}} \\
-\multicolumn{2}{c}{\tablenum[table-format = 4.4]{333.5567}} \\
-\multicolumn{2}{c}{\tablenum[table-format = 4.4]{4563.21}} \\
-\bottomrule
-\end{tabular}
-\hfil
-\begin{tabular}{lr}
-\toprule
-Heading & Heading \\
-\midrule
-\multirow{2}*{\tablenum{88,999}} & aaa \\
-& bbb \\
-\multirow{2}*{\tablenum{33,435}} & ccc \\
-& ddd \\
-\bottomrule
-\end{tabular}
-\end{table}
+\unit{\milli\litre\per\mole\deci\ampere} \\
+%%\qty{10}{\kilo\gram\squared\deci\second} \\
+\unit[prefix-mode = extract-exponent]{\milli\litre\per\mole\deci\ampere}\\
+\unit[prefix-mode = extract-exponent]{\kilo\gram\squared\deci\second}\\
+\unit{\mega\gram\squared\deci\second}\\
+\unit[prefix-mode = extract-exponent]{\mega\gram\squared\deci\second}\\
+\unit{\micro\gram\squared\deci\second}\\
+\unit[prefix-mode = extract-exponent]{\micro\gram\squared\deci\second}\\
+\unit{\per\mega\gram\squared\deci\second}\\
+\unit[prefix-mode = extract-exponent]{\per\mega\gram\squared\deci\second}\\
+\unit{\per\micro\gram\squared\deci\second}\\
+\unit[prefix-mode = extract-exponent]{\per\micro\gram\squared\deci\second}
+%%\qty[prefix-mode = extract-exponent]{10}{\kilo\gram\squared\deci\second}\\
+
+\subsubsection{separate-uncertainty-units}
+{
+\sisetup{uncertainty-mode = separate}%
+\qty{12.3(4)}{\kilo\gram} \\
+\qty[separate-uncertainty-units = bracket]{12.3(4)}{\kilo\gram} \\
+\qty[separate-uncertainty-units = repeat]{12.3(4)}{\kilo\gram}\\
+\qty[separate-uncertainty-units = single]{12.3(4)}{\kilo\gram}
+}
-\begin{table}[H]
-\centering
-\caption{Units in tables.}
-\label{tab:s:demo}
-\begin{tabular}{s}
-\toprule
-\multicolumn{1}{c}{Unit} \\
-\midrule
-\metre\squared\per\second \\
-\pascal \\
-m.s^{-1} \\
-\bottomrule
-\end{tabular}
-\end{table}
-
-\begin{table}[H]
-\centering
-\caption{The \texttt{s} column processes everything.}
-\label{tab:s:processing}
-\sisetup{color = orange}
-\begin{tabular}{ss}
-\toprule
-{Unit}
-& \multicolumn{1}{c}{Unit}\\
-\midrule
-{\si{m^3}} & \multicolumn{1}{c}{\si{m^3}} \\
-\kilogram
-& \kilogram \\
-\bottomrule
-\end{tabular}
-\end{table}
-
-\subsubsection{table-parse-only}
-\begin{table}[H]
-\centering
-\caption{Parsing without aligning in an \texttt{S} column.}
-\label{tab:S:parse}
-\begin{tabular}{SS[table-parse-only]}
+\subsection{Tabular Material}
+
+\subsubsection{table-alignment-mode}
+
+Parsing without aligning in an \texttt{S} column
+\begin{center}
+\begin{tabular}
+{@{}S
+S[table-alignment-mode = none]
+@{}}
\toprule
-{Decimal-centred} &
-{Simple centring} \\
+{Decimal-centered} &
+{Simple centering} \\
\midrule
12.345 & 12.345 \\
-6,78 & 6,78 \\
+6,78 & 6,78 \\
-88.8(9) & -88.8(9) \\
-4.5e3 & 4.5e3 \\
+4.5e3 & 4.5e3 \\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
\subsubsection{table-number-alignment}
-\begin{table}[H]
-\caption{Aligning the \texttt{S} column.}
-\label{tab:S:align}
-\centering
-\sisetup{
-table-figures-integer = 2,
-table-figures-decimal = 4
-}
-\begin{tabular}{
-S
+
+Aligning the \texttt{S} column
+\begin{center}
+\sisetup{table-format = 2.4, table-alignment-mode = format}
+\begin{tabular}{@{}
+S[table-alignment-mode = marker]
S[table-number-alignment = center]
S[table-number-alignment = left]
S[table-number-alignment = right]
-}
+@{}}
\toprule
{Some Values} & {Some Values} & {Some Values} & {Some Values} \\
\midrule
@@ -992,130 +1679,48 @@ \subsubsection{table-number-alignment}
90.473 & 90.473 & 90.473 & 90.473\\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
-\subsubsection{table-figures-decimal, table-figures-exponent,table-figures-integer,table-figures-
-uncertainty}
-
-\begin{table}[H]
-\caption{Reserving space in \texttt{S} columns.}
-\label{tab:S:space}
-\sisetup{
-table-number-alignment = center,
-table-figures-integer = 2
-}
-\centering
-\begin{tabular}{
-S
-S[table-number-alignment = right]
-S[table-figures-uncertainty = 1]
-S[separate-uncertainty, table-figures-uncertainty = 1]
-S[table-sign-mantissa]
-S[table-figures-exponent = 1]
-}
-\toprule
-{Values}
-& {Values}
-& {Values}
-& {Values}
-& {Values}
-& {Values} \\
-\midrule
-2.3 & 2.3 & 2.3(5) & 2.3(5) & 2.3 & 2.3e8\\
-34.23 & 34.23 & 34.23(4) & 34.23(4) & 34.23 & 34.23\\
-56.78 & 56.78 & 56.78(3) & 56.78(3) & -56.78 & 56.78e3\\
-3,76 & 3,76 & 3,76(2) & 3.76(2) & +-3.76 & e6\\
-\bottomrule
-\end{tabular}
-\end{table}
+\subsubsection{table-format}
-\subsubsection{table-comparator}
-\begin{table}[H]
-\caption{Reserving space for comparators in \texttt{S} columns.}
-\label{tab:S:comparators}
+Reserving space in \texttt{S} columns
+\begin{center}
\sisetup{
+table-alignment-mode = format,
table-number-alignment = center,
-table-figures-integer = 2,
-table-figures-decimal = 2,
-table-figures-exponent = 2,
}
-\centering
-\begin{tabular}{
-S
-S[table-comparator = true]}
-\toprule
-{Values}
-& {Values} \\
-\midrule
-2 .3 & < 2.3e8\\
-34.23 & = 34.23 \\
-56.78 & >= 56.78e3\\
-3,76 & \gg e6 \\
-\bottomrule
-\end{tabular}
-\end{table}
-
-\subsubsection{table-format}
-\begin{table}[H]
-\caption{Using the \texttt{table-format} option.}
-\label{tab:S:format}
-\centering
-\begin{tabular}{
-S
+\begin{tabular}{@{}
S[table-format = 2.2]
+S[table-format = 2.2, table-number-alignment = right]
S[table-format = 2.2(1)]
+S[table-format = 2.2(1), uncertainty-mode = separate]
S[table-format = +2.2]
S[table-format = 2.2e1]
-}
+@{}}
\toprule
{Values}
& {Values}
& {Values}
& {Values}
+& {Values}
& {Values} \\
\midrule
-2.3 & 2.3 & 2.3(5) & 2.3 & 2.3e8 \\
-34.23 & 34.23 & 34.23(4) & 34.23 & 34.23 \\
-56.78 & 56.78 & 56.78(3) & -56.78 & 56.78e3 \\
-3,76 & 3,76 & 3.76(2) & +-3.76 & e6 \\
+2.3 & 2.3 & 2.3(5) & 2.3(5) & 2.3 & 2.3e8 \\
+34.23 & 34.23 & 34.23(4) & 34.23(4) & 34.23 & 34.23 \\
+56.78 & 56.78 & 56.78(3) & 56.78(3) & -56.78 & 56.78e3 \\
+3,76 & 3,76 & 3,76(2) & 3.76(2) & +-3.76 & e6 \\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
+\subsubsection{table-model-setup}
-\subsubsection{table-space-text-pre, table-space-text-post}
-\begin{table}[H]
-\caption{Text before and after numbers.}
-\label{tab:S:ends}
-\centering
-\sisetup{
-table-number-alignment = center,
-table-figures-integer = 2,
-table-figures-decimal = 4,
-table-space-text-pre
-= now~,
-table-space-text-post =
-\textsuperscript{\emph{a}}
-}
-\begin{tabular}{S}
-\toprule
-{Values} \\
-\midrule
-2.3456 \\
-34.2345 \textsuperscript{\emph{a}}\\
-56.7835 \\
-now~ 90.473 \\
-\bottomrule
-\end{tabular}
-\end{table}
-
-\subsubsection{table-align-comparator, table-align-exponent, table-align-uncertainty}
-\begin{table}[H]
-\centering
-\caption{The \texttt{table-align-exponent} option}
-\label{tab:align:exp}
-\sisetup{table-format = 1.3e2, table-number-alignment = center}
-\begin{tabular}{SS[table-align-exponent = false]}
+\subsubsection{table-align-[comparator\textbar exponent\textbar uncertainty]}
+
+The \texttt{table-align-exponent} option
+\begin{center}
+\sisetup{table-format = 1.3e2}
+\begin{tabular}{@{}SS[table-align-exponent = false]@{}}
\toprule
{Header} & {Header} \\
\midrule
@@ -1123,17 +1728,15 @@ \subsubsection{table-align-comparator, table-align-exponent, table-align-uncerta
1.234e56 & 1.234e56 \\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
-\begin{table}[H]
-\centering
-\caption{The \texttt{table-align-uncertainty} option}
-\label{tab:align:uncert}
+The \texttt{table-align-uncertainty} option
+\begin{center}
\sisetup{
-separate-uncertainty,
+uncertainty-mode = separate,
table-format = 1.3(1),
}
-\begin{tabular}{SS[table-align-uncertainty = false]}
+\begin{tabular}{@{}SS[table-align-uncertainty = false]@{}}
\toprule
{Header} & {Header} \\
\midrule
@@ -1141,14 +1744,12 @@ \subsubsection{table-align-comparator, table-align-exponent, table-align-uncerta
1.234(5) & 1.234(5) \\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
-\begin{table}[H]
-\centering
-\caption{The \texttt{table-align-comparator} option}
-\label{tab:align:comp}
+The \texttt{table-align-comparator} option
+\begin{center}
\sisetup{table-format = >2.2}
-\begin{tabular}{SS[table-align-comparator = false]}
+\begin{tabular}{@{}SS[table-align-comparator = false]@{}}
\toprule
{Header} & {Header} \\
\midrule
@@ -1156,103 +1757,75 @@ \subsubsection{table-align-comparator, table-align-exponent, table-align-uncerta
< 12.34 & < 12.34 \\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
-\subsubsection{table-omit-exponent}
-\begin{table}[H]
-\centering
-\caption{The \texttt{table-omit-exponent} option}
-\label{tab:exp:omit}
-\begin{tabular}{
-S[table-format = 1.1e1]
-S[fixed-exponent = 3, table-format = 2.1, table-omit-exponent]
+Reserving space for comparators in \texttt{S} columns
+\begin{center}
+\sisetup{
+table-number-alignment = center,
+table-format=<<2.2e2,
}
+\begin{tabular}{
+S
+S%[table-comparator = true]
+S[table-align-comparator=false]}
\toprule
-{Header} & {Header / \num{e3}} \\
+{Values} & {Values} & {Values} \\
\midrule
-1.2e3 & 1.2e3 \\
-3e2 & 3e2 \\
-1.0e4 & 1.0e4 \\
+2 .3 & < 2.3e8 & < 2.3e8\\
+34.23 & = 34.23 & = 34.23 \\
+56.78 & >= 56.78e3 & >= 56.78e3\\
+3,76 & \gg e6 & \gg e6 \\
\bottomrule
\end{tabular}
-\end{table}
-
-\subsubsection{table-align-text-pre,table-align-text-post}
-% \begin{table}[H]
-% \caption{Closing notes up to text.}
-% \label{tab:S:notes}
-% \newrobustcmd\NoteMark[1]{%
-% \textsuperscript{\emph{#1}}%
-% }
-% \centering
-% \sisetup{
-% table-number-alignment = center,
-% table-figures-integer = 2,
-% table-figures-decimal = 4,
-% table-space-text-pre
-% = \NoteMark{a}
-% }
-% \begin{tabular}{
-% S
-% S[table-align-text-pre = false]
-% }
-% \toprule
-% {Values}
-% & {Values} \\
-% \midrule
-% 2.3456 &
-% 2.3456 \\
-% \NoteMark{a} 4.234 & \NoteMark{a} 4.234 \\
-% \NoteMark{b}
-% .78
-% & \NoteMark{b}
-% .78 \\
-% \NoteMark{d} 88
-% & \NoteMark{d} 88
-% \\
-% \bottomrule
-% \end{tabular}
-% \hfil
-% \sisetup{table-space-text-post = \NoteMark{a}}
-% \begin{tabular}{
-% S
-% S[table-align-text-post = false]
-% }
-% \toprule
-% {Values}
-% & {Values} \\
-% \midrule
-% 2.3456
-% & 2.3456 \\
-% 34.234 \NoteMark{a} & 34.234 \NoteMark{a} \\
-% 56.78
-% \NoteMark{b} & 56.78 \NoteMark{b} \\
-% 90.4
-% \NoteMark{c} & 90.4
-% \NoteMark{c} \\
-% 88
-% \NoteMark{d} & 88
-% \NoteMark{d} \\
-% \bottomrule
-% \end{tabular}
-% \end{table}
+\end{center}
-\subsubsection{table-auto-round}
-\begin{table}[H]
-\centering
-\caption{The \texttt{table-auto-round} option.}
-\label{tab:S:auto}
-\sisetup{
-table-number-alignment = center,
-table-figures-integer = 1,
-table-figures-decimal = 3
+\subsubsection{table-align-text-[before\textbar after]}
+
+Closing notes up to text
+\begin{center}
+\newrobustcmd\NoteMark[1]{%
+\textsuperscript{\emph{#1}}%
}
-% Notice the overfull hbox which results with
-% the first column
-\begin{tabular}{
+\sisetup{table-format = {\NoteMark{a}}2.4}
+\begin{tabular}{@{}
S
-S[table-auto-round]
-}
+S[table-align-text-before = false]
+@{}}
+\toprule
+{Values} & {Values} \\
+\midrule
+2.3456 & 2.3456 \\
+\NoteMark{a} 4.234 & \NoteMark{a} 4.234 \\
+\NoteMark{b} .78 & \NoteMark{b} .78 \\
+\NoteMark{d} 88 & \NoteMark{d} 88 \\
+\bottomrule
+\end{tabular}
+\qquad
+\sisetup{table-format = 2.4{\NoteMark{a}}}
+%\sisetup{table-format = 2.4\NoteMark{a}} % todo error
+\begin{tabular}{@{}
+S
+S[table-align-text-after = false]
+@{}}
+\toprule
+{Values} & {Values} \\
+\midrule
+2.3456 & 2.3456 \\
+34.234 \NoteMark{a} & 34.234 \NoteMark{a} \\
+56.78 \NoteMark{b} & 56.78 \NoteMark{b} \\
+90.4 \NoteMark{c} & 90.4 \NoteMark{c} \\
+88 \NoteMark{d} & 88 \NoteMark{d} \\
+\bottomrule
+\end{tabular}
+\end{center}
+
+\subsubsection{table-auto-round}
+
+The \texttt{table-auto-round} option
+\begin{center}
+\sisetup{table-format = 1.3}
+\begin{tabular}{@{}SS[table-auto-round]@{}}
\toprule
{Header} & {Header} \\
\midrule
@@ -1260,59 +1833,104 @@ \subsubsection{table-auto-round}
1.2345 & 1.2345 \\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
\subsubsection{parse-numbers}
-\begin{table}[H]
-\caption{Aligning without parsing.}
-\label{tab:S:nonparsed}
+
+Aligning without parsing
+\begin{center}
\sisetup{
parse-numbers = false,
-table-figures-integer = 2,
-table-figures-decimal = 3
+table-format = 3.3
}
-\centering
-\begin{tabular}{
+\begin{tabular}{@{}
S
S[table-number-alignment = center]
S[table-number-alignment = right]
S[table-number-alignment = left]
-}
+@{}}
\toprule
{Some values}
& {Some values}
& {Some values}
& {Some values} \\
\midrule
-2.35 &
-2.35 &
-2.35 &
-2.35 \\
-34.234 &
-34.234 &
-34.234 & 34.234 \\
-56.783 &
-56.783 &
-56.783 & 56.783 \\
-3,762 &
-3,762 &
-3,762 &
-3.762 \\
+2.35 & 2.35 & 2.35 & 2.35 \\
+34.234 & 34.234 & 34.234 & 34.234 \\
+56.783 & 56.783 & 56.783 & 56.783 \\
+3,762 & 3,762 & 3,762 & 3.762 \\
\sqrt{2} & \sqrt{2} & \sqrt{2} & \sqrt{2} \\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
+
+\subsubsection{drop-exponent}
+
+The \texttt{drop-exponent} option
+\begin{center}
+\begin{tabular}{@{}
+S[table-format = 1.1e1]
+S[
+drop-exponent = true,
+exponent-mode = fixed,
+fixed-exponent = 3,
+table-format = 2.1,
+]
+@{}}
+\toprule
+{Header} & \multicolumn{1}{c@{}}{Header / \num[print-unity-mantissa = false]{e3}} \\
+\midrule
+1.2e3 & 1.2e3 \\
+3e2 & 3e2 \\
+1.0e4 & 1.0e4 \\
+\bottomrule
+\end{tabular}
+\end{center}
+
+\subsubsection{table-column-width, table-fixed-width}
+
+Fixed-width columns
+\begin{center}
+\begin{tabular}
+{@{}
+S
+S[table-column-width = 2cm]
+@{}}
+\toprule
+{Flexible} &
+{Fixed} \\
+\midrule
+1.23 & 1.23 \\
+45.6 & 45.6 \\
+\bottomrule
+\end{tabular}
+\end{center}
-\subsubsection{table-text-alignment}
-\begin{table}[H]
-\caption{Aligning text in \texttt{S} columns.}
-\label{tab:S:text}
+Right-aligning under a heading
+\begin{center}
+\newlength\mylength
+\settowidth{\mylength}{Long header}
\sisetup{
-table-number-alignment = center,
-table-figures-integer = 4,
-table-figures-decimal = 4
+table-alignment-mode = none ,
+table-column-width = \mylength ,
+table-number-alignment = right
}
-\centering
+\begin{tabular}{@{}S@{}}
+\toprule
+{Long header} \\
+\midrule
+12.33 \\
+2 \\
+1234 \\
+\bottomrule
+\end{tabular}
+\end{center}
+
+\subsubsection{table-text-alignment, table-alignment}
+
+Aligning text in \texttt{S} columns
+\begin{center}
+\sisetup{table-format = 4.4}
\begin{tabular}{
S
S[table-text-alignment = left]
@@ -1325,257 +1943,153 @@ \subsubsection{table-text-alignment}
\midrule
992.435 & 992.435 & 992.435 \\
7734.2344 & 7734.2344 & 7734.2344 \\
-56.7834 &
-56.7834 &
-56.7834 \\
-3,7462 &
-3,7462 &
-3,7462 \\
-\bottomrule
-\end{tabular}
-\end{table}
-
-\subsubsection{table-unit-alignment}
-\begin{table}[H]
-\centering
-\caption{Alignment options in \texttt{s} columns.}
-\label{tab:s:align}
-\begin{tabular}
-{
-s[table-unit-alignment = right]
-s
-s[table-unit-alignment = left]
-}
-\toprule
-{Right-aligned} &
-{Centred text} &
-{Left-aligned} \\
-\midrule
-\metre\per\second & \metre\per\second & \metre\per\second \\
-\kilogram
-& \kilogram
-& \kilogram
-\\
+56.7834 & 56.7834 & 56.7834 \\
+3,7462 & 3,7462 & 3,7462 \\
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
\subsubsection{table-alignment}
-\subsubsection{table-column-width}
-\begin{table}[H]
-\centering
-\caption{Fixed-width columns.}
-\label{tab:width:fixed}
-\begin{tabular}
+\subsection{Locale Options}
+\qty{1.234}{\metre}\\
+\qty[locale = DE]{6.789}{\metre} % todo spacing
+
+\subsection{Preamble-only options}
+These are in a different file. We check that trying to change the options has no effect here:
+
{
-s
-s[table-column-width = 2 cm]
-S
-S[table-column-width = 2 cm]
+\sisetup{
+ list-input-separator=:,
+ product-input-separator=*,
+ table-column-type=T
}
+\numlist{1;2;3} \\
+\numproduct{1x2x3} \\
+Table check:\\
+\begin{tabular}
+{@{}S
+S[table-alignment-mode = none]
+@{}}
\toprule
-{Flexible} &
-{Fixed}
-&
-{Flexible} &
-{Fixed}
-\\
+{Decimal-centered} &
+{Simple centering} \\
\midrule
-\metre\per\second & \metre\per\second & 1.23 & 1.23 \\
-\kilogram\candela & \kilogram\candela & 45.6 & 45.6 \\
+12.345 & 12.345 \\
+6,78 & 6,78 \\
+-88.8(9) & -88.8(9) \\
+4.5e3 & 4.5e3 \\
\bottomrule
\end{tabular}
-\end{table}
-
-% \begin{table}[H]
-% \centering
-% \caption{Right-aligning under a heading.}
-% \label{tab:width:special}
-% \settowidth\mylength{Long header}
-% \sisetup{
-% table-format
-% = 4
-% ,
-% table-number-alignment = center
-% ,
-% table-column-width
-% = \mylength ,
-% input-decimal-markers =
-% ,
-% input-symbols
-% = .
-% ,
-% }
-% \begin{tabular}{S}
-% \toprule
-% {Long header} \\
-% \midrule
-% 12.33 \\
-% 2
-% \\
-% 1234
-% \\
-% \bottomrule
-% \end{tabular}
-% \end{table}
-
-\end{document}
-
-
-
-
-
-
-
-
-
-
-
-
-
-\si{kg.m.s^{-1}}\\
-\si{\kilogram\metre\per\second} \\
-\si[per-mode=symbol]{\kilogram\metre\per\second} \\
-\si[per-mode=symbol]{\kilogram\metre\per\ampere\per\second}
-
-\numlist{10;20;30}\\
-\SIlist{0.13;0.67;0.80}{\milli\metre} \\
-\numrange{10}{20}\\
-\SIrange{0.13}{0.67}{\milli\metre}\\
-
-
-\numlist{10;30;50;70}
-
-\numrange{10}{30}
-
-
-\si{kg.m/s^2} \\
-\si{g_{polymer}~mol_{cat}.s^{-1}}
-
-\si{\kilo\gram\metre\per\square\second}
-\si{\gram\per\cubic\centi\metre}
-\si{\square\volt\cubic\lumen\per\farad}
-\si{\metre\squared\per\gray\cubic\lux}
-\si{\henry\second}
-
-\SI[mode=text]{1.23}{J.mol^{-1}.K^{-1}}\\
-\SI{.23e7}{\candela}\\
-\SI[per-mode=symbol]{1.99}[\$]{\per\kilogram}\\
-\SI[per-mode=fraction]{1,345}{\coulomb\per\mole}\\
-
-\SIlist{10;30;45}{\metre}
-
-\si{\square\becquerel} \\
-\si{\joule\squared\per\lumen} \\
-\si{\cubic\lux\volt\tesla\cubed}
-
-\si{\henry\tothe{5}} \\
-\si{\raiseto{4.5}\radian}
-
-\si{\joule\per\mole\per\kelvin} \\
-\si{\joule\per\mole\kelvin} \\
-\si{\per\henry\tothe{5}} \\
-\si{\per\square\becquerel}
+}
-\si{\kilogram\of{metal}} \\
-\SI[qualifier-mode = brackets]
-{0.1}{\milli\mole\of{cat}\per\kilogram\of{prod}}
+% these are v2
+%\subsubsection{input-product, input-quotient}
+%\numproduct{1 x 2 x 3} \\
+%\numproduct[input-product=*]{4 * 5 * 6} \\
-\si[per-mode = fraction]
-{\cancel\kilogram\metre\per\cancel\kilogram\per\second} \\
-\si{\highlight{red}\kilogram\metre\per\second} \\
-\si[unit-color = purple]
-{\highlight{red}\kilogram\metre\per\second}
+%\section{Localisation}
+%This is in a different file.
+\setcounter{section}{9}
-\si{\kilo} \\
-\si{\micro} \\
-\si[prefixes-as-symbols = false]{\kilo}
+\section{Hints for using siunitx}
-\si{\kilo\gram\micro} \\
-\SI{10}{\micro}
+\setcounter{subsection}{1}
+\subsection{Adjusting \textbackslash litre and \textbackslash liter}
+{
+\DeclareSIUnit\litre{SUCCESS}
+\unit{\liter}
+}
+\setcounter{subsection}{4}
-\begin{table}
-\caption{Standard behaviour of the \texttt{S} column type.}
-\label{tab:S:standard}
-\centering
-\begin{tabular}{S}
-\toprule
-{Some Values} \\
-\midrule
-2.3456 \\
-34.2345 \\
--6.7835 \\
-90.473 \\
-5642.5
-\\
-1.2e3 \\
-e4 \\
-\bottomrule
-\end{tabular}
-\end{table}
+\subsection{Expanding content in tables}
-\begin{table}
-\caption{Detection of surrounding material in an \texttt{S}
-column.}
-\label{tab:S:extras}
-\centering
-\begin{tabular}{S[color=orange]}
+Values as macros in \texttt{S} columns
+\begin{center}
+\newcommand*\myvaluea{1234}
+\newcommand\myvalueb{1234}
+\DeclareRobustCommand*\myvaluec{1234}
+\protected\def\myvalued{1234}
+\begin{tabular}{@{}S@{}}
\toprule
{Some Values} \\
\midrule
-12.34 \\
-\color{purple} 975,31 \\
-44.268 \textsuperscript{\emph{a}} \\
+\myvaluea 8.8 \myvaluea \\ % Both expanded
+\myvalueb 8.8 \myvalueb \\ % First expanded by TeX
+% to numbers
+%\myvaluec 8.8 \myvaluec \\ % First expanded by TeX % todo error caused on next line
+% but not to numbers!
+\myvalued 8.8 \myvalued \\ % Neither expanded % todo output overlaps
+{\myvaluea\ 8.8 \myvaluea} \\ % Neither expanded
\bottomrule
\end{tabular}
-\end{table}
+\end{center}
+
+% \usepackage{xfp}
+%Calculated values
+%\begin{center}
+%\newcommand{\valuea}{66.7012}
+%\newcommand{\valueb}{66.0212}
+%\newcommand{\valuec}{64.9026}
+%\begin{tabular}{
+%@{}
+%S[table-format = 2.4]
+%S[table-format = 3.4]
+%@{}
+%}
+%\toprule
+%{Value} & {Doubled} \\
+%\midrule
+%\valuea & \fpeval{\valuea * 2} \\
+%\valueb & \fpeval{\valueb * 2} \\
+%\valuec & \fpeval{\valuec * 2} \\
+%\bottomrule
+%\end{tabular}
+%\end{center}
+
+\setcounter{subsection}{10}
+
+\subsection{Special considerations for the \texorpdfstring{\Backslash}{\textbackslash}\unit{\kWh} unit}
+
+\unit{\kWh} \\
+\unit{\kWh\per\metre}\\
+{
+\DeclareSIUnit\kWh{kWh}
+\DeclareSIUnit\KWH{kWh}
+\unit{\KWH\per\metre}\\
+\unit{kWh.m^{-1}}\\
+}
+\unit{\candela\per\kWh}\\
+\unit{\candela\per\kilo\watt\per\hour} \\
+\unit[sticky-per]{\candela\per\kWh}
-\begin{table}
-\caption{Controlling complex alignment with the tablenum macro.}
-\label{tab:tablenum}
-\centering
-\begin{tabular}{lr}
-\toprule
-Heading & Heading \\
-\midrule
-Info & More info \\
-Info & More info \\
-\multicolumn{2}{c}{\tablenum[table-format = 4.4]{12,34}}
-\\
-\multicolumn{2}{c}{\tablenum[table-format = 4.4]{333.5567}} \\
-\multicolumn{2}{c}{\tablenum[table-format = 4.4]{4563.21}}
-\bottomrule
-\end{tabular}
-\hfil
-\begin{tabular}{lr}
-\toprule
-Heading & Heading \\
-\midrule
-\multirow{2}*{\tablenum{88,999}} & aaa \\
-& bbb \\
-\multirow{2}*{\tablenum{33,435}} & ccc \\
-& ddd \\
-\bottomrule
-\end{tabular}
-\end{table}
+\subsection{Prefixes and small angles}
-\begin{table}
-\centering
-\caption{Units in tables.}
-\label{tab:s:demo}
-\begin{tabular}{s}
-\toprule
-\multicolumn{1}{c}{Unit} \\
-\midrule
-\metre\squared\per\second \\
-\pascal \\
-m.s^{-1} \\
-\bottomrule
-\end{tabular}
-\end{table}
+{
+\DeclareSIUnit\arcsecond{as}
+\qty{1e-3}{\arcsecond} \\
+\qty[prefix-mode = combine-exponent]{1e-3}{\arcsecond}
+}
+%\setcounter{subsection}{15}
+%
+%\subsubsection{Symbolic `digits'}
+%
+%{
+%\sisetup{input-digits = 0123456789\pi}%
+%\num{4\pi e-7}\\ % fails
+%\robustify\dots % fails
+%\sisetup{input-digits = 0123456789\dots}%
+%\qty{0,4066\dots}{\metre\squared}
+%}
+
+\setcounter{subsection}{16}
+
+\subsection{Demonstrating prefixes}
+\unit{\yotta\noop} \\
+\qty[prefix-mode = extract-exponent, print-unity-mantissa = false]{1}{\yotta\noop} \\
+a\unit{\noop}b
\end{document}
diff --git a/t/complex/si.xml b/t/complex/si.xml
index 6fe21e31c..d249edbf5 100644
--- a/t/complex/si.xml
+++ b/t/complex/si.xml
@@ -1,9022 +1,15143 @@
+
-
-
-
+
+
-
- Unsemantic:
- Semantic again:
- Unsemantic:
+ Semantic again:
+
Pretty nonsensical stuff?
- Pretty nonsensical stuff?
+
The SI base units: The coherent derived units: The non-SI units: SI prefixes:
+
+
+
+ Unit
+ Macro
+ Symbol
+
+
+ ampere
+
+
+
+
+ candela
+
+
+
+
+ kelvin
+
+
+
+
+ kilogram
+
+
+
+
+ metre
+
+
+
+
+ mole
+
+
+
+
+
+ second
+
+
+
+
+
+
+ Unit
+ Macro
+ Symb
+ Unit
+ Macro
+ Symb
+
+
+ becquerel
+
+
+ newton
+
+
+
+
+ degreeCelsius
+
+
+ ohm
+
+
+
+
+ coulomb
+
+
+ pascal
+
+
+
+
+ farad
+
+
+ radian
+
+
+
+
+ gray
+
+
+ siemens
+
+
+
+
+ hertz
+
+
+ sievert
+
+
+
+
+ henry
+
+
+ steradian
+
+
+
+
+ joule
+
+
+ tesla
+
+
+
+
+ katal
+
+
+ volt
+
+
+
+
+ lumen
+
+
+ watt
+
+
+
+
+
+ lux
+
+
+ weber
+
+
+
+
+
+
+ Unit
+ Macro
+ Symbol
+
+
+ astronomicalunit
+
+
+
+
+ bel
+
+
+
+
+ dalton
+
+
+
+
+ day
+
+
+
+
+ decibel
+
+
+
+
+ degree
+
+
+
+
+ electronvolt
+
+
+
+
+ hectare
+
+
+
+
+ hour
+
+
+
+
+ litre
+
+
+
+
+ liter
+
+
+
+
+ arcminute
+
+
+
+
+ minute
+
+
+
+
+ arcsecond
+
+
+
+
+ neper
+
+
+
+
+
+ tonne
+
+
+
+
+
+
+ Prefix
+ Cmd
+ Symb
+ Power
+ Prefix
+ Cmd
+ Symb
+ Power
+
+
+ quecto
+
+
+
+ deca
+
+
+
+
+
+ ronto
+
+
+
+ hecto
+
+
+
+
+
+ yocto
+
+
+
+ kilo
+
+
+
+
+
+ zepto
+
+
+
+ mega
+
+
+
+
+
+ atto
+
+
+
+ giga
+
+
+
+
+
+ femto
+
+
+
+ tera
+
+
+
+
+
+ pico
+
+
+
+ peta
+
+
+
+
+
+ nano
+
+
+
+ exa
+
+
+
+
+
+ micro
+
+
+
+ zetta
+
+
+
+
+
+ milli
+
+
+
+ yotta
+
+
+
+
+
+ centi
+
+
+
+ ronna
+
+
+
+
+
+
+ deci
+
+
+
+ quetta
+
+
+
+
Abbreviated units
+Binary prefixes:
+Standard behaviour of the
Detection of surrounding material in an
Text before and after numbers
+Controlling complex alignment with the tablenum macro
+Also:
This is unique to version 3.
+also:
a
| Unit | -Macro | -Symbol | -
| ampere | -||
| candela | -||
| kelvin | -||
| kilogram | -||
| metre | -||
| mole | -||
| second | -
| Unit | -Macro | -Symbol | -Unit | -Macro | -Symbol | -
| becquerel | -newton | -||||
| degreeCelsius | -ohm | -||||
| coulomb | -pascal | -||||
| farad | -radian | -||||
| gray | -siemens | -||||
| hertz | -sievert | -||||
| henry | -steradian | -||||
| joule | -tesla | -||||
| katal | -volt | -||||
| lumen | -watt | -||||
| lux | -weber | -
| Unit | -Macro | -Symbol | -
| day | -||
| degree | -||
| hectare | -||
| hour | -||
| litre | -||
| liter | -||
| arcminute | -||
| minute | -||
| arcsecond | -||
| tonne | -
| Unit | -Macro | -Symbol | -
| astronomicalunit | -||
| atomicmassunit | -||
| bohr | -||
| clight | -||
| dalton | -||
| electronmass | -||
| electronvolt | -||
| elementarycharge | -||
| hartree | -||
| planckbar | -
| Unit | -Macro | -Symbol | -
| angstrom | -||
| bar | -||
| barn | -||
| bel | -||
| decibel | -||
| knot | -||
| mmHg | -||
| nauticalmile | -||
| neper | -
| Unit | -Macro | -Symbol | -Power | -Unit | -Macro | -Symbol | -Power | -
| yocto | -deca | -||||||
| zepto | -hecto | -||||||
| atto | -kilo | -||||||
| femto | -mega | -||||||
| pico | -giga | -||||||
| nano | -tera | -||||||
| micro | -peta | -||||||
| milli | -exa | -||||||
| centi | -zetta | -||||||
| deci | -yotta | -
| Unit | -Macro | -Symbol | -
| fg | -||
| pg | -||
| ng | -||
| ug | -||
| mg | -||
| g | -||
| kg | -||
| amu | -||
| pm | -||
| nm | -||
| um | -||
| mm | -||
| cm | -||
| dm | -||
| m | -||
| km | -||
| as | -||
| fs | -||
| ps | -||
| ns | -||
| us | -||
| ms | -||
| s | -||
| fmol | -||
| pmol | -||
| nmol | -||
| umol | -||
| mmol | -||
| mol | -||
| kmol | -||
| pA | -||
| nA | -||
| uA | -||
| mA | -||
| A | -||
| kA | -||
| ul | -||
| ml | -||
| l | -||
| hl | -||
| uL | -||
| mL | -||
| L | -||
| hL | -||
| mHz | -||
| Hz | -||
| kHz | -||
| MHz | -||
| GHz | -||
| THz | -||
| mN | -||
| N | -||
| kN | -||
| MN | -||
| Pa | -||
| kPa | -||
| MPa | -||
| GPa | -||
| mohm | -||
| kohm | -||
| Mohm | -||
| pV | -||
| nV | -||
| uV | -||
| mV | -||
| V | -||
| kV | -||
| W | -||
| uW | -||
| mW | -||
| kW | -||
| MW | -||
| GW | -||
| J | -||
| kJ | -||
| eV | -||
| meV | -||
| keV | -||
| MeV | -||
| GeV | -||
| TeV | -||
| kWh | -||
| F | -||
| fF | -||
| pF | -||
| K | -||
| dB | -
| Unit | -Macro | -Symbol | -Power | -
| kibi | -a |
- ||
| mebi | -|||
| gibi | -|||
| tebi | -|||
| pebi | -|||
| exbi | -|||
| zebi | -|||
| yobi | -
Real numbers as complex:
Other tests:
| Some Values | -
| Some Values | -
| Heading | -Heading | -
| Info | -More info | -
| Info | -More info | -
| Heading | -Heading | -
| aaa | -|
| bbb | -|
| ccc | -|
| ddd | -|
| Unit | -
| Unit | -|
| Decimal-centred | -Simple centring | -
| Some Values | -Some Values | -Some Values | -Some Values | -
| Values | -Values | -Values | -Values | -Values | -Values | -
| Values | -Values | -
| Values | -Values | -Values | -Values | -Values | -
| Values | -
| now 90.473 | -
| Header | -Header | -
X X X X |
- |
| Header | -Header | -
| Header | -Header | -
| Header | -Header / Parsing without aligning in an Decimal-centered |
+ Simple centering |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ Aligning the Some Values |
+ Some Values |
+ Some Values |
+ Some Values |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ Reserving space in Values |
+ Values |
+ Values |
+ Values |
+ Values |
+ Values |
+ |
+ |
+ |
- |
| Header | -Header | -
| Some values | -Some values | -Some values | -Some values | -|||||||||||||||||||||||||||||||
The Header |
+ Header |
+ |
+ |
+ |
+ |
+ The Header |
+ Header |
+ |
+ |
+ |
+ |
+ The Header |
+ Header |
+ |
+ |
+ |
+ |
+ Reserving space for comparators in Values |
+ Values |
+ Values |
+ |
+ |
+ |
- |
+ |
+ |
- |
+ |
- |
- |
- |
- |
- |
- |
| Values | -Values | -Values | -
| Values | +Values | +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Values | +Values | +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Right-aligned | -Centred text | -Left-aligned | -|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The Header |
+ Header |
+ |
+ |
+ |
+ |
+ Aligning without parsing +Some values |
+ Some values |
+ Some values |
+ Some values |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ The Header |
+ Header / |
+ |
+ |
+ |
+ |
+ |
+ |
+ Fixed-width columns +Flexible |
+ Fixed |
+ |
+ |
+ |
+ |
+ Right-aligning under a heading +Long header |
+ |
+ |
+ |
+ Aligning text in Values |
+ Values |
+ Values |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ These are in a different file. We check that trying to change the options has no effect here: +
|
| Flexible | -Fixed | -Flexible | -Fixed | +Some Values |
|
- |
- | ||
|
- | |||
| 1234 8.8 1234 |
Non-SI units whose values in SI units must be obtained experimentally +(that are deprecated in siunitx v3, but make sure we still have them):
+| Unit | +
| Input | +Threshold |
+ Threshold |
+
| Some Values | +Some Values | +Some Values | +Some Values | +