rust-decimal
rust_decimal — fixed-point decimal arithmetic for Rust. 128-bit signed decimal, exact (no float rounding errors), suitable for finance, currency, Bitcoin sat ↔ fiat conversions, accounting. Up to 28 significant digits, arithmetic preserves precision. Includes RoundingStrategy for explicit truncation/rounding control. Integrates with serde, sqlx, postgres. USE WHEN: user mentions "rust_decimal", "Decimal", "dec!", "RoundingStrategy", "no float", "exact arithmetic", "fixed-point Rust", "currency math Rust", "sat to fiat conversion" DO NOT USE FOR: Crypto primitives - use `security/libsodium` or `bitcoin/cryptography/*` DO NOT USE FOR: BigInteger / arbitrary-precision math - use `num-bigint` DO NOT USE FOR: Time/duration math - use `chrono` or `time` crate
What this skill does
# rust_decimal — Exact Decimal Arithmetic
> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `rust-decimal`.
## Why Not f64 for Money
```rust
let total: f64 = 0.1 + 0.2;
println!("{}", total); // 0.30000000000000004 — WRONG
let cents: f64 = 100.50_f64;
let cents_int = (cents * 100.0) as i64;
println!("{}", cents_int); // 10049 — WRONG (should be 10050)
```
Floating-point cannot represent most decimal fractions exactly. For money, this is **unacceptable**: rounding bugs become real losses, audit failures, regulatory issues.
For Bitcoin specifically: **always use `i64` for sats** (1 BTC = 100,000,000 sats fits easily). For fiat conversions, multi-currency display, fee calculations involving rates → use `Decimal`.
## Setup
```toml
[dependencies]
rust_decimal = "1.36"
rust_decimal_macros = "1.36"
# Optional features
rust_decimal = { version = "1.36", features = ["serde-with-str", "db-postgres", "macros"] }
```
Useful features:
- `serde-with-str` — serialize as string (preserves precision in JSON)
- `serde-with-float` — serialize as float (loses precision, but compatible)
- `serde-with-arbitrary-precision` — preserve via serde_json's arbitrary precision
- `db-postgres`, `db-tokio-postgres`, `db-diesel` — DB integration
- `macros` — `dec!` literal macro
- `maths` — extra math functions (exp, ln, sin, etc.)
- `tokio-pg` — async postgres
- `borsh` — binary serialization for blockchain projects
## Basic Usage
```rust
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
let a = dec!(0.1);
let b = dec!(0.2);
let c = a + b;
assert_eq!(c, dec!(0.3)); // EXACT
let price = dec!(19.99);
let quantity = dec!(3);
let total = price * quantity;
assert_eq!(total, dec!(59.97));
// Division
let avg = total / quantity;
assert_eq!(avg, dec!(19.99));
```
`dec!` macro creates Decimal at compile time, validates literal.
## From Strings
```rust
use std::str::FromStr;
let amount = Decimal::from_str("0.00012345")?;
let amount: Decimal = "12345.6789".parse()?;
```
For untrusted input always parse — never `as` from float.
## From Integers
```rust
let sats = Decimal::from(100_000_000_i64);
let btc = sats / dec!(100_000_000);
assert_eq!(btc, dec!(1));
```
## To Various Types
```rust
let d = dec!(123.456);
let s: String = d.to_string(); // "123.456"
let f: f64 = d.try_into()?; // 123.456 (lossy)
let i: i64 = d.try_into()?; // ERROR — has fractional part
let i: i64 = (d * dec!(1_000)).round().try_into()?; // 123456 (scaled)
```
## Bitcoin sats ↔ fiat Conversion
```rust
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
const SATS_PER_BTC: i64 = 100_000_000;
fn sats_to_fiat(sats: i64, btc_to_fiat_rate: Decimal) -> Decimal {
let btc = Decimal::from(sats) / Decimal::from(SATS_PER_BTC);
btc * btc_to_fiat_rate
}
fn fiat_to_sats(fiat: Decimal, btc_to_fiat_rate: Decimal) -> i64 {
let btc = fiat / btc_to_fiat_rate;
let sats = btc * Decimal::from(SATS_PER_BTC);
sats.round().to_i64().unwrap_or(0)
}
// Example: at $60,000/BTC
let rate = dec!(60000);
let sats = 100_000; // 0.001 BTC
let usd = sats_to_fiat(sats, rate);
assert_eq!(usd, dec!(60.00));
let euro_amount = dec!(100);
let euro_rate = dec!(55000); // €55,000/BTC
let needed_sats = fiat_to_sats(euro_amount, euro_rate);
assert_eq!(needed_sats, 181_818); // sats
```
## Rounding
```rust
use rust_decimal::RoundingStrategy;
let d = dec!(2.5);
let rounded = d.round(); // banker's rounding by default → 2
let rounded_half_up = d.round_dp_with_strategy(0, RoundingStrategy::MidpointAwayFromZero); // 3
let truncated = d.round_dp_with_strategy(0, RoundingStrategy::ToZero); // 2
// Specific decimal places
let price = dec!(19.9999);
let display_2dp = price.round_dp(2); // 20.00
let display_4dp = price.round_dp(4); // 19.9999
```
`RoundingStrategy` variants:
- `MidpointAwayFromZero` — "round half up" (most intuitive)
- `MidpointTowardZero` — "round half down"
- `MidpointNearestEven` — banker's rounding (default for `round()`)
- `ToZero` — truncate
- `AwayFromZero` — always round away from zero
- `ToNegativeInfinity` — floor
- `ToPositiveInfinity` — ceiling
For fiat display: typically `MidpointAwayFromZero` to 2 decimal places.
For Bitcoin fee calculations: use `ToPositiveInfinity` (always pay enough fee).
## Comparison
```rust
let a = dec!(1.0);
let b = dec!(1.00);
assert_eq!(a, b); // ✅ equal in value
assert_eq!(a.scale(), 1); // a has scale 1
assert_eq!(b.scale(), 2); // b has scale 2
// Hash works correctly even with different scales
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(a);
assert!(set.contains(&b));
```
## Serde
```rust
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Transaction {
amount: Decimal,
fee: Decimal,
rate_at_send: Decimal,
}
let tx = Transaction {
amount: dec!(0.001),
fee: dec!(0.00001),
rate_at_send: dec!(60000),
};
let json = serde_json::to_string(&tx)?;
println!("{}", json);
// Default: {"amount":0.001,"fee":0.00001,"rate_at_send":60000}
// With "serde-with-str" feature: amounts as strings
```
For wallet apps storing tx history in JSON, use `serde-with-str` to preserve exact values:
```toml
rust_decimal = { version = "1.36", features = ["serde-with-str"] }
```
## SQLite / SQLCipher Storage
`rust_decimal` doesn't have native rusqlite support, but easy pattern:
```rust
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use rust_decimal::Decimal;
use std::str::FromStr;
// Storage as TEXT (preserves precision)
struct DecimalSql(Decimal);
impl ToSql for DecimalSql {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.0.to_string()))
}
}
impl FromSql for DecimalSql {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let s = value.as_str()?;
Decimal::from_str(s)
.map(DecimalSql)
.map_err(|_| FromSqlError::InvalidType)
}
}
```
For SQLite **always store as TEXT** — `REAL` (float) loses precision.
For Postgres: use `db-postgres` feature → maps to NUMERIC type natively.
## Custom Display Formatting
```rust
use rust_decimal::Decimal;
let amount = dec!(1234567.89);
println!("{}", amount); // 1234567.89
println!("{:.2}", amount); // 1234567.89
// Locale-aware (manual)
fn format_eu(d: Decimal) -> String {
let s = d.to_string();
let parts: Vec<&str> = s.split('.').collect();
let integer = parts[0];
let decimal = parts.get(1).copied().unwrap_or("");
// Add thousand separators
let chars: Vec<char> = integer.chars().rev().collect();
let with_seps: String = chars
.chunks(3)
.map(|chunk| chunk.iter().collect::<String>())
.collect::<Vec<_>>()
.join(".")
.chars().rev().collect();
if decimal.is_empty() {
with_seps
} else {
format!("{},{}", with_seps, decimal) // EU: 1.234.567,89
}
}
```
For real i18n use `unic-langid` + `icu` crate.
## Math Operations
```rust
let a = dec!(10);
let b = dec!(3);
let q = a / b;
println!("{}", q); // 3.3333333333333333333333333333
// Limit precision
let q_truncated = q.round_dp(2); // 3.33
// Power
let n = dec!(2).powi(10); // 1024
// Absolute, sign
assert_eq!(dec!(-5).abs(), dec!(5));
assert!(dec!(-1).is_sign_negative());
// MIN/MAX
let max = Decimal::MAX; // 79228162514264337593543950335
let min = Decimal::MIN; Related in Ads & Marketing
ads
IncludedMulti-platform paid advertising audit and optimization skill. Analyzes Google, Meta, YouTube, LinkedIn, TikTok, Microsoft, and Apple Ads. 250+ checks with scoring, parallel agents, industry templates, and AI creative generation.
banana
IncludedAI image generation Creative Director powered by Google Gemini Nano Banana models. Use this skill for ANY request involving image creation, editing, visual asset production, or creative direction. Triggers on: generate an image, create a photo, edit this picture, design a logo, make a banner, visual for my anything, and all /banana commands. Handles text-to-image, image editing, multi-turn creative sessions, batch workflows, and brand presets.
rpg-migration-analyzer
IncludedAnalyzes legacy RPG (Report Program Generator) programs from AS/400 and IBM i systems for migration to modern Java applications. Extracts business logic from RPG III/IV/ILE source code, identifies data structures (D-specs), file operations (F-specs), program dependencies (CALLB/CALLP), and converts RPG constructs to Java equivalents. Generates migration reports, complexity estimates, and Java implementation strategies with POJO classes, JPA entities, and service methods. Use when modernizing AS/400 or IBM i legacy systems, analyzing RPG source files (.rpg, .rpgle, .RPGLE), converting RPG to Java, mapping data specifications to Java classes, planning legacy system migration, or when user mentions RPG analysis, Report Program Generator, RPG III/IV/ILE, AS/400 modernization, IBM i migration, packed decimal conversion, or mainframe application rewrite.
brand-library-architect
IncludedBuild a complete brand library for a product — visual asset render pipeline, brand documentation set (BRAND, COPY, MANIFESTO, BIOS, FAQ, GLOSSARY, TONE, PRICING), open-source convention files (README, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT), and a self-contained press kit. This skill should be used when the user asks to "build a brand library / brand kit / press kit / brand assets" for a product, "set up a brand library workflow," "create a positioning manifesto plus visual identity," or any combination of brand documentation + visual asset pipeline. Apply phase-by-phase or run end-to-end. Templates are product-agnostic and use {{TOKEN}} placeholders the skill prompts the user to fill.
writing-tech-post
IncludedAuthors engineering blog posts end-to-end: launch deep-dives, incident postmortems, architecture migrations, performance case studies, tutorials, AI/agent system writeups, security disclosures, and research-to-product translations. Picks the correct archetype, plans the abstraction ladder, enforces an evidence cadence (diagrams, benchmarks, profiles, traces, code, ablations), tunes voice against publisher house styles (Datadog, Vercel, GitHub, AWS, Meta, Cloudflare, Jane Street), and runs a pre-publish gate for narrative momentum and disclosure ethics. Use when drafting a new engineering post, restructuring a draft that feels flat, deciding which evidence form belongs where, validating that depth and product context are balanced, or preparing a postmortem, migration, or performance narrative for external publication. Do not use for API reference documentation, README authoring, marketing copy, release notes, generic SEO content, ghost-written executive thought leadership, or non-engineering long-form essays.
blog-google
IncludedGoogle API integration for blog performance: PageSpeed Insights, CrUX Core Web Vitals with 25-week history, Search Console performance, URL Inspection, Indexing API, GA4 organic traffic, NLP entity analysis for E-E-A-T, YouTube video search for embedding, and Google Ads Keyword Planner. Progressive feature availability based on credential tier (API key, OAuth/service account, GA4, Ads). Shares config with claude-seo at ~/.config/claude-seo/google-api.json. Use when user says "google data", "page speed", "core web vitals", "search console", "indexation", "GA4", "keyword research", "nlp entities", "blog performance", "youtube search", "google api setup".