Skip to main content

DisplayContext

Struct DisplayContext 

Source
pub struct DisplayContext { /* private fields */ }
Expand description

Display context for formatting numbers with consistent precision per currency.

Tracks a frequency distribution of decimal places per currency and exposes it via get_precision under the configured Precision policy. Default policy is Precision::MostCommon to match Python bean-query.

Fixed per-currency overrides (from option "display_precision") always win over inferred precision regardless of the policy.

Implementations§

Source§

impl DisplayContext

Source

pub fn new() -> Self

Create a new empty display context.

Source

pub fn update(&mut self, number: Decimal, currency: &str)

Update the display context with a number for a currency.

Records the decimal precision (number of digits after the decimal point) of number against currency’s histogram, so subsequent get_precision calls reflect the new sample under the active Precision policy.

Source

pub fn update_from(&mut self, other: &Self)

Update the display context from another display context.

  • Inferred per-currency distributions: merge histograms (sum counts across both sides). This preserves frequency information so the merged context’s mode reflects the union of samples — strictly more correct than the old “max of maxes” merge, and matches Python display_context.DisplayContext.update_from.
  • Fixed per-currency overrides (option "display_precision"): propagated from other only when self has no fixed override for that currency (so a per-context override stays authoritative).
  • render_commas: enabled if either side has it on (one-way “sticky on” merge — same rationale as before).
  • precision policy: NOT propagated. The policy is a property of the consumer (e.g. BQL renderer vs price-display formatter), not the data, so it stays as set on self.

The fixed-precision and render_commas merging matters when a column context inherits from a ledger context for Value::Number rendering: without it, the ledger’s display options would silently fail to apply to naked-decimal columns. See PR #961 follow-up.

Source

pub const fn set_precision(&mut self, precision: Precision)

Set the inference policy for Self::get_precision.

Default is Precision::MostCommon to match Python bean-query. Callers that need to preserve the highest-precision sample (e.g. price-display formatters) can opt into Precision::Maximum.

Source

pub const fn precision(&self) -> Precision

Get the active inference policy.

Source

pub fn currencies(&self) -> impl Iterator<Item = &str>

Iterate the currencies that have observed dp samples or fixed overrides, in deterministic-but-unspecified order.

Skips the __default__ sentinel — that bucket is for naked-decimal columns (BQL Value::Number) and isn’t a “real” currency from the user’s perspective.

Source

pub fn histogram(&self, currency: &str) -> Vec<(u32, u32)>

Return the dp histogram for currency as ascending (dp, count) pairs. Empty if the currency has no observed samples.

Useful for diagnostic / debugging tooling (e.g. rledger doctor display-context) that wants to show why a particular precision was chosen.

Source

pub fn precision_under(&self, currency: &str, policy: Precision) -> Option<u32>

Look up the precision that would be returned under a specific policy, without mutating self. Same semantics as Self::get_precision but lets a single context be queried under both policies (e.g. for diagnostic output that compares MostCommon vs Maximum).

Source

pub fn has_fixed_precision(&self, currency: &str) -> bool

True if currency has a fixed-precision override (from option "display_precision" or Self::set_fixed_precision).

Source

pub const fn set_render_commas(&mut self, render_commas: bool)

Set the render_commas flag.

Source

pub const fn render_commas(&self) -> bool

Get the render_commas flag.

Source

pub fn set_fixed_precision(&mut self, currency: &str, precision: u32)

Set a fixed precision for a currency (from option "display_precision").

Fixed precision takes precedence over inferred precision.

Source

pub fn get_precision(&self, currency: &str) -> Option<u32>

Get the precision for a currency.

Returns the fixed precision if set; otherwise looks up the inferred precision under the active Precision policy (MostCommon by default — the mode of the observed distribution; or Maximum — the highest observed dp). Returns None if the currency has never been seen.

Source

pub fn default_precision(&self) -> u32

Get the default precision used when formatting a Decimal that has no associated currency (e.g. the result of SUM(number) in BQL).

