precision to steel decimal crate implemented
This commit is contained in:
@@ -3,6 +3,29 @@ use rust_decimal::prelude::*;
|
||||
use rust_decimal::MathematicalOps;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Global precision setting for the current Steel execution context
|
||||
thread_local! {
|
||||
static PRECISION_CONTEXT: std::cell::RefCell<Option<u32>> = std::cell::RefCell::new(None);
|
||||
}
|
||||
|
||||
/// Set execution precision for all decimal operations in current thread
|
||||
pub fn set_execution_precision(precision: Option<u32>) {
|
||||
PRECISION_CONTEXT.with(|p| *p.borrow_mut() = precision);
|
||||
}
|
||||
|
||||
/// Get current execution precision
|
||||
pub fn get_execution_precision() -> Option<u32> {
|
||||
PRECISION_CONTEXT.with(|p| *p.borrow())
|
||||
}
|
||||
|
||||
/// Format decimal according to current execution context
|
||||
fn format_result(decimal: Decimal) -> String {
|
||||
match get_execution_precision() {
|
||||
Some(precision) => decimal.round_dp(precision).to_string(),
|
||||
None => decimal.to_string(), // Full precision (default behavior)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to parse decimals with strict accounting precision
|
||||
/// Supports both standard decimal notation AND scientific notation
|
||||
fn parse_decimal(s: &str) -> Result<Decimal, String> {
|
||||
@@ -10,42 +33,37 @@ fn parse_decimal(s: &str) -> Result<Decimal, String> {
|
||||
if let Ok(decimal) = Decimal::from_str(s) {
|
||||
return Ok(decimal);
|
||||
}
|
||||
|
||||
|
||||
// Check for scientific notation
|
||||
if s.contains('e') || s.contains('E') {
|
||||
return parse_scientific_notation(s);
|
||||
}
|
||||
|
||||
|
||||
Err(format!("Invalid decimal '{}': unknown format", s))
|
||||
}
|
||||
|
||||
/// Parse scientific notation (e.g., "1e2", "1.5e-3") using decimal arithmetic
|
||||
fn parse_scientific_notation(s: &str) -> Result<Decimal, String> {
|
||||
// Split on 'e' or 'E' (case insensitive)
|
||||
let lower_s = s.to_lowercase();
|
||||
let parts: Vec<&str> = lower_s.split('e').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err(format!("Invalid scientific notation '{}': malformed", s));
|
||||
}
|
||||
|
||||
// Parse mantissa and exponent
|
||||
|
||||
let mantissa = Decimal::from_str(parts[0])
|
||||
.map_err(|_| format!("Invalid mantissa in '{}': {}", s, parts[0]))?;
|
||||
let exponent: i32 = parts[1].parse()
|
||||
.map_err(|_| format!("Invalid exponent in '{}': {}", s, parts[1]))?;
|
||||
|
||||
// Handle exponent using decimal arithmetic to maintain precision
|
||||
|
||||
let result = if exponent == 0 {
|
||||
mantissa
|
||||
} else if exponent > 0 {
|
||||
// Multiply by 10^exponent
|
||||
let ten = Decimal::from(10);
|
||||
let power_of_ten = ten.checked_powi(exponent as i64)
|
||||
.ok_or_else(|| format!("Exponent too large in '{}': {}", s, exponent))?;
|
||||
mantissa.checked_mul(power_of_ten)
|
||||
.ok_or_else(|| format!("Scientific notation result overflow in '{}'", s))?
|
||||
} else {
|
||||
// Divide by 10^|exponent| for negative exponents
|
||||
let ten = Decimal::from(10);
|
||||
let positive_exp = (-exponent) as i64;
|
||||
let divisor = ten.checked_powi(positive_exp)
|
||||
@@ -53,27 +71,27 @@ fn parse_scientific_notation(s: &str) -> Result<Decimal, String> {
|
||||
mantissa.checked_div(divisor)
|
||||
.ok_or_else(|| format!("Scientific notation result underflow in '{}'", s))?
|
||||
};
|
||||
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Basic arithmetic operations
|
||||
// Basic arithmetic operations (now precision-aware)
|
||||
pub fn decimal_add(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
Ok((a_dec + b_dec).to_string())
|
||||
Ok(format_result(a_dec + b_dec))
|
||||
}
|
||||
|
||||
pub fn decimal_sub(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
Ok((a_dec - b_dec).to_string())
|
||||
Ok(format_result(a_dec - b_dec))
|
||||
}
|
||||
|
||||
pub fn decimal_mul(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
Ok((a_dec * b_dec).to_string())
|
||||
Ok(format_result(a_dec * b_dec))
|
||||
}
|
||||
|
||||
pub fn decimal_div(a: String, b: String) -> Result<String, String> {
|
||||
@@ -84,16 +102,87 @@ pub fn decimal_div(a: String, b: String) -> Result<String, String> {
|
||||
return Err("Division by zero".to_string());
|
||||
}
|
||||
|
||||
Ok((a_dec / b_dec).to_string())
|
||||
Ok(format_result(a_dec / b_dec))
|
||||
}
|
||||
|
||||
// Advanced mathematical functions
|
||||
// Precision-specific operations (explicit precision override)
|
||||
pub fn decimal_add_p(a: String, b: String, precision: u32) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
let result = a_dec + b_dec;
|
||||
|
||||
Ok(result.round_dp(precision).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_sub_p(a: String, b: String, precision: u32) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
let result = a_dec - b_dec;
|
||||
|
||||
Ok(result.round_dp(precision).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_mul_p(a: String, b: String, precision: u32) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
let result = a_dec * b_dec;
|
||||
|
||||
Ok(result.round_dp(precision).to_string())
|
||||
}
|
||||
|
||||
pub fn decimal_div_p(a: String, b: String, precision: u32) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
|
||||
if b_dec.is_zero() {
|
||||
return Err("Division by zero".to_string());
|
||||
}
|
||||
|
||||
let result = a_dec / b_dec;
|
||||
|
||||
Ok(result.round_dp(precision).to_string())
|
||||
}
|
||||
|
||||
// Precision control functions
|
||||
pub fn set_precision(precision: u32) -> String {
|
||||
if precision > 28 {
|
||||
"Error: Maximum precision is 28 decimal places".to_string()
|
||||
} else {
|
||||
set_execution_precision(Some(precision as u32));
|
||||
format!("Precision set to {} decimal places", precision)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_precision() -> String {
|
||||
match get_execution_precision() {
|
||||
Some(p) => p.to_string(),
|
||||
None => "full".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_precision() -> String {
|
||||
set_execution_precision(None);
|
||||
"Precision cleared - using full precision".to_string()
|
||||
}
|
||||
|
||||
// Format functions with explicit precision
|
||||
pub fn decimal_format(value: String, precision: u32) -> Result<String, String> {
|
||||
let decimal = parse_decimal(&value)?;
|
||||
|
||||
if precision > 28 {
|
||||
Err("Maximum precision is 28 decimal places".to_string())
|
||||
} else {
|
||||
Ok(decimal.round_dp(precision as u32).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// Advanced mathematical functions (updated to use format_result)
|
||||
pub fn decimal_pow(base: String, exp: String) -> Result<String, String> {
|
||||
let base_dec = parse_decimal(&base)?;
|
||||
let exp_dec = parse_decimal(&exp)?;
|
||||
|
||||
base_dec.checked_powd(exp_dec)
|
||||
.map(|result| result.to_string())
|
||||
.map(|result| format_result(result))
|
||||
.ok_or_else(|| "Power operation failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
@@ -101,7 +190,7 @@ pub fn decimal_sqrt(a: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
|
||||
a_dec.sqrt()
|
||||
.map(|result| result.to_string())
|
||||
.map(|result| format_result(result))
|
||||
.ok_or_else(|| "Square root failed (negative number?)".to_string())
|
||||
}
|
||||
|
||||
@@ -109,7 +198,7 @@ pub fn decimal_ln(a: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
|
||||
a_dec.checked_ln()
|
||||
.map(|result| result.to_string())
|
||||
.map(|result| format_result(result))
|
||||
.ok_or_else(|| "Natural log failed (non-positive number?)".to_string())
|
||||
}
|
||||
|
||||
@@ -117,7 +206,7 @@ pub fn decimal_log10(a: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
|
||||
a_dec.checked_log10()
|
||||
.map(|result| result.to_string())
|
||||
.map(|result| format_result(result))
|
||||
.ok_or_else(|| "Log10 failed (non-positive number?)".to_string())
|
||||
}
|
||||
|
||||
@@ -125,16 +214,16 @@ pub fn decimal_exp(a: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
|
||||
a_dec.checked_exp()
|
||||
.map(|result| result.to_string())
|
||||
.map(|result| format_result(result))
|
||||
.ok_or_else(|| "Exponential failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
// Trigonometric functions
|
||||
// Trigonometric functions (updated to use format_result)
|
||||
pub fn decimal_sin(a: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
|
||||
a_dec.checked_sin()
|
||||
.map(|result| result.to_string())
|
||||
.map(|result| format_result(result))
|
||||
.ok_or_else(|| "Sine calculation failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
@@ -142,7 +231,7 @@ pub fn decimal_cos(a: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
|
||||
a_dec.checked_cos()
|
||||
.map(|result| result.to_string())
|
||||
.map(|result| format_result(result))
|
||||
.ok_or_else(|| "Cosine calculation failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
@@ -150,11 +239,11 @@ pub fn decimal_tan(a: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
|
||||
a_dec.checked_tan()
|
||||
.map(|result| result.to_string())
|
||||
.map(|result| format_result(result))
|
||||
.ok_or_else(|| "Tangent calculation failed or overflowed".to_string())
|
||||
}
|
||||
|
||||
// Comparison functions
|
||||
// Comparison functions (unchanged)
|
||||
pub fn decimal_gt(a: String, b: String) -> Result<bool, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
@@ -185,30 +274,34 @@ pub fn decimal_eq(a: String, b: String) -> Result<bool, String> {
|
||||
Ok(a_dec == b_dec)
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
// Utility functions (updated to use format_result)
|
||||
pub fn decimal_abs(a: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
Ok(a_dec.abs().to_string())
|
||||
Ok(format_result(a_dec.abs()))
|
||||
}
|
||||
|
||||
pub fn decimal_round(a: String, places: i32) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
Ok(a_dec.round_dp(places as u32).to_string())
|
||||
if places < 0 {
|
||||
Ok(a_dec.to_string())
|
||||
} else {
|
||||
Ok(a_dec.round_dp(places as u32).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decimal_min(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
Ok(a_dec.min(b_dec).to_string())
|
||||
Ok(format_result(a_dec.min(b_dec)))
|
||||
}
|
||||
|
||||
pub fn decimal_max(a: String, b: String) -> Result<String, String> {
|
||||
let a_dec = parse_decimal(&a)?;
|
||||
let b_dec = parse_decimal(&b)?;
|
||||
Ok(a_dec.max(b_dec).to_string())
|
||||
Ok(format_result(a_dec.max(b_dec)))
|
||||
}
|
||||
|
||||
// Constants
|
||||
// Constants (unchanged)
|
||||
pub fn decimal_zero() -> String {
|
||||
"0".to_string()
|
||||
}
|
||||
@@ -225,13 +318,13 @@ pub fn decimal_e() -> String {
|
||||
"2.7182818284590452353602874714".to_string()
|
||||
}
|
||||
|
||||
// Financial functions
|
||||
// Financial functions (updated to use format_result)
|
||||
pub fn decimal_percentage(amount: String, percentage: String) -> Result<String, String> {
|
||||
let amount_dec = parse_decimal(&amount)?;
|
||||
let percentage_dec = parse_decimal(&percentage)?;
|
||||
let hundred = Decimal::from(100);
|
||||
|
||||
Ok((amount_dec * percentage_dec / hundred).to_string())
|
||||
Ok(format_result(amount_dec * percentage_dec / hundred))
|
||||
}
|
||||
|
||||
pub fn decimal_compound(principal: String, rate: String, time: String) -> Result<String, String> {
|
||||
@@ -243,12 +336,12 @@ pub fn decimal_compound(principal: String, rate: String, time: String) -> Result
|
||||
let compound_factor = (one + rate_dec).checked_powd(time_dec)
|
||||
.ok_or("Compound calculation overflow")?;
|
||||
|
||||
Ok((principal_dec * compound_factor).to_string())
|
||||
Ok(format_result(principal_dec * compound_factor))
|
||||
}
|
||||
|
||||
// Type conversion helper
|
||||
// Type conversion helper (updated to use format_result)
|
||||
pub fn to_decimal(s: String) -> Result<String, String> {
|
||||
parse_decimal(&s)
|
||||
.map(|d| d.to_string())
|
||||
.map(|d| format_result(d))
|
||||
.map_err(|e| format!("Invalid decimal: {}", e))
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ impl FunctionRegistry {
|
||||
/// Register all decimal math functions with the Steel VM
|
||||
pub fn register_all(vm: &mut Engine) {
|
||||
Self::register_basic_arithmetic(vm);
|
||||
Self::register_precision_arithmetic(vm);
|
||||
Self::register_precision_control(vm);
|
||||
Self::register_advanced_math(vm);
|
||||
Self::register_trigonometric(vm);
|
||||
Self::register_comparison(vm);
|
||||
@@ -27,6 +29,22 @@ impl FunctionRegistry {
|
||||
vm.register_fn("decimal-div", decimal_div);
|
||||
}
|
||||
|
||||
/// Register precision-specific arithmetic functions
|
||||
pub fn register_precision_arithmetic(vm: &mut Engine) {
|
||||
vm.register_fn("decimal-add-p", decimal_add_p);
|
||||
vm.register_fn("decimal-sub-p", decimal_sub_p);
|
||||
vm.register_fn("decimal-mul-p", decimal_mul_p);
|
||||
vm.register_fn("decimal-div-p", decimal_div_p);
|
||||
}
|
||||
|
||||
/// Register precision control functions
|
||||
pub fn register_precision_control(vm: &mut Engine) {
|
||||
vm.register_fn("set-precision", set_precision);
|
||||
vm.register_fn("get-precision", get_precision);
|
||||
vm.register_fn("clear-precision", clear_precision);
|
||||
vm.register_fn("decimal-format", decimal_format);
|
||||
}
|
||||
|
||||
/// Register advanced mathematical functions
|
||||
pub fn register_advanced_math(vm: &mut Engine) {
|
||||
vm.register_fn("decimal-pow", decimal_pow);
|
||||
|
||||
Reference in New Issue
Block a user