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
impl DisplayContext
Sourcepub fn update(&mut self, number: Decimal, currency: &str)
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.
Sourcepub fn update_from(&mut self, other: &Self)
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 fromotheronly whenselfhas 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).precisionpolicy: 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 onself.
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.
Sourcepub const fn set_precision(&mut self, precision: Precision)
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.
Sourcepub fn currencies(&self) -> impl Iterator<Item = &str>
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.
Sourcepub fn histogram(&self, currency: &str) -> Vec<(u32, u32)>
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.
Sourcepub fn precision_under(&self, currency: &str, policy: Precision) -> Option<u32>
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).
Sourcepub fn has_fixed_precision(&self, currency: &str) -> bool
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).
Sourcepub const fn set_render_commas(&mut self, render_commas: bool)
pub const fn set_render_commas(&mut self, render_commas: bool)
Set the render_commas flag.
Sourcepub const fn render_commas(&self) -> bool
pub const fn render_commas(&self) -> bool
Get the render_commas flag.
Sourcepub fn set_fixed_precision(&mut self, currency: &str, precision: u32)
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.
Sourcepub fn get_precision(&self, currency: &str) -> Option<u32>
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.
Sourcepub fn default_precision(&self) -> u32
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):
__default__bucket — if any naked-decimal observations have been recorded viaupdate(n, DEFAULT_CURRENCY), the bucket’s effective precision wins. This is what BQL populates forValue::Numbercolumns (matches Pythonbean-query’s per-columnDecimalRenderer).- 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.00for a USD-only file). - 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.
Sourcepub fn quantize(&self, number: Decimal, currency: &str) -> Decimal
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.
Sourcepub fn format(&self, number: Decimal, currency: &str) -> String
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
decimalcarries scale through arithmetic and bean-query preserves it, so aSUM(number) GROUP BY currencythat aggregates a-805.50896row and a-396.50000row 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 USD→7.50). Preserves the #954 fix that stopsSUM(0.00) = 0rendering as plain0. - 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).
Sourcepub fn format_amount(&self, number: Decimal, currency: &str) -> String
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:
DecimalRendererfor naked decimals (preserves scale, since Pythondecimalcarries scale through arithmetic).AmountRendererfor 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.
Sourcepub fn format_amount_number(&self, number: Decimal, currency: &str) -> String
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>.
Sourcepub fn format_default(&self, number: Decimal) -> String
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
impl Clone for DisplayContext
Source§fn clone(&self) -> DisplayContext
fn clone(&self) -> DisplayContext
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for DisplayContext
impl Debug for DisplayContext
Source§impl Default for DisplayContext
impl Default for DisplayContext
Source§fn default() -> DisplayContext
fn default() -> DisplayContext
Auto Trait Implementations§
impl Freeze for DisplayContext
impl RefUnwindSafe for DisplayContext
impl Send for DisplayContext
impl Sync for DisplayContext
impl Unpin for DisplayContext
impl UnsafeUnpin for DisplayContext
impl UnwindSafe for DisplayContext
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.