Resolution order (matches the BQL renderer’s expectations after PR #986):

  1. __default__ bucket — if any naked-decimal observations have been recorded via update(n, DEFAULT_CURRENCY), the bucket’s effective precision wins. This is what BQL populates for Value::Number columns (matches Python bean-query’s per-column DecimalRenderer).
  2. Max effective precision across every other currency — fallback when no naked-decimal observations exist. Covers issue #954: a column of Value::Number(0) that came from an aggregate collapsing to literal zero still renders with the column’s expected dp (e.g. 0.00 for a USD-only file).
  3. Returns 0 if no currencies have been recorded at all.

“Effective” precision means per-currency fixed overrides inferred (same rule as Self::get_precision) and respects the active Precision policy, so a fixed display_precision of 2 for USD won’t be overridden by an inferred 4-digit value.

Source

pub fn quantize(&self, number: Decimal, currency: &str) -> Decimal

Quantize a number to the tracked precision for a currency.

Mirrors Python’s Decimal.quantize: the result has exactly the target scale — rounding when the input has more dp, padding with trailing zeros when the input has fewer. This matches what bean-query’s AmountRenderer does: it quantizes via the ledger dctx before populating the column dctx, so the column dctx sees uniformly-padded values.

If the currency has no tracked precision, returns the number unchanged.

Pre-fix this used round_dp(dp), which only ROUNDS down — it never PADS up. That meant a 2dp input under a 4dp target stayed 2dp, the column dctx saw dp=2, and the output rendered 2dp instead of bean-query’s 4dp.

Source

pub fn format(&self, number: Decimal, currency: &str) -> String

Format a decimal number for a currency using the tracked precision.

Render rules (matching bean-query’s AmountRenderer.format):

  • If the value’s intrinsic scale exceeds the currency’s tracked precision, render at the value’s scale. Python’s decimal carries scale through arithmetic and bean-query preserves it, so a SUM(number) GROUP BY currency that aggregates a -805.50896 row and a -396.50000 row renders as -1202.00896 (scale=5), not -1202.01 (rounded to USD’s 2dp).
  • If the value’s scale is less than the tracked precision, pad with trailing zeros (7.5 USD7.50). Preserves the #954 fix that stops SUM(0.00) = 0 rendering as plain 0.
  • If the currency has no tracked precision, fall through to the value’s natural rendering with trailing zeros stripped.

The previous implementation always quantized to the tracked precision via round_dp(dp). That was correct for under-scale padding but wrong for over-scale truncation — it lost arithmetic precision that bean-query preserved (closes #1103).

Source

pub fn format_amount(&self, number: Decimal, currency: &str) -> String

Format an amount (number + currency) using the tracked precision.

Unlike Self::format (which preserves over-scale arithmetic precision to match Python bean-query’s DecimalRenderer for scalar Value::Number results), this method always quantizes to the currency’s tracked dp — matching bean-query’s AmountRenderer for Amounts, Positions, and Inventory entries.

Python uses two distinct renderers for the two semantic kinds of output:

  • DecimalRenderer for naked decimals (preserves scale, since Python decimal carries scale through arithmetic).
  • AmountRenderer for amount-typed values (uses the ledger’s display context per-currency dp, which is the user-facing “how many decimal places does this currency render at” setting).

Rust used to conflate the two through a single format call, which is why #1103’s fix (preserving scale in format) inadvertently regressed the BQL compat suite by ~7pp on queries that produce Value::Inventory — the position amounts inside the inventory now render with raw arithmetic scale instead of the currency’s display dp. See #1112 for the regression analysis.

Source

pub fn format_amount_number(&self, number: Decimal, currency: &str) -> String

Format the number portion of an Amount/Position (no currency suffix), quantized to the tracked dp.

Used by the BQL numberify rendering path that strips the currency from positions/inventories — same semantics as Self::format_amount but without the trailing <CURRENCY>.

Source

pub fn format_default(&self, number: Decimal) -> String

Format a Decimal that has no associated currency.

Used by the BQL query renderer for Value::Number results — bare Decimals produced by aggregates like SUM(number) or columns like cost_number.

Matches Python bean-query’s DecimalRenderer.format, which uses the value’s natural string representation (preserving the scale baked into the Decimal) without imposing uniform precision across rows. So Value::Number(Decimal('0.00')) renders 0.00 (scale survives — covers issue #954) while Value::Number(0) renders 0 (no artificial padding).

When the value has scale 0 (no fractional part) but the context has a __default__-bucket precision, we DO pad up to that precision — this is the issue #954 path: an aggregate that collapsed to literal zero (scale lost) still gets rendered with the column’s expected dp.

Trait Implementations§

Source§

impl Clone for DisplayContext

Source§

fn clone(&self) -> DisplayContext

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for DisplayContext

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for DisplayContext

Source§

fn default() -> DisplayContext

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> ArchivePointee for T

Source§

type ArchivedMetadata = ()

The archived version of the pointer metadata for this type.
Source§

fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata

Converts some archived metadata to the pointer metadata for itself.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> LayoutRaw for T

Source§

fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>

Returns the layout of the type.
Source§

impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
where T: SharedNiching<N1, N2>, N1: Niching<T>, N2: Niching<T>,

Source§

unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool

Returns whether the given value has been niched. Read more
Source§

fn resolve_niched(out: Place<NichedOption<T, N1>>)

Writes data to out indicating that a T is niched.
Source§

impl<T> Pointee for T

Source§

type Metadata = ()

The metadata type for pointers and references to this type.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.