12. Calculation Engine Logic
The calculation engine is the core of the system, implementing four different algorithms for denomination breakdown. Each algorithm is optimized for different use cases and provides O(n) time complexity.
12.1 Greedy Algorithm (Default)
Algorithm Description
The greedy algorithm uses the largest denomination first approach, minimizing the total number of notes and coins.
Implementation
def greedy_algorithm(amount: Decimal, denominations: List[Decimal]) -> Dict:
"""
Greedy: Start with largest denomination, use maximum possible.
Time Complexity: O(n) where n = number of denominations
Space Complexity: O(n) for result storage
Args:
amount: Amount to calculate
denominations: List of available denominations (sorted descending)
Returns:
{
'breakdown': [
{'denomination': float, 'count': int, 'total_value': float},
...
],
'summary': {
'total_pieces': int,
'total_notes': int,
'total_coins': int,
'total_denominations': int
}
}
"""
result = []
remaining = amount
# Sort denominations in descending order
sorted_denoms = sorted(denominations, reverse=True)
for denom in sorted_denoms:
if remaining >= denom:
# Calculate how many of this denomination
count = int(remaining / denom)
# Add to result
result.append({
'denomination': float(denom),
'count': count,
'total_value': float(count * denom)
})
# Subtract from remaining
remaining -= count * denom
# Handle floating point precision
remaining = Decimal(str(round(float(remaining), 2)))
# Calculate summary
summary = {
'total_pieces': sum(item['count'] for item in result),
'total_notes': sum(item['count'] for item in result
if item['denomination'] >= note_threshold),
'total_coins': sum(item['count'] for item in result
if item['denomination'] < note_threshold),
'total_denominations': len(result)
}
return {'breakdown': result, 'summary': summary}
Example
Input: 1850.50 INR (Greedy Mode)
| Denomination | Type | Count | Total Value |
|---|---|---|---|
| ?500 | Note | 3 | ?1,500.00 |
| ?200 | Note | 1 | ?200.00 |
| ?100 | Note | 1 | ?100.00 |
| ?50 | Note | 1 | ?50.00 |
| ?0.50 | Coin | 1 | ?0.50 |
Summary: Total 7 pieces (6 notes + 1 coin) using 5 different denominations
12.2 Balanced Algorithm
Strategy
- Use greedy algorithm as baseline
- Count notes vs coins ratio
- If imbalanced (>2:1 notes), replace large notes with smaller denominations
- If imbalanced (>3:1 coins), consolidate to larger denominations
Implementation
def balanced_algorithm(amount: Decimal, denominations: List[Decimal],
note_threshold: Decimal) -> Dict:
"""
Balanced: Try to balance notes and coins.
Strategy:
1. Use greedy for large amounts
2. When possible, replace large notes with smaller denominations
3. Balance notes vs coins ratio
"""
greedy_result = greedy_algorithm(amount, denominations)
# Count notes vs coins
notes = [d for d in greedy_result['breakdown']
if d['denomination'] >= note_threshold]
coins = [d for d in greedy_result['breakdown']
if d['denomination'] < note_threshold]
notes_count = sum(n['count'] for n in notes)
coins_count = sum(c['count'] for c in coins)
# If imbalanced, try to adjust
if notes_count > coins_count * 2:
# Too many notes, try using smaller denominations
return _rebalance_to_coins(amount, denominations, note_threshold)
elif coins_count > notes_count * 3:
# Too many coins, try using larger denominations
return _rebalance_to_notes(amount, denominations, note_threshold)
return greedy_result
Rebalancing Logic
def _rebalance_to_coins(amount: Decimal, denominations: List[Decimal],
note_threshold: Decimal) -> Dict:
"""Replace largest notes with smaller denominations."""
result = []
remaining = amount
sorted_denoms = sorted(denominations, reverse=True)
for denom in sorted_denoms:
if remaining >= denom:
count = int(remaining / denom)
# Limit large denominations
if denom >= note_threshold and denom >= 100:
count = min(count, 2) # Max 2 large notes
remaining -= count * denom
result.append({
'denomination': float(denom),
'count': count,
'total_value': float(count * denom)
})
return {'breakdown': result}
def _rebalance_to_notes(amount: Decimal, denominations: List[Decimal],
note_threshold: Decimal) -> Dict:
"""Consolidate coins to larger denominations."""
# Use only notes for calculation
note_denoms = [d for d in denominations if d >= note_threshold]
# Round to nearest note
rounded = (amount // min(note_denoms)) * min(note_denoms)
return greedy_algorithm(rounded, note_denoms)
12.3 Minimize Large Denominations
Implementation
def minimize_large(amount: Decimal, denominations: List[Decimal]) -> Dict:
"""
Minimize large denominations (use more smaller ones).
Strategy: Avoid largest denominations when possible
"""
result = []
remaining = amount
# Sort denominations
sorted_denoms = sorted(denominations, reverse=True)
# Skip largest denomination if possible
for i, denom in enumerate(sorted_denoms):
if i == 0 and remaining < denom * 2:
# Skip largest if amount < 2× largest denomination
continue
if remaining >= denom:
count = int(remaining / denom)
# Limit usage of large denominations
if i < 2: # First two largest
count = min(count, 2) # Max 2 pieces
remaining -= count * denom
result.append({
'denomination': float(denom),
'count': count,
'total_value': float(count * denom)
})
return {'breakdown': result, 'remaining': float(remaining)}
Example
Comparison: 3000 INR
| Mode | Breakdown | Total Pieces |
|---|---|---|
| Greedy | ?2000×1, ?500×2 | 3 pieces |
| Minimize Large | ?500×6 | 6 pieces |
Minimize Large avoids ?2000 note, uses more ?500 notes instead.
12.4 Minimize Small Denominations
Implementation
def minimize_small(amount: Decimal, denominations: List[Decimal],
note_threshold: Decimal) -> Dict:
"""
Minimize small denominations (prefer notes).
Strategy: Round to nearest note denomination
"""
# Find smallest note denomination
note_denoms = [d for d in denominations if d >= note_threshold]
smallest_note = min(note_denoms) if note_denoms else denominations[-1]
# Round amount to nearest note
rounded = (amount // smallest_note) * smallest_note
if rounded > 0:
# Use greedy on rounded amount
result = greedy_algorithm(rounded, note_denoms)
return result
else:
# Fallback to regular greedy
return greedy_algorithm(amount, denominations)
Example
Comparison: 47.50 INR
| Mode | Breakdown | Result |
|---|---|---|
| Greedy | ?20×2, ?5×1, ?2×1, ?0.50×1 | 5 pieces (4 notes, 1 coin) |
| Minimize Small | ?20×2 (rounded to ?40) | 2 pieces (2 notes, 0 coins) |
Minimize Small rounds ?47.50 ? ?40 to avoid coins entirely.
12.5 Currency Configurations
File: packages/core-engine/config/currencies.json
Indian Rupee (INR)
{
"INR": {
"name": "Indian Rupee",
"symbol": "?",
"code": "INR",
"note_threshold": 10,
"denominations": [
{"value": 2000, "type": "note"},
{"value": 500, "type": "note"},
{"value": 200, "type": "note"},
{"value": 100, "type": "note"},
{"value": 50, "type": "note"},
{"value": 20, "type": "note"},
{"value": 10, "type": "note"},
{"value": 5, "type": "coin"},
{"value": 2, "type": "coin"},
{"value": 1, "type": "coin"}
]
}
}
US Dollar (USD)
{
"USD": {
"name": "US Dollar",
"symbol": "$",
"code": "USD",
"note_threshold": 1,
"denominations": [
{"value": 100, "type": "note"},
{"value": 50, "type": "note"},
{"value": 20, "type": "note"},
{"value": 10, "type": "note"},
{"value": 5, "type": "note"},
{"value": 1, "type": "note"},
{"value": 0.25, "type": "coin"},
{"value": 0.10, "type": "coin"},
{"value": 0.05, "type": "coin"},
{"value": 0.01, "type": "coin"}
]
}
}
Euro (EUR)
{
"EUR": {
"name": "Euro",
"symbol": "€",
"code": "EUR",
"note_threshold": 5,
"denominations": [
{"value": 500, "type": "note"},
{"value": 200, "type": "note"},
{"value": 100, "type": "note"},
{"value": 50, "type": "note"},
{"value": 20, "type": "note"},
{"value": 10, "type": "note"},
{"value": 5, "type": "note"},
{"value": 2, "type": "coin"},
{"value": 1, "type": "coin"},
{"value": 0.50, "type": "coin"},
{"value": 0.20, "type": "coin"},
{"value": 0.10, "type": "coin"},
{"value": 0.05, "type": "coin"},
{"value": 0.02, "type": "coin"},
{"value": 0.01, "type": "coin"}
]
}
}
British Pound (GBP)
{
"GBP": {
"name": "British Pound",
"symbol": "£",
"code": "GBP",
"note_threshold": 5,
"denominations": [
{"value": 50, "type": "note"},
{"value": 20, "type": "note"},
{"value": 10, "type": "note"},
{"value": 5, "type": "note"},
{"value": 2, "type": "coin"},
{"value": 1, "type": "coin"},
{"value": 0.50, "type": "coin"},
{"value": 0.20, "type": "coin"},
{"value": 0.10, "type": "coin"},
{"value": 0.05, "type": "coin"},
{"value": 0.02, "type": "coin"},
{"value": 0.01, "type": "coin"}
]
}
}
12.6 Performance Characteristics
Time Complexity
| Algorithm | Complexity | Description |
|---|---|---|
| Greedy | O(n) |
n = number of denominations |
| Balanced | O(n) |
Single pass + rebalancing check |
| Minimize Large | O(n) |
Single pass with skipping logic |
| Minimize Small | O(n) |
Rounding + greedy on notes |
Space Complexity
All algorithms: O(n) for result storage
Benchmarks (Modern CPU)
| Operation | Time | Notes |
|---|---|---|
| Single calculation | < 1 ms | Any amount, any mode |
| 1,000 calculations | < 50 ms | Batch processing |
| 10,000 calculations | < 500 ms | Bulk upload scenario |
| 100 calculations (bulk) | < 100 ms | Target for UI responsiveness |
All algorithms execute in <1ms per calculation, ensuring instant UI feedback even for large bulk uploads.
12.7 Input Validation
Amount Validation
def validate_amount(amount: Decimal) -> None:
"""Validate calculation amount."""
if amount <= 0:
raise ValidationException("Amount must be greater than 0")
if amount > Decimal('1000000000000'): # 1 trillion
raise ValidationException("Amount too large (max: 1 trillion)")
# Check for reasonable decimal places (max 2)
if amount.as_tuple().exponent < -2:
raise ValidationException("Maximum 2 decimal places allowed")
Currency Validation
SUPPORTED_CURRENCIES = ['INR', 'USD', 'EUR', 'GBP']
def validate_currency(currency: str) -> None:
"""Validate currency code."""
if currency not in SUPPORTED_CURRENCIES:
raise ValidationException(
f"Unsupported currency: {currency}. "
f"Supported: {', '.join(SUPPORTED_CURRENCIES)}"
)
Mode Validation
SUPPORTED_MODES = ['greedy', 'balanced', 'minimize_large', 'minimize_small']
def validate_mode(mode: str) -> None:
"""Validate calculation mode."""
if mode not in SUPPORTED_MODES:
raise ValidationException(
f"Unsupported mode: {mode}. "
f"Supported: {', '.join(SUPPORTED_MODES)}"
)
This section documents all four calculation algorithms (Greedy, Balanced, Minimize Large, Minimize Small), complete currency configurations for INR/USD/EUR/GBP, performance benchmarks (<1ms per calculation), and comprehensive input validation.