Skip to content

Public parser that exposes quantity components #864

@paulzuurbier

Description

@paulzuurbier

Summary

Add a public expl3 function that parses a quantity (number + unit) through the normal siunitx pipeline — including prefix extraction, rounding, scientific/engineering exponent handling, and the extract-exponent / extract-mass-in-kilograms rules — but instead of typesetting the result, returns the decomposed components (sign, mantissa, exponent, extracted prefix, base unit, …) as an expl3 property list. This lets downstream code consume any subset of the parsed data, without re-implementing siunitx's parsing rules.

Motivation

siunitx currently offers a rich key–value interface for how a quantity is typeset, but treats the parsed decomposition as an implementation detail. Several common use-cases need access to the individual fields:

  • Plot axis labels. When an axis is scaled by a power of ten, the author wants to put that same exponent (combined with any prefix extracted from the unit) in the label, while the tick labels show the unscaled numbers. Today this requires either double bookkeeping (letting siunitx format the label and separately teaching pgfplots about the exponent) or hand-rolled prefix arithmetic. A call that returns "after prefix extraction, exponent is 3 and the base unit is \metre" solves this cleanly.
  • Table alignment on the mantissa. Aligning a column on the decimal marker of the mantissa regardless of how the exponent is rendered currently relies on table-* keys. Direct access to mantissa-int / mantissa-dec / exponent would let authors build their own alignment, including in non-table contexts (aligned equations, matrices).
  • Calculations with parsed values. Packages that combine siunitx input with \fpeval/\int_eval typically re-parse the number themselves. Exposing the already-parsed numeric fields removes that duplication and guarantees consistent rules.
  • Conditional formatting. "Print the bare unit if mantissa = 1 and exponent = 0 after extraction, otherwise print the full quantity" is a recurring pattern (see §Concrete case below). A public parser makes this a two-line conditional instead of an internal-function override.

Concrete case (illustrative)

Pgfplots axis label with prefix-mode = extract-exponent, extract-mass-in-kilograms = true, input \qty{1e-3}{\km}:

  • Desired output when the net result reduces to mantissa 1 and exponent 0: just the base unit (e.g. m), no "1", no "10^0", no thin-space.
  • Current behaviour: print-unity-mantissa = false + print-zero-exponent = false still print 1 as a fallback (see \__siunitx_number_output_integer:nnn, siunitx.sty, around line 2319), because at that layer siunitx doesn't know there is still a unit to anchor the output.

A documented parser would allow:

\siunitx_quantity_parse:nnnN
  { prefix-mode = extract-exponent, extract-mass-in-kilograms = true }
  { 1e-3 } { \km } \l_tmpa_prop
\prop_get:NnN \l_tmpa_prop { mantissa-int } \l_tmpa_tl  % "1"
\prop_get:NnN \l_tmpa_prop { exponent }     \l_tmpb_tl  % "0"
\prop_get:NnN \l_tmpa_prop { unit }         \l_tmpc_tl  % "\metre"
% ... then decide locally how to typeset.

Feasibility

The decomposition already exists internally. \__siunitx_number_output:nnnnnnn (siunitx.sty, line 2235) receives seven separate fields — sign, color, integer, decimal, uncertainty, exponent, trailing — and calls field-specific output routines (\__siunitx_number_output_integer:nnn, …_decimal:nn, …_exponent:nnnnn, …_end:). Prefix extraction has likewise already run by the time these are called.

siunitx also already publishes a small expl3 layer that follows naming conventions consistent with this proposal:

  • \siunitx_if_number:n[TF] (siunitx.sty, line 2803)
  • \siunitx_number_parse:nN
  • \siunitx_number_output:N, \siunitx_number_output:n (lines 2215–2219)
  • \siunitx_quantity_product:nn (line 4798)
  • \siunitx_quantity_print:nn (line 8005 region)

So the request is not a new architecture — it is exposing data that the pipeline already has, through a function that matches the existing naming scheme.

Proposed API

\siunitx_quantity_parse:nnnN { <options> } { <number> } { <unit> } <prop var>

    Runs the full siunitx parsing pipeline (including prefix-mode,
    rounding, uncertainty, scientific / engineering / fixed
    exponent handling, extract-mass-in-kilograms, …) with the
    given options applied locally.  Stores the parsed fields as
    entries in <prop var>.  Does not typeset anything.

    Keys stored in <prop var>:
      sign            "+" | "-" | ""
      mantissa-int    integer part of the mantissa, e.g. "1", "", "0"
      mantissa-dec    decimal part of the mantissa, e.g. "25", ""
      uncertainty     uncertainty token list (format as siunitx emits)
      exponent        final exponent after prefix extraction, e.g. "3", "0", ""
      prefix          prefix token extracted from the unit, e.g. "\kilo", ""
      unit            base unit after prefix extraction, e.g. "\metre"
      unit-raw        the unit as written by the user (pre-extraction)

    Variant: ...parse:nnnNTF that also branches on "is this a valid
    quantity".

Companion public-layer additions that would round out the API:

  • \siunitx_quantity_typeset:N <prop var> — typeset from a property list produced by parse:nnnN. Lets the caller mutate fields between parse and typeset (e.g. force a specific exponent, swap a unit).
  • A quantity-hook key whose value is a callback \cs { ... } receiving the property list, invoked at the point in \qty/\SI where typesetting would begin. Same data path as parse:nnnN, but plugged into the existing user-level macros, so existing \qty[...]{}{} call sites can opt in.

Backwards compatibility

Pure addition. No existing key, macro, or internal function changes semantics; \qty, \num, \SI, \ang, the S column type all behave identically for users who do not call the new functions or set the new key.

Documentation placement

  • User-facing mention in siunitx.pdf under a new "Accessing parsed quantities" section, alongside the existing expl3 API listing.
  • Full signatures in siunitx-code.pdf, consistent with how \siunitx_number_parse:nN and \siunitx_quantity_product:nn are already documented there.

Closing

The change is a modest, additive exposure of data the package already computes. It would remove a recurring class of workaround — reparsing in user code, or overriding internal \__siunitx_* functions — and fits naturally into siunitx's existing expl3 public layer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions