[package]
name = "decimal-scaled"
version = "0.4.0"
edition = "2024"
rust-version = "1.85"
license = "MIT OR Apache-2.0"
description = "Const-generic base-10 fixed-point decimals (D9/D18/D38/D76/D153/D307) with integer-only transcendentals correctly rounded to within 0.5 ULP — exact at the type's last representable place. Deterministic across every platform; no_std-friendly."
repository = "https://github.com/mootable/decimal-scaled"
homepage = "https://github.com/mootable/decimal-scaled"
documentation = "https://mootable.github.io/decimal-scaled/"
readme = "README.md"
keywords = ["decimal", "fixed-point", "deterministic", "no-std", "precision"]
categories = ["mathematics", "no-std", "science", "finance"]
exclude = [
    ".github/",
    "bash.exe.stackdump",
    "research/",
    "tarpaulin.toml",
    "scripts/",
    "docs/benchmarks*.draft",
]

# docs.rs renders the doc build with whichever features are enabled
# here, not the crate's default-feature set. Turning on the wide
# tiers, the macros, and serde means every public type and macro
# (D9/D18/D38/D76/D153/D307, all `dNN!` proc-macros, `serde_helpers`)
# is visible on the rendered page. Experimental-floats and the
# mutually-exclusive `rounding-*` flags are deliberately omitted so
# the docs build doesn't require nightly or pick a non-default
# rounding mode.
[package.metadata.docs.rs]
features = ["std", "serde", "strict", "macros", "wide", "x-wide", "xx-wide", "dyn"]
rustdoc-args = ["--cfg", "docsrs"]

[features]
# `strict` is on by default. The crate's headline feature is the
# correctly-rounded (<=0.5 ULP) integer-only transcendentals;
# plain `ln` / `sin` / `sqrt` / etc. dispatch to that path.
#
# Strict is ALSO the dispatch when no feature is set at all; the
# explicit `strict` feature mostly signals intent and survives a
# transitive `fast` flip from a downstream crate (which still
# resolves to strict — strict wins when both are present).
#
# To opt out and use the f64 bridge instead you have to take all
# three steps: (a) `default-features = false`, (b) explicitly add
# `fast` + `std`, and (c) NOT re-add `strict`:
#   decimal-scaled = { version = "0.3.1", default-features = false,
#                      features = ["std", "serde", "fast"] }
# That gives ~16 decimal digits of platform-libm precision rather
# than the deterministic 0.5 ULP strict path.
default = ["std", "serde", "strict"]
std = ["alloc"]
alloc = []
serde = ["dep:serde"]
macros = ["dep:decimal_scaled_macros"]
# Marks the build as on the strict path: plain `sin` / `ln` / etc.
# dispatch to the integer-only, <=0.5 ULP, platform-deterministic
# `*_strict` methods. On by default. Does NOT require `std` — the
# integer-only transcendentals compile under `no_std`.
strict = []
# Runtime-polymorphic façade. Adds the `DynDecimal` object-safe trait
# and `DecimalWidth` enum, with a blanket impl bridging every concrete
# `Dxx<S>` type. Off by default — typed `Decimal` users pay nothing.
# Boxing cost (heap allocation per binary op) is the price of erasure.
dyn = []
# Opt out of strict dispatch: plain transcendentals forward to the
# f64 bridge. Only takes effect when `strict` is NOT enabled — if
# both are present, strict wins. Both `*_strict` and `*_fast`
# named methods are emitted regardless of which feature is on; this
# feature only chooses what plain `*` resolves to.
fast = []
# Compiles BOTH the macro-default and the per-type override
# implementations for every method that has them. Default builds compile
# only the chosen winner (see OVERRIDE_POLICY.md). With `bench-alt` on
# benchmarks can call `*_strict_default` and `*_strict_override` side by
# side without recompiling. Off by default — adds build time and binary
# size for no runtime benefit unless you're benching.
bench-alt = []
# Compile-time selector for the default RoundingMode used by the
# no-arg `rescale` family (and any future default-rounding methods).
# If none of these is enabled, the default is HalfToEven (IEEE-754
# default; banker's rounding). Mutually exclusive in intent — if more
# than one is enabled, the priority order in `src/rounding.rs` picks
# canonically.
rounding-half-away-from-zero = []
rounding-half-toward-zero = []
rounding-trunc = []
rounding-floor = []
rounding-ceiling = []
# Nightly-only: expose `from_f16` / `to_f16` /
# `from_f128` / `to_f128` on each decimal type. f16 and
# f128 are experimental in Rust; this feature requires the nightly
# toolchain. On stable, leave the feature off.
experimental-floats = []
# Wide decimal tiers (D76 / D153 / D307). Each width is independently
# opt-in; `wide` is the umbrella for the 256-bit / 512-bit tiers and
# `x-wide` adds the 1024-bit tier. They are backed by the in-tree
# hand-rolled wide-integer types in `crate::wide_int` — no external
# big-integer dependency. D9 / D18 / D38 use primitive storage and
# need none of this.
wide = ["d57", "d76", "d115", "d153", "d230"]
d57 = []
d76 = []
d115 = []
d153 = []
d230 = []
# Very-wide tier (D307+). Gated separately because its strict-
# transcendental work integers run into thousands of bits and inflate
# build time vs the narrower wide tiers.
x-wide = ["d307", "d462", "d616"]
d307 = []
d462 = []
d616 = []
# Extra-wide tier (D924+). Pulls in the Int12288 / Int16384 work
# integers — multi-second compile-time cost for the macro emission.
xx-wide = ["d924", "d1232"]
d924 = []
d1232 = []
# Internal perf-investigation feature: emits `tracing::info_span!`
# blocks at the section boundaries of the wide-tier strict
# transcendental cores (`exp_fixed`, `ln_fixed`, …). With
# `tracing-chrome` set up in an example, the resulting JSON loads in
# https://ui.perfetto.dev for a per-section flame view. Default-off;
# the spans compile to nothing without the feature, so production
# users see zero overhead.
perf-trace = ["dep:tracing"]

