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