Persian Currency (Rial & Toman)
Relevant source files
The following files were used as context for generating this wiki page:
Introduction
The Hesabyar project implements a robust system for handling Persian currency, specifically managing the relationship between Rial and Toman. The system is architected to prioritize the Rial as the base unit for data storage (matching Room's Long type) while providing flexible display options for the user. This dual-unit support is critical for Persian-speaking users who frequently switch between the official Rial and the colloquial Toman.
The core logic for currency conversion and formatting is implemented in a shared Rust Core (hesabyar-core), which is exposed to the Android Kotlin frontend via UniFFI. This ensures high performance, memory safety, and consistency across potential future platforms. The system handles digit normalization (Persian/Arabic to ASCII), thousands separation, and unit-aware string formatting.
Sources: rust/hesabyar-core/README.md, app/src/main/java/io/github/mojri/hesabyar/ui/CurrencyFormatter.kt:23-28, AGENTS.md:28
Core Architecture and Data Flow
Hesabyar utilizes a layered architecture where the financial logic resides in Rust and the UI state management resides in Kotlin. The CurrencyFormatter object in Kotlin acts as the primary bridge, maintaining the currentUnit state and delegating heavy lifting to the RustBridge.
Currency Unit Representation
The system recognizes two primary units defined in both Kotlin and Rust:
- RIAL: The base unit for database storage.
- TOMAN: The common display unit.
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/CurrencyFormatter.kt:3-10, rust/hesabyar-core/src/currency.rs:1-3
Conversion Invariants
The system enforces a strict, hardcoded invariant for all calculations: 1 Toman is exactly 10 Rials. This rule is immutable within the codebase to ensure financial accuracy and avoid conversational shorthand ambiguities.
| Conversion | Logic |
|---|---|
| Toman to Rial | display_value * 10 |
| Rial to Toman | rial / 10 (Integer division) |
Sources: rust/hesabyar-core/src/currency.rs:31-41, app/src/main/java/io/github/mojri/hesabyar/ui/CurrencyFormatter.kt:103-108
Logic Flow Diagram
The following diagram illustrates how a raw Rial value from the database is processed for UI display.
The diagram shows the delegation from Kotlin to Rust and the final Persian digit normalization step.
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/CurrencyFormatter.kt:73-95
Technical Implementation
Rust Core Implementation (currency.rs)
The Rust layer provides pure functions for numeric formatting and conversion. It uses unsigned_abs() to handle i64::MIN without overflow and inserts ASCII commas as thousands separators.
format_number(value: i64): Adds thousands separators to a raw number.format_currency(rial: i64, unit: CurrencyUnit): Appends the Persian unit label ("ریال" or "تومان").to_rial/from_rial: Perform the invariant-based multiplication/division.
Sources: rust/hesabyar-core/src/currency.rs:5-30
Kotlin Bridge and UI Formatting (CurrencyFormatter.kt)
The Kotlin CurrencyFormatter handles the "Persian-first" requirement by converting ASCII digits returned by Rust into Persian Unicode digits (0x06F0 range).
private fun toPersianDigits(western: String): String {
val sb = StringBuilder(western.length)
for (c in western) {
when {
c in '0'..'9' -> sb.append((c.code - '0'.code + 0x06F0).toChar())
c == ',' -> sb.append('\u066C') // Arabic thousands separator ٬
else -> sb.append(c)
}
}
return sb.toString()
}
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/CurrencyFormatter.kt:45-56
Data Normalization
The text_preprocessor.rs in the Rust core is responsible for cleaning user input. It normalizes various Persian/Arabic symbols into standard formats before parsing amounts.
| Original Character | Description | Normalized To |
|---|---|---|
\u{06F0} - \u{06F9} | Persian Digits | 0-9 (ASCII) |
\u{0660} - \u{0669} | Arabic Digits | 0-9 (ASCII) |
\u{066C} | Arabic Thousands Separator | Removed (in money context) |
\u{200C} | Zero Width Non-Joiner | Space |
Sources: rust/hesabyar-core/src/parser/text_preprocessor.rs:1-40
Testing and Validation
The system is verified through extensive unit tests in both Kotlin and Rust.
Kotlin Unit Tests
Tests in CurrencyFormatterTest.kt ensure that the state management and fallback logic (used when the native library is unavailable during JVM tests) correctly handle signs, zeros, and rounding for odd Rial values.
- Round-trip Consistency: Ensures
toRial(fromRial(x)) == xfor Toman. - Sign Handling: Validates that negative signs are correctly placed (e.g.,
-۱٬۰۰۰ تومان). - Truncation: Confirms that 55 Rials becomes 5 Tomans via integer division.
Sources: app/src/test/java/io/github/mojri/hesabyar/ui/CurrencyFormatterTest.kt:15-115
Sequence of Currency Conversion
The sequence illustrates the transformation from raw Rial value to a localized Persian string.
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/CurrencyFormatter.kt:66-72
Summary
The Persian Currency system in Hesabyar provides a seamless experience for managing Rial and Toman. By anchoring financial data in Rial Long values and utilizing a Rust-based core for logic, the application maintains high precision and performance. The multi-stage formatting pipeline ensures that while calculations are performed on standard ASCII digits, the end-user sees a fully localized experience with Persian numerals and appropriate separators.
Sources: README.md, rust/hesabyar-core/README.md