[dependencies]
serde = { version = "1", features = ["derive"], optional = true }
num-traits = { version = "0.2", default-features = false }
decimal_scaled_macros = { path = "macros", version = "0.4.0", default-features = false, optional = true }
tracing = { version = "0.1", default-features = false, features = ["std"], optional = true }

[dev-dependencies]
criterion = { version = "4.6.0", default-features = false, features = ["cargo_bench_support", "html_reports"], package = "codspeed-criterion-compat" }
i256 = { version = "0.2", default-features = false }
serde_json = "1"
postcard = { version = "1", default-features = false, features = ["alloc"] }
decimal_scaled_macros = { path = "macros" }
# Perf-trace examples wire tracing-chrome to write Perfetto/Chrome JSON.
tracing-chrome = "0.7"
tracing-subscriber = { version = "0.3", default-features = false, features = ["std"] }
# Benchmark-only baselines for the backend comparisons (see benches/).
# None of these are normal dependencies.
bnum = { version = "0.14", default-features = false }
ruint = { version = "1", default-features = false }
rust_decimal = { version = "1", default-features = false, features = ["maths"] }
fixed = "1"
# g_math: another fixed-point arithmetic crate that claims "0 ULP
# transcendentals". Used as a benchmark baseline + accuracy comparison
# (see benches/g_math_comparison.rs).
g_math = "0.4"
# Library-comparison comparators for the §5 chapter in docs/benchmarks.md.
# All bench-only; never compiled into a normal build.
fastnum = { version = "0.7", default-features = false, features = ["std"] }
bigdecimal = "0.4"
dashu-float = "0.4"
decimal-rs = "0.1"
scientific = "0.5"
# Chart generation for docs/figures/library_comparison/*.png — pure
# Rust, no Python dep. Driven by examples/chart_gen.rs.
plotters = { version = "0.3", default-features = false, features = ["bitmap_backend", "bitmap_encoder", "line_series", "ttf"] }

