Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-ops/src/series/ops/round.rs
6939 views
1
use polars_core::prelude::*;
2
use polars_core::with_match_physical_numeric_polars_type;
3
#[cfg(feature = "serde")]
4
use serde::{Deserialize, Serialize};
5
use strum_macros::IntoStaticStr;
6
7
use crate::series::ops::SeriesSealed;
8
9
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoStaticStr)]
10
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11
#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
12
#[strum(serialize_all = "snake_case")]
13
#[derive(Default)]
14
pub enum RoundMode {
15
#[default]
16
HalfToEven,
17
HalfAwayFromZero,
18
}
19
20
pub trait RoundSeries: SeriesSealed {
21
/// Round underlying floating point array to given decimal.
22
fn round(&self, decimals: u32, mode: RoundMode) -> PolarsResult<Series> {
23
let s = self.as_series();
24
25
if let Ok(ca) = s.f32() {
26
match mode {
27
RoundMode::HalfToEven => {
28
return if decimals == 0 {
29
let s = ca.apply_values(|val| val.round_ties_even()).into_series();
30
Ok(s)
31
} else if decimals >= 326 {
32
// More precise than smallest denormal.
33
Ok(s.clone())
34
} else {
35
// Note we do the computation on f64 floats to not lose precision
36
// when the computation is done, we cast to f32
37
let multiplier = 10.0_f64.powi(decimals as i32);
38
let s = ca
39
.apply_values(|val| {
40
let ret = ((val as f64 * multiplier).round_ties_even() / multiplier)
41
as f32;
42
if ret.is_finite() {
43
ret
44
} else {
45
// We return the original value which is correct both for overflows and non-finite inputs.
46
val
47
}
48
})
49
.into_series();
50
Ok(s)
51
};
52
},
53
RoundMode::HalfAwayFromZero => {
54
return if decimals == 0 {
55
let s = ca.apply_values(|val| val.round()).into_series();
56
Ok(s)
57
} else if decimals >= 326 {
58
// More precise than smallest denormal.
59
Ok(s.clone())
60
} else {
61
// Note we do the computation on f64 floats to not lose precision
62
// when the computation is done, we cast to f32
63
let multiplier = 10.0_f64.powi(decimals as i32);
64
let s = ca
65
.apply_values(|val| {
66
let ret = ((val as f64 * multiplier).round_ties_even() / multiplier)
67
as f32;
68
if ret.is_finite() {
69
ret
70
} else {
71
// We return the original value which is correct both for overflows and non-finite inputs.
72
val
73
}
74
})
75
.into_series();
76
Ok(s)
77
};
78
},
79
}
80
}
81
if let Ok(ca) = s.f64() {
82
match mode {
83
RoundMode::HalfToEven => {
84
return if decimals == 0 {
85
let s = ca.apply_values(|val| val.round_ties_even()).into_series();
86
Ok(s)
87
} else if decimals >= 326 {
88
// More precise than smallest denormal.
89
Ok(s.clone())
90
} else if decimals >= 300 {
91
// We're getting into unrepresentable territory for the multiplier
92
// here, split up the 10^n multiplier into 2^n and 5^n.
93
let mul2 = libm::scalbn(1.0, decimals as i32);
94
let invmul2 = 1.0 / mul2; // Still exact for any valid value of decimals.
95
let mul5 = 5.0_f64.powi(decimals as i32);
96
let s = ca
97
.apply_values(|val| {
98
let ret = (val * mul2 * mul5).round_ties_even() / mul5 * invmul2;
99
if ret.is_finite() {
100
ret
101
} else {
102
// We return the original value which is correct both for overflows and non-finite inputs.
103
val
104
}
105
})
106
.into_series();
107
Ok(s)
108
} else {
109
let multiplier = 10.0_f64.powi(decimals as i32);
110
let s = ca
111
.apply_values(|val| {
112
let ret = (val * multiplier).round_ties_even() / multiplier;
113
if ret.is_finite() {
114
ret
115
} else {
116
// We return the original value which is correct both for overflows and non-finite inputs.
117
val
118
}
119
})
120
.into_series();
121
Ok(s)
122
};
123
},
124
RoundMode::HalfAwayFromZero => {
125
return if decimals == 0 {
126
let s = ca.apply_values(|val| val.round()).into_series();
127
Ok(s)
128
} else if decimals >= 326 {
129
// More precise than smallest denormal.
130
Ok(s.clone())
131
} else if decimals >= 300 {
132
// We're getting into unrepresentable territory for the multiplier
133
// here, split up the 10^n multiplier into 2^n and 5^n.
134
let mul2 = libm::scalbn(1.0, decimals as i32);
135
let invmul2 = 1.0 / mul2; // Still exact for any valid value of decimals.
136
let mul5 = 5.0_f64.powi(decimals as i32);
137
let s = ca
138
.apply_values(|val| {
139
let ret = (val * mul2 * mul5).round() / mul5 * invmul2;
140
if ret.is_finite() {
141
ret
142
} else {
143
// We return the original value which is correct both for overflows and non-finite inputs.
144
val
145
}
146
})
147
.into_series();
148
Ok(s)
149
} else {
150
let multiplier = 10.0_f64.powi(decimals as i32);
151
let s = ca
152
.apply_values(|val| {
153
let ret = (val * multiplier).round() / multiplier;
154
if ret.is_finite() {
155
ret
156
} else {
157
// We return the original value which is correct both for overflows and non-finite inputs.
158
val
159
}
160
})
161
.into_series();
162
Ok(s)
163
};
164
},
165
}
166
}
167
#[cfg(feature = "dtype-decimal")]
168
if let Some(ca) = s.try_decimal() {
169
let scale = ca.scale() as u32;
170
171
if scale <= decimals {
172
return Ok(ca.clone().into_series());
173
}
174
175
let decimal_delta = scale - decimals;
176
let multiplier = 10i128.pow(decimal_delta);
177
let threshold = multiplier / 2;
178
179
let res = match mode {
180
RoundMode::HalfToEven => ca.physical().apply_values(|v| {
181
let rem_big = v % (2 * multiplier);
182
let is_v_floor_even = rem_big.abs() < multiplier;
183
let rem = if is_v_floor_even {
184
rem_big
185
} else if rem_big > 0 {
186
rem_big - multiplier
187
} else {
188
rem_big + multiplier
189
};
190
191
let threshold = threshold + i128::from(is_v_floor_even);
192
let round_offset = if rem.abs() >= threshold {
193
if v < 0 { -multiplier } else { multiplier }
194
} else {
195
0
196
};
197
v - rem + round_offset
198
}),
199
RoundMode::HalfAwayFromZero => ca.physical().apply_values(|v| {
200
let rem = v % multiplier;
201
let round_offset = if rem.abs() >= threshold {
202
if v < 0 { -multiplier } else { multiplier }
203
} else {
204
0
205
};
206
v - rem + round_offset
207
}),
208
};
209
return Ok(res
210
.into_decimal_unchecked(ca.precision(), scale as usize)
211
.into_series());
212
}
213
214
polars_ensure!(s.dtype().is_integer(), InvalidOperation: "round can only be used on numeric types" );
215
Ok(s.clone())
216
}
217
218
fn round_sig_figs(&self, digits: i32) -> PolarsResult<Series> {
219
let s = self.as_series();
220
polars_ensure!(digits >= 1, InvalidOperation: "digits must be an integer >= 1");
221
222
#[cfg(feature = "dtype-decimal")]
223
if let Some(ca) = s.try_decimal() {
224
let precision = ca.precision();
225
let scale = ca.scale() as u32;
226
227
let s = ca
228
.physical()
229
.apply_values(|v| {
230
if v == 0 {
231
return 0;
232
}
233
234
let mut magnitude = v.abs().ilog10();
235
let magnitude_mult = 10i128.pow(magnitude); // @Q? It might be better to do this with a
236
// LUT.
237
if v.abs() > magnitude_mult {
238
magnitude += 1;
239
}
240
let decimals = magnitude.saturating_sub(digits as u32);
241
let multiplier = 10i128.pow(decimals); // @Q? It might be better to do this with a
242
// LUT.
243
let threshold = multiplier / 2;
244
245
// We use rounding=ROUND_HALF_EVEN
246
let rem = v % multiplier;
247
let is_v_floor_even = decimals <= scale && ((v - rem) / multiplier) % 2 == 0;
248
let threshold = threshold + i128::from(is_v_floor_even);
249
let round_offset = if rem.abs() >= threshold {
250
multiplier
251
} else {
252
0
253
};
254
let round_offset = if v < 0 { -round_offset } else { round_offset };
255
v - rem + round_offset
256
})
257
.into_decimal_unchecked(precision, scale as usize)
258
.into_series();
259
260
return Ok(s);
261
}
262
263
polars_ensure!(s.dtype().is_primitive_numeric(), InvalidOperation: "round_sig_figs can only be used on numeric types" );
264
with_match_physical_numeric_polars_type!(s.dtype(), |$T| {
265
let ca: &ChunkedArray<$T> = s.as_ref().as_ref().as_ref();
266
let s = ca.apply_values(|value| {
267
let value = value as f64;
268
if value == 0.0 {
269
return value as <$T as PolarsNumericType>::Native;
270
}
271
// To deal with very large/small numbers we split up 10^n in 5^n and 2^n.
272
// The scaling by 2^n is almost always lossless.
273
let exp = digits - 1 - value.abs().log10().floor() as i32;
274
let pow5 = 5.0_f64.powi(exp);
275
let scaled = libm::scalbn(value, exp) * pow5;
276
let descaled = libm::scalbn(scaled.round() / pow5, -exp);
277
if descaled.is_finite() {
278
descaled as <$T as PolarsNumericType>::Native
279
} else {
280
value as <$T as PolarsNumericType>::Native
281
}
282
}).into_series();
283
return Ok(s);
284
});
285
}
286
287
/// Floor underlying floating point array to the lowest integers smaller or equal to the float value.
288
fn floor(&self) -> PolarsResult<Series> {
289
let s = self.as_series();
290
291
if let Ok(ca) = s.f32() {
292
let s = ca.apply_values(|val| val.floor()).into_series();
293
return Ok(s);
294
}
295
if let Ok(ca) = s.f64() {
296
let s = ca.apply_values(|val| val.floor()).into_series();
297
return Ok(s);
298
}
299
#[cfg(feature = "dtype-decimal")]
300
if let Some(ca) = s.try_decimal() {
301
let precision = ca.precision();
302
let scale = ca.scale() as u32;
303
if scale == 0 {
304
return Ok(ca.clone().into_series());
305
}
306
307
let decimal_delta = scale;
308
let multiplier = 10i128.pow(decimal_delta);
309
310
let ca = ca
311
.physical()
312
.apply_values(|v| {
313
let rem = v % multiplier;
314
let round_offset = if v < 0 { multiplier + rem } else { rem };
315
let round_offset = if rem == 0 { 0 } else { round_offset };
316
v - round_offset
317
})
318
.into_decimal_unchecked(precision, scale as usize);
319
320
return Ok(ca.into_series());
321
}
322
323
polars_ensure!(s.dtype().is_primitive_numeric(), InvalidOperation: "floor can only be used on numeric types" );
324
Ok(s.clone())
325
}
326
327
/// Ceil underlying floating point array to the highest integers smaller or equal to the float value.
328
fn ceil(&self) -> PolarsResult<Series> {
329
let s = self.as_series();
330
331
if let Ok(ca) = s.f32() {
332
let s = ca.apply_values(|val| val.ceil()).into_series();
333
return Ok(s);
334
}
335
if let Ok(ca) = s.f64() {
336
let s = ca.apply_values(|val| val.ceil()).into_series();
337
return Ok(s);
338
}
339
#[cfg(feature = "dtype-decimal")]
340
if let Some(ca) = s.try_decimal() {
341
let precision = ca.precision();
342
let scale = ca.scale() as u32;
343
if scale == 0 {
344
return Ok(ca.clone().into_series());
345
}
346
347
let decimal_delta = scale;
348
let multiplier = 10i128.pow(decimal_delta);
349
350
let ca = ca
351
.physical()
352
.apply_values(|v| {
353
let rem = v % multiplier;
354
let round_offset = if v < 0 { -rem } else { multiplier - rem };
355
let round_offset = if rem == 0 { 0 } else { round_offset };
356
v + round_offset
357
})
358
.into_decimal_unchecked(precision, scale as usize);
359
360
return Ok(ca.into_series());
361
}
362
363
polars_ensure!(s.dtype().is_primitive_numeric(), InvalidOperation: "ceil can only be used on numeric types" );
364
Ok(s.clone())
365
}
366
}
367
368
impl RoundSeries for Series {}
369
370
#[cfg(test)]
371
mod test {
372
use super::*;
373
374
#[test]
375
fn test_round_series() {
376
let series = Series::new("a".into(), &[1.003, 2.23222, 3.4352]);
377
let out = series.round(2, RoundMode::default()).unwrap();
378
let ca = out.f64().unwrap();
379
assert_eq!(ca.get(0), Some(1.0));
380
}
381
}
382
383