doge_runtime/stdlib/
nerd.rs1use std::cmp::Ordering;
5
6use bigdecimal::{FromPrimitive, RoundingMode, Signed, ToPrimitive};
7use num_bigint::BigInt;
8
9use crate::error::{DogeError, DogeResult};
10use crate::value::Value;
11
12fn numeric(fname: &str, v: &Value) -> DogeResult<f64> {
15 let f = match v {
16 Value::Int(n) => n.to_f64(),
17 Value::Float(f) => Some(*f),
18 Value::Decimal(d) => d.to_f64(),
19 _ => {
20 return Err(DogeError::type_error(format!(
21 "nerd.{fname} needs a number, got {}",
22 v.describe()
23 )))
24 }
25 };
26 f.ok_or_else(|| DogeError::overflow(format!("{v} is too large for nerd.{fname}")))
27}
28
29fn ensure_number(fname: &str, v: &Value) -> DogeResult<()> {
31 if matches!(v, Value::Int(_) | Value::Float(_) | Value::Decimal(_)) {
32 Ok(())
33 } else {
34 Err(DogeError::type_error(format!(
35 "nerd.{fname} needs a number, got {}",
36 v.describe()
37 )))
38 }
39}
40
41fn float_to_int(f: f64) -> DogeResult {
45 if f.is_finite() {
46 BigInt::from_f64(f)
47 .map(Value::Int)
48 .ok_or_else(|| DogeError::overflow("the result is outside the Int range"))
49 } else {
50 Err(DogeError::overflow("the result is outside the Int range"))
51 }
52}
53
54fn round_like(
58 fname: &str,
59 x: &Value,
60 float_op: impl Fn(f64) -> f64,
61 mode: RoundingMode,
62) -> DogeResult {
63 match x {
64 Value::Int(n) => Ok(Value::Int(n.clone())),
65 Value::Float(f) => float_to_int(float_op(*f)),
66 Value::Decimal(d) => {
67 let (digits, _) = d.with_scale_round(0, mode).into_bigint_and_exponent();
68 Ok(Value::Int(digits))
69 }
70 _ => Err(DogeError::type_error(format!(
71 "nerd.{fname} needs a number, got {}",
72 x.describe()
73 ))),
74 }
75}
76
77pub fn nerd_abs(x: &Value) -> DogeResult {
81 match x {
82 Value::Int(n) => Ok(Value::Int(n.abs())),
83 Value::Float(f) => Ok(Value::Float(f.abs())),
84 Value::Decimal(d) => Ok(Value::decimal(d.abs())),
85 _ => Err(DogeError::type_error(format!(
86 "nerd.abs needs a number, got {}",
87 x.describe()
88 ))),
89 }
90}
91
92pub fn nerd_sqrt(x: &Value) -> DogeResult {
94 let f = numeric("sqrt", x)?;
95 if f < 0.0 {
96 return Err(DogeError::value_error("cannot sqrt a negative number"));
97 }
98 Ok(Value::Float(f.sqrt()))
99}
100
101pub fn nerd_floor(x: &Value) -> DogeResult {
103 round_like("floor", x, f64::floor, RoundingMode::Floor)
104}
105
106pub fn nerd_ceil(x: &Value) -> DogeResult {
108 round_like("ceil", x, f64::ceil, RoundingMode::Ceiling)
109}
110
111pub fn nerd_round(x: &Value) -> DogeResult {
113 round_like("round", x, f64::round, RoundingMode::HalfUp)
114}
115
116pub fn nerd_min(a: &Value, b: &Value) -> DogeResult {
120 ensure_number("min", a)?;
121 ensure_number("min", b)?;
122 Ok(if crate::ops::order(a, b)? == Ordering::Greater {
123 b.clone()
124 } else {
125 a.clone()
126 })
127}
128
129pub fn nerd_max(a: &Value, b: &Value) -> DogeResult {
132 ensure_number("max", a)?;
133 ensure_number("max", b)?;
134 Ok(if crate::ops::order(a, b)? == Ordering::Less {
135 b.clone()
136 } else {
137 a.clone()
138 })
139}
140
141pub fn nerd_pow(a: &Value, b: &Value) -> DogeResult {
145 crate::ops::pow(a.clone(), b.clone())
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::error::ErrorKind;
152 use crate::ops::values_equal;
153
154 fn dec(s: &str) -> Value {
155 Value::decimal(s.parse().unwrap())
156 }
157
158 #[test]
159 fn abs_keeps_type_and_never_overflows() {
160 assert!(values_equal(
161 &nerd_abs(&Value::int(-5)).unwrap(),
162 &Value::int(5)
163 ));
164 assert!(matches!(nerd_abs(&Value::Float(-2.5)).unwrap(), Value::Float(f) if f == 2.5));
165 assert!(values_equal(&nerd_abs(&dec("-2.5")).unwrap(), &dec("2.5")));
166 let expected = Value::Int(-BigInt::from(i64::MIN));
168 assert!(values_equal(
169 &nerd_abs(&Value::int(i64::MIN)).unwrap(),
170 &expected
171 ));
172 assert_eq!(
173 nerd_abs(&Value::str("x")).unwrap_err().kind,
174 ErrorKind::TypeError
175 );
176 }
177
178 #[test]
179 fn sqrt_is_float_and_rejects_negatives() {
180 assert!(matches!(nerd_sqrt(&Value::int(16)).unwrap(), Value::Float(f) if f == 4.0));
181 assert_eq!(
182 nerd_sqrt(&Value::int(-1)).unwrap_err().kind,
183 ErrorKind::ValueError
184 );
185 }
186
187 #[test]
188 fn floor_ceil_round_yield_ints() {
189 assert!(values_equal(
190 &nerd_floor(&Value::Float(2.9)).unwrap(),
191 &Value::int(2)
192 ));
193 assert!(values_equal(
194 &nerd_ceil(&Value::Float(2.1)).unwrap(),
195 &Value::int(3)
196 ));
197 assert!(values_equal(
198 &nerd_round(&Value::Float(2.5)).unwrap(),
199 &Value::int(3)
200 ));
201 assert!(values_equal(
202 &nerd_floor(&Value::int(7)).unwrap(),
203 &Value::int(7)
204 ));
205 assert!(values_equal(
207 &nerd_floor(&dec("2.9")).unwrap(),
208 &Value::int(2)
209 ));
210 assert!(values_equal(
211 &nerd_ceil(&dec("-2.9")).unwrap(),
212 &Value::int(-2)
213 ));
214 assert!(values_equal(
215 &nerd_round(&dec("2.5")).unwrap(),
216 &Value::int(3)
217 ));
218 }
219
220 #[test]
221 fn round_of_a_non_finite_float_is_overflow() {
222 assert_eq!(
223 nerd_floor(&Value::Float(f64::INFINITY)).unwrap_err().kind,
224 ErrorKind::Overflow
225 );
226 }
227
228 #[test]
229 fn min_max_keep_the_winners_type() {
230 assert!(
231 matches!(nerd_min(&Value::int(3), &Value::Float(2.5)).unwrap(), Value::Float(f) if f == 2.5)
232 );
233 assert!(values_equal(
234 &nerd_max(&Value::int(3), &Value::Float(2.5)).unwrap(),
235 &Value::int(3)
236 ));
237 assert!(values_equal(
239 &nerd_min(&Value::int(3), &dec("1.5")).unwrap(),
240 &dec("1.5")
241 ));
242 assert!(values_equal(
244 &nerd_min(&Value::int(4), &Value::Float(4.0)).unwrap(),
245 &Value::int(4)
246 ));
247 assert_eq!(
248 nerd_min(&Value::str("a"), &Value::str("b"))
249 .unwrap_err()
250 .kind,
251 ErrorKind::TypeError
252 );
253 }
254
255 #[test]
256 fn pow_is_int_for_int_base_and_grows_past_i64() {
257 assert!(values_equal(
258 &nerd_pow(&Value::int(2), &Value::int(10)).unwrap(),
259 &Value::int(1024)
260 ));
261 assert!(matches!(
262 nerd_pow(&Value::int(2), &Value::Float(0.5)).unwrap(),
263 Value::Float(_)
264 ));
265 let expected = Value::Int(BigInt::from(10).pow(100u32));
267 assert!(values_equal(
268 &nerd_pow(&Value::int(10), &Value::int(100)).unwrap(),
269 &expected
270 ));
271 }
272}