[[bench]]
name = "mul_div_candidates"
harness = false

# Per-PR perf-regression gate, driven by CodSpeed under simulation
# instrumentation. See .github/workflows/codspeed.yml.
[[bench]]
name = "pr_gate"
harness = false

[[bench]]
name = "all_functions"
harness = false

# Focused micro-bench for D38<33>::log10_strict / log2_strict / etc.
# Used to validate the borrow-D57 migration of the secondary log/exp
# entry points (ln/exp were borrowed by commit 09e7ea9).
[[bench]]
name = "d38_s33_log10_micro"
harness = false
required-features = ["wide"]

# Wide-integer backend comparison: native hand-rolled `Int256` vs
# `bnum` / `ruint`. Needs the wide tier.
[[bench]]
name = "wide_int_backends"
harness = false
required-features = ["wide"]

# Decimal / fixed-point backend comparison: native `D38` / `D76` vs
# `bnum`-backed `D76`, `rust_decimal`, and `fixed` — arithmetic and
# transcendentals (including the fast vs strict variants). Needs the
# wide tier.
[[bench]]
name = "decimal_backends"
harness = false
required-features = ["wide"]

# D38 mul / div: native Moller-Granlund path vs the wide-arm
# (Int256-widening) algorithm head-to-head.
[[bench]]
name = "d_w128_mul_div_paths"
harness = false
required-features = ["wide"]

# AGM vs artanh / Taylor strict transcendentals at the deepest tier
# (D307<300>). Tracks the AGM path's throughput so a future bench-
# driven promotion has a data point.
[[bench]]
name = "agm_vs_taylor"
harness = false
required-features = ["x-wide"]

# Per-width full_matrix slice — one bench binary per decimal width.
# Each runs the arith + strict (+ lossy, narrow only) families for
# just its width, plus any cross-crate baseline that matches the
# width (rust_decimal under D38, bnum under D76, fixed::I64F64 under
# D18). Splitting per-width keeps a power-loss / lid-close near the
# end of a sweep from wiping out completed widths' criterion data.
[[bench]]
name = "full_matrix_d9"
harness = false

[[bench]]
name = "full_matrix_d18"
harness = false

[[bench]]
name = "full_matrix_d38"
harness = false

[[bench]]
name = "full_matrix_d57"
harness = false
required-features = ["wide"]

[[bench]]
name = "full_matrix_d76"
harness = false

[[bench]]
name = "full_matrix_d115"
harness = false
required-features = ["wide"]

[[bench]]
name = "full_matrix_d153"
harness = false

[[bench]]
name = "full_matrix_d230"
harness = false
required-features = ["wide"]

[[bench]]
name = "full_matrix_d307"
harness = false

[[bench]]
name = "full_matrix_d462"
harness = false
required-features = ["x-wide"]

[[bench]]
name = "full_matrix_d616"
harness = false
required-features = ["x-wide"]

[[bench]]
name = "full_matrix_d924"
harness = false
required-features = ["xx-wide"]

[[bench]]
name = "full_matrix_d1232"
harness = false
required-features = ["xx-wide"]

# Like-for-like comparison vs `g_math` (another fixed-point crate that
# claims "0 ULP transcendentals"). Runs at decimal precision matched
# to g_math's default Q64.64 (~19 decimal digits = D38<19>) and reports
# both timing and an accuracy delta against the higher-precision
# D76<35> baseline.
[[bench]]
name = "g_math_comparison"
harness = false
required-features = ["wide"]


# Focused microbench for wide-tier div/mul during tuning passes.
# Runs in ~30s, used to validate per-change wins before kicking
# the full lib_cmp_d{N} sweep.
[[bench]]
name = "quick_div"
harness = false
required-features = ["wide", "x-wide", "xx-wide"]

# Gate bench for the per-L fixed-array mul specialisation.
[[bench]]
name = "m2_mul_fixed"
harness = false
required-features = ["wide", "x-wide", "xx-wide", "bench-alt"]

# Multi-input atan comparison across decimal-scaled / fastnum /
# dashu-float at D38<19>. Times 8 input classes to expose
# per-library fast-path coverage (fastnum's NaN-out for |x|>1,
# our 3 new fast paths, etc.).
[[bench]]
name = "atan_inputs"
harness = false
required-features = ["wide"]

# Gate for ln_approx(working_digits) — caller-controlled precision.
[[bench]]
name = "m2_ln_approx"
harness = false
required-features = ["wide"]




# Per-width library_comparison benches. Each runs ONE tier's full
# peer comparison so `cargo bench --bench lib_cmp_d307` iterates
# in minutes. Run them all sequentially for a full matrix sweep.
[[bench]]
name = "lib_cmp_d9"
harness = false

[[bench]]
name = "lib_cmp_d18"
harness = false

[[bench]]
name = "lib_cmp_d38"
harness = false

[[bench]]
name = "lib_cmp_d57"
harness = false
required-features = ["wide"]

[[bench]]
name = "lib_cmp_d76"
harness = false
required-features = ["wide"]

[[bench]]
name = "lib_cmp_d115"
harness = false
required-features = ["wide"]

[[bench]]
name = "lib_cmp_d153"
harness = false
required-features = ["wide"]

[[bench]]
name = "lib_cmp_d230"
harness = false
required-features = ["wide"]

[[bench]]
name = "lib_cmp_d307"
harness = false
required-features = ["wide"]

[[bench]]
name = "lib_cmp_d462"
harness = false
required-features = ["x-wide"]

[[bench]]
name = "lib_cmp_d616"
harness = false
required-features = ["x-wide"]

[[bench]]
name = "lib_cmp_d924"
harness = false
required-features = ["xx-wide"]

[[bench]]
name = "lib_cmp_d1232"
harness = false
required-features = ["xx-wide"]

[lints.rust]
unused_variables = "allow"
dead_code = "allow"

# Crate-wide clippy policy — applies to lib code, tests, benches,
# and the build script. Mirrors the `#![allow(...)]` block at the
# top of `src/lib.rs` (which is still needed because `lints.clippy`
# was introduced in Cargo 1.74 and the per-file directive supports
# Rust toolchains that don't yet have the manifest-level feature).
[lints.clippy]
module_name_repetitions = "allow"
doc_lazy_continuation = "allow"
empty_line_after_outer_attr = "allow"
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"
cast_sign_loss = "allow"
cast_lossless = "allow"
cast_precision_loss = "allow"
unreadable_literal = "allow"
manual_assert = "allow"
result_unit_err = "allow"
collapsible_if = "allow"
similar_names = "allow"
many_single_char_names = "allow"
too_many_lines = "allow"
inline_always = "allow"
float_cmp = "allow"
manual_let_else = "allow"
format_push_string = "allow"
comparison_chain = "allow"
must_use_candidate = "allow"
return_self_not_must_use = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
doc_markdown = "allow"
# Lints relevant only to tests / benches.
items_after_statements = "allow"
uninlined_format_args = "allow"
semicolon_if_nothing_returned = "allow"
should_panic_without_expect = "allow"
nonminimal_bool = "allow"
assertions_on_constants = "allow"
bool_assert_comparison = "allow"
bool_to_int_with_if = "allow"

[workspace]
members = [
    ".",
    "./macros",
]
resolver = "3"

# `debug = true` emits a full PDB (function names + line info) on
# Windows MSVC and full DWARF on Linux/Mac. Required for sample-based
# profilers (samply, cargo-flamegraph) to resolve frames to symbol
# names — `line-tables-only` only carries file:line and leaves the
# function-name table empty, which makes flame graphs unsymbolicated.
# Doesn't affect optimisation level; ~30% binary growth from debug
# info, but the PDB is a sibling file on Windows (not in the .exe)
# so runtime perf and shipped binary size are unchanged.
[profile.release]
debug = true
