positive 0.2.1

A type-safe wrapper for guaranteed positive decimal values
Documentation

Dual License Crates.io Downloads Stars Issues PRs Build Status Coverage Dependencies Documentation Wiki

Positive

A type-safe wrapper for guaranteed positive decimal values in Rust.

Overview

Positive is a Rust library that provides a type-safe wrapper around Decimal values, ensuring that the contained value is always non-negative (>= 0). This is particularly useful in financial applications where negative values would be invalid or meaningless, such as prices, quantities, volatilities, and other strictly positive metrics.

Features

  • Type Safety: Compile-time and runtime guarantees that values are non-negative
  • Decimal Precision: Built on rust_decimal for accurate financial calculations
  • Rich API: Comprehensive arithmetic operations, conversions, and mathematical utilities
  • Serde Support: Full serialization/deserialization support for JSON and other formats
  • Approx Support: Approximate equality comparisons for floating-point tolerance
  • Checked Operations: Safe arithmetic operations that return Result instead of panicking
  • Optional utoipa Integration: OpenAPI schema generation support via feature flag

Installation

Add this to your Cargo.toml:

[dependencies]
positive = "0.1"

To enable OpenAPI schema support:

[dependencies]
positive = { version = "0.1", features = ["utoipa"] }

Quick Start

use positive::{Positive, pos, pos_or_panic};

// Create a positive value using the macro (returns Result)
let price = pos!(100.50).unwrap();

// Or use pos_or_panic! for direct value (panics on invalid input)
let price = pos_or_panic!(100.50);

// Create using the constructor
let quantity = Positive::new(10.0).unwrap();

// Arithmetic operations
let total = price * quantity;

// Safe operations that return Result
let discount = pos_or_panic!(5.0);
let final_price = price.checked_sub(&discount).unwrap();

// Saturating subtraction (returns ZERO instead of negative)
let result = pos_or_panic!(3.0).saturating_sub(&pos_or_panic!(5.0));
assert_eq!(result, Positive::ZERO);

API Overview

Creation

use positive::{Positive, pos, pos_or_panic, spos};
use rust_decimal::Decimal;

// From f64
let p = Positive::new(5.0).unwrap();

// From Decimal
let p = Positive::new_decimal(Decimal::ONE).unwrap();

// Using macros
let p = pos!(5.0);           // Returns Result<Positive, PositiveError>
let p = pos_or_panic!(5.0);  // Panics on invalid input
let p = spos!(5.0);          // Returns Option<Positive>

Constants

use positive::Positive;

let zero = Positive::ZERO;       // 0
let one = Positive::ONE;         // 1
let two = Positive::TWO;         // 2
let ten = Positive::TEN;         // 10
let hundred = Positive::HUNDRED; // 100
let thousand = Positive::THOUSAND; // 1000
let pi = Positive::PI;           // π
let inf = Positive::INFINITY;    // Maximum value

Conversions

use positive::pos_or_panic;

let p = pos_or_panic!(5.5);

let f: f64 = p.to_f64();              // Panics on failure
let f: Option<f64> = p.to_f64_checked(); // Returns None on failure
let f: f64 = p.to_f64_lossy();        // Returns 0.0 on failure
let i: i64 = p.to_i64();              // To signed integer
let u: u64 = p.to_u64();              // To unsigned integer
let d = p.to_dec();                   // To Decimal

Arithmetic Operations

use positive::pos_or_panic;

let a = pos_or_panic!(10.0);
let b = pos_or_panic!(3.0);

// Standard operations
let sum = a + b;        // Addition
let diff = a - b;       // Subtraction (panics if result < 0)
let prod = a * b;       // Multiplication
let quot = a / b;       // Division

// Safe operations
let safe_diff = a.checked_sub(&b);    // Returns Result
let sat_diff = a.saturating_sub(&b);  // Returns ZERO if result < 0
let safe_quot = a.checked_div(&b);    // Returns Result (handles div by zero)

Mathematical Functions

use positive::pos_or_panic;

let p = pos_or_panic!(16.0);

let sqrt = p.sqrt();           // Square root
let ln = p.ln();               // Natural logarithm
let log10 = p.log10();         // Base-10 logarithm
let exp = p.exp();             // Exponential (e^x)
let pow = p.pow(pos_or_panic!(2.0));    // Power with Positive exponent
let powi = p.powi(2);          // Integer power
let floor = p.floor();         // Floor
let ceil = p.ceiling();        // Ceiling
let round = p.round();         // Round to nearest integer
let round2 = p.round_to(2);    // Round to 2 decimal places

Utility Methods

use positive::pos_or_panic;

let p = pos_or_panic!(5.0);

let is_zero = p.is_zero();                      // Check if zero
let is_mult = p.is_multiple(2.0);               // Check if multiple of value
let clamped = p.clamp(pos_or_panic!(1.0), pos_or_panic!(10.0));   // Clamp between bounds
let min_val = p.min(pos_or_panic!(3.0));                 // Minimum of two values
let max_val = p.max(pos_or_panic!(3.0));                 // Maximum of two values
let formatted = p.format_fixed_places(2);       // Format with fixed decimals

Error Handling

The library provides PositiveError for comprehensive error handling:

use positive::{Positive, PositiveError};

fn example() -> Result<Positive, PositiveError> {
    let value = Positive::new(-5.0)?;  // Returns Err(OutOfBounds)
    Ok(value)
}

Error variants include:

  • InvalidValue - Value cannot be represented as a valid positive decimal
  • ArithmeticError - Error during mathematical operations
  • ConversionError - Error when converting between types
  • OutOfBounds - Value exceeds defined limits
  • InvalidPrecision - Invalid decimal precision settings

Serialization

Positive implements Serialize and Deserialize:

use positive::pos_or_panic;

let p = pos_or_panic!(42.5);
let json = serde_json::to_string(&p).unwrap();  // "42.5"
let parsed: positive::Positive = serde_json::from_str(&json).unwrap();

Use Cases

  • Financial Applications: Prices, quantities, fees, rates
  • Scientific Computing: Physical quantities that cannot be negative
  • Game Development: Health points, distances, timers
  • Data Validation: Ensuring input values meet positivity constraints

License

This project is licensed under the MIT License.

Contribution and Contact

We welcome contributions to this project! If you would like to contribute, please follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Make your changes and ensure that the project still builds and all tests pass.
  4. Commit your changes and push your branch to your forked repository.
  5. Submit a pull request to the main repository.

If you have any questions, issues, or would like to provide feedback, please feel free to contact the project maintainer:

Contact Information

We appreciate your interest and look forward to your contributions!

✍️ License

Licensed under MIT license