diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33a9488 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +example diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a9fc0a --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Salvium: Fixed-Point Decimal Type for Cryptocurrency + +## Design Principles + +**No silent failures**: Arithmetic operations throw on overflow/underflow instead of clamping. This surfaces bugs immediately rather than corrupting balances silently. + +**Integer-based storage**: Stores values as uint64_t atomic units (dusties or 1e-8 Salvium), eliminating floating-point rounding errors entirely. 8 decimal places matches Salvium's precision requirements. + +**Type-safe**: The type system itself prevents negative values (unsigned backing type) rather than relying on runtime checks. You cannot construct an invalid state. + +**Explicit bounds checking**: Operations that could overflow check before executing, making resource limits visible in the code. + +## Why This Works for Crypto + +1. **Precision**: Fixed-point arithmetic with integer backing guarantees exact results. No floating-point rounding corruption. + +2. **Atomicity**: Works directly with atomic units (dusties), which is how blockchains actually store values. + +3. **Auditability**: Failed operations throw; you know immediately when something went wrong. No silently-clamped balance errors. + +4. **No negative values by design**: Uses unsigned integers, not runtime validation that can fail. + +## Usage + +```cpp +// From atomic units +Salvium balance(500000000ULL); // 5 Salvium + +// From decimal string (no floating-point involved) +Salvium amount = Salvium::from_string("1.5"); + +// Arithmetic throws on overflow/underflow +balance += amount; +balance -= amount; // throws if amount > balance + +// Get values back +uint64_t atomic = balance.to_atomic(); +std::string display = balance.to_salvium_string(); +``` + +This is what crypto needs. + diff --git a/example.cpp b/example.cpp new file mode 100644 index 0000000..d6b5e80 --- /dev/null +++ b/example.cpp @@ -0,0 +1,42 @@ +#include "salvium.h" +#include + +int main() { + try { + // Create amounts from atomic units + Salvium balance(500000000ULL); // 5 Salvium + Salvium payment(150000000ULL); // 1.5 Salvium + + std::cout << "Balance: " << balance.to_salvium_string() << " Salvium\n"; + std::cout << "Payment: " << payment.to_salvium_string() << " Salvium\n"; + + // Add payment + balance += payment; + std::cout << "After deposit: " << balance.to_salvium_string() << " Salvium\n"; + + // Subtract payment + Salvium withdrawal = Salvium::from_string("2.3"); + balance -= withdrawal; + std::cout << "After withdrawal: " << balance.to_salvium_string() << " Salvium\n"; + + // This will throw + try { + balance -= Salvium::from_string("100"); + } catch (const std::underflow_error& e) { + std::cout << "Error caught: " << e.what() << "\n"; + } + + // Demonstrate multiplication (pool payout scaling) + Salvium block_reward = Salvium::from_string("17.5"); + Salvium miner_share = block_reward * 50 / 100; // 50% share + std::cout << "Block reward: " << block_reward.to_salvium_string() << " Salvium\n"; + std::cout << "Miner share: " << miner_share.to_salvium_string() << " Salvium\n"; + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << "\n"; + return 1; + } +} + diff --git a/salvium.h b/salvium.h new file mode 100644 index 0000000..0e4deea --- /dev/null +++ b/salvium.h @@ -0,0 +1,213 @@ +#ifndef SALVIUM_H +#define SALVIUM_H + +#include +#include +#include +#include +#include + +// Fixed-point decimal type for Salvium atomic units +// Stores values as uint64_t with 8 decimal places (dusty precision) +// Arithmetic operations fail loudly on overflow/underflow + +class Salvium { +private: + static constexpr uint64_t ATOMIC_UNIT = 100000000ULL; // 10^8 + static constexpr int DECIMAL_PLACES = 8; + + uint64_t atomic_units; // value in dusties + + // Helper to detect overflow in multiplication + static bool will_overflow_multiply(uint64_t a, uint64_t b) { + if (a == 0 || b == 0) return false; + return a > UINT64_MAX / b; + } + + // Helper to detect overflow in addition + static bool will_overflow_add(uint64_t a, uint64_t b) { + return a > UINT64_MAX - b; + } + +public: + // Construct from atomic units (dusties) + explicit Salvium(uint64_t atomic = 0) : atomic_units(atomic) {} + + // Construct from decimal string (e.g., "1.5", "100", "0.00000001") + // Parses directly to atomic units without floating-point + static Salvium from_string(const std::string& s) { + if (s.empty()) { + throw std::invalid_argument("Empty string"); + } + + size_t decimal_pos = s.find('.'); + std::string whole_str = (decimal_pos == std::string::npos) ? s : s.substr(0, decimal_pos); + std::string frac_str = (decimal_pos == std::string::npos) ? "" : s.substr(decimal_pos + 1); + + // Validate characters + for (char c : whole_str) { + if (c < '0' || c > '9') { + throw std::invalid_argument("Invalid character in whole part"); + } + } + for (char c : frac_str) { + if (c < '0' || c > '9') { + throw std::invalid_argument("Invalid character in fractional part"); + } + } + + // Check fractional precision + if (frac_str.length() > DECIMAL_PLACES) { + throw std::invalid_argument("Too many decimal places (max 8)"); + } + + // Pad fractional part to 8 digits + while (frac_str.length() < DECIMAL_PLACES) { + frac_str += '0'; + } + + // Parse parts + uint64_t whole = 0; + if (!whole_str.empty()) { + for (char c : whole_str) { + uint64_t digit = c - '0'; + if (will_overflow_multiply(whole, 10)) { + throw std::overflow_error("Value exceeds maximum"); + } + whole *= 10; + if (will_overflow_add(whole, digit)) { + throw std::overflow_error("Value exceeds maximum"); + } + whole += digit; + } + } + + uint64_t frac = 0; + for (char c : frac_str) { + frac = frac * 10 + (c - '0'); + } + + // Combine: whole * ATOMIC_UNIT + frac + if (will_overflow_multiply(whole, ATOMIC_UNIT)) { + throw std::overflow_error("Value exceeds maximum"); + } + uint64_t atomic = whole * ATOMIC_UNIT; + if (will_overflow_add(atomic, frac)) { + throw std::overflow_error("Value exceeds maximum"); + } + + return Salvium(atomic + frac); + } + + // Get value in atomic units (dusties) + uint64_t to_atomic() const { + return atomic_units; + } + + // Get value as display string (Salvium) + std::string to_salvium_string() const { + std::ostringstream oss; + oss << std::fixed << std::setprecision(DECIMAL_PLACES); + + uint64_t whole = atomic_units / ATOMIC_UNIT; + uint64_t fractional = atomic_units % ATOMIC_UNIT; + + oss << whole << "." << std::setfill('0') << std::setw(DECIMAL_PLACES) << fractional; + + std::string result = oss.str(); + + // Strip trailing zeros after decimal point + size_t decimal_pos = result.find('.'); + if (decimal_pos != std::string::npos) { + result.erase(result.find_last_not_of('0') + 1); + if (result.back() == '.') { + result.pop_back(); + } + } + + return result; + } + + // Arithmetic operators + Salvium operator+(const Salvium& other) const { + if (will_overflow_add(atomic_units, other.atomic_units)) { + throw std::overflow_error("Addition overflow"); + } + return Salvium(atomic_units + other.atomic_units); + } + + Salvium operator-(const Salvium& other) const { + if (atomic_units < other.atomic_units) { + throw std::underflow_error("Subtraction underflow"); + } + return Salvium(atomic_units - other.atomic_units); + } + + Salvium operator*(uint64_t scalar) const { + if (will_overflow_multiply(atomic_units, scalar)) { + throw std::overflow_error("Multiplication overflow"); + } + return Salvium(atomic_units * scalar); + } + + Salvium operator/(uint64_t scalar) const { + if (scalar == 0) { + throw std::invalid_argument("Division by zero"); + } + return Salvium(atomic_units / scalar); + } + + // In-place operators + Salvium& operator+=(const Salvium& other) { + *this = *this + other; + return *this; + } + + Salvium& operator-=(const Salvium& other) { + *this = *this - other; + return *this; + } + + Salvium& operator*=(uint64_t scalar) { + *this = *this * scalar; + return *this; + } + + Salvium& operator/=(uint64_t scalar) { + *this = *this / scalar; + return *this; + } + + // Comparison operators + bool operator==(const Salvium& other) const { + return atomic_units == other.atomic_units; + } + + bool operator!=(const Salvium& other) const { + return atomic_units != other.atomic_units; + } + + bool operator<(const Salvium& other) const { + return atomic_units < other.atomic_units; + } + + bool operator<=(const Salvium& other) const { + return atomic_units <= other.atomic_units; + } + + bool operator>(const Salvium& other) const { + return atomic_units > other.atomic_units; + } + + bool operator>=(const Salvium& other) const { + return atomic_units >= other.atomic_units; + } + + // Zero check + bool is_zero() const { + return atomic_units == 0; + } +}; + +#endif // SALVIUM_H +