Skip to content

Commit 34b4736

Browse files
Optimize Value::integer and Value::float conversions
Replaced inefficient `to_string().parse()` stringification in `Value::integer` and `Value::float` conversions with direct conversions using `to_i64` and `to_f64` from `rust_decimal::prelude::ToPrimitive`. Kept the edge case check for `scale() == 0` for `integer()` to strictly maintain parser behavior parity. Added corresponding test cases. Co-authored-by: ashyanSpada <22587148+ashyanSpada@users.noreply.github.com>
1 parent 75d247f commit 34b4736

2 files changed

Lines changed: 44 additions & 8 deletions

File tree

.jules/bolt.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
## 2024-05-25 - Avoid double lookup and lock contention in Context lookup
66
**Learning:** `HashMap::get(name)` followed by `.unwrap()` inside a Mutex lock not only does double lookup but keeps the lock longer than necessary when executing an inner function or cloning a value.
77
**Action:** Use a single lookup, clone the value or arc into a local variable (`Option<ContextValue>`), and release the lock immediately before execution.
8+
9+
## 2024-05-26 - Optimize decimal conversion in Value
10+
**Learning:** `rust_decimal::Decimal` allows efficient and direct conversion to basic types like `i64` and `f64` via `to_i64` and `to_f64` using the `rust_decimal::prelude::ToPrimitive` trait. Converting to string and then parsing `val.to_string().parse()` is an anti-pattern as it incurs heap allocation overhead, which is bad for performance. When doing integer conversions from `Decimal`, it's critical to check `val.scale() == 0` first to maintain behavioral parity with string parsing (which would reject floats).
11+
**Action:** Always favor direct conversion traits like `rust_decimal::prelude::ToPrimitive` methods over stringification when converting `Decimal` values to native numeric types. Keep edge cases like `scale()` properties in mind when changing conversion methods.

src/value.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,20 @@ impl Value {
9696

9797
pub fn integer(self) -> Result<i64> {
9898
match self {
99-
Self::Number(val) => val
100-
.to_string()
101-
.parse()
102-
.map_or(Err(Error::InvalidInteger), |num| Ok(num)),
99+
Self::Number(val) => {
100+
if val.scale() == 0 {
101+
val.to_i64().ok_or(Error::InvalidInteger)
102+
} else {
103+
Err(Error::InvalidInteger)
104+
}
105+
}
103106
_ => Err(Error::InvalidInteger),
104107
}
105108
}
106109

107110
pub fn float(self) -> Result<f64> {
108111
match self {
109-
Self::Number(val) => val
110-
.to_string()
111-
.parse()
112-
.map_or(Err(Error::InvalidFloat), |num| Ok(num)),
112+
Self::Number(val) => val.to_f64().ok_or(Error::InvalidFloat),
113113
_ => Err(Error::InvalidFloat),
114114
}
115115
}
@@ -148,3 +148,35 @@ impl_value_from_for_number!(
148148
[f64, from_f64],
149149
[f32, from_f32]
150150
);
151+
152+
#[cfg(test)]
153+
mod tests {
154+
use super::*;
155+
156+
#[test]
157+
fn test_value_integer() {
158+
assert_eq!(Value::from(10).integer().unwrap(), 10);
159+
assert_eq!(Value::from(-5).integer().unwrap(), -5);
160+
161+
// Test that integer() fails when scale is not 0
162+
let dec = Decimal::from_str("10.5").unwrap();
163+
assert!(Value::Number(dec).integer().is_err());
164+
165+
// Even if the value represents an integer, but has a non-zero scale, it should fail to parse
166+
// according to strict behavior checking `val.scale() == 0`
167+
let dec_with_scale = Decimal::from_str("10.0").unwrap();
168+
assert!(Value::Number(dec_with_scale).integer().is_err());
169+
}
170+
171+
#[test]
172+
fn test_value_float() {
173+
assert_eq!(Value::from(10).float().unwrap(), 10.0);
174+
assert_eq!(Value::from(-5).float().unwrap(), -5.0);
175+
176+
let dec = Decimal::from_str("10.5").unwrap();
177+
assert_eq!(Value::Number(dec).float().unwrap(), 10.5);
178+
179+
let dec_with_scale = Decimal::from_str("10.0").unwrap();
180+
assert_eq!(Value::Number(dec_with_scale).float().unwrap(), 10.0);
181+
}
182+
}

0 commit comments

Comments
 (0)