Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/polars-core/src/chunked_array/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,9 @@ impl ChunkCast for StringChunked {
.into_series())
},
(None, None) => self.to_decimal(100),
_ => {
polars_bail!(ComputeError: "expected 'precision' or 'scale' when casting to Decimal")
(_, None) => {
polars_warn!("when inferring scale in string->decimal cast, precision is currently ignored");
self.to_decimal(100)
},
},
#[cfg(feature = "dtype-date")]
Expand Down
14 changes: 14 additions & 0 deletions crates/polars-core/src/series/any_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ fn any_values_to_decimal(
0 // integers are treated as decimals with scale of zero
} else if let AnyValue::Decimal(_, scale) = av {
*scale
} else if matches!(av, AnyValue::Float32(_) | AnyValue::Float64(_)) {
match scale {
Some(s) => s,
None => {
polars_bail!(
ComputeError: "conversion of any-value of dtype {} to decimal must specify scale", av.dtype(),
);
},
}
} else if matches!(av, AnyValue::Null) {
continue;
} else {
Expand Down Expand Up @@ -87,6 +96,11 @@ fn any_values_to_decimal(
)
} else if let AnyValue::Decimal(v, scale) = av {
(*v, *scale)
} else if matches!(av, AnyValue::Float32(_) | AnyValue::Float64(_)) {
let vf = av.try_extract::<f64>().unwrap_or_else(|_| unreachable!());
let v = ((10f64.powi(scale as i32) * vf).round()) as i128;
// TODO: check
(v, scale)
} else {
// it has to be a null because we've already checked it
builder.append_null();
Expand Down
4 changes: 2 additions & 2 deletions py-polars/polars/datatypes/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,12 @@ class Decimal(NumericType):
"""

precision: int | None
scale: int
scale: int | None

def __init__(
self,
precision: int | None = None,
scale: int = 0,
scale: int | None = None,
):
self.precision = precision
self.scale = scale
Expand Down
9 changes: 7 additions & 2 deletions py-polars/polars/datatypes/constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
dt.UInt16: PySeries.new_opt_u16,
dt.UInt32: PySeries.new_opt_u32,
dt.UInt64: PySeries.new_opt_u64,
dt.Decimal: PySeries.new_decimal,
dt.Date: PySeries.new_opt_i32,
dt.Datetime: PySeries.new_opt_i64,
dt.Duration: PySeries.new_opt_i64,
Expand All @@ -57,6 +56,12 @@ def polars_type_to_constructor(
# upon construction
if base_type == dt.Array:
return functools.partial(PySeries.new_array, dtype.width, dtype.inner) # type: ignore[union-attr]
if base_type == dt.Decimal:
return functools.partial(
PySeries.new_decimal,
getattr(dtype, "precision", None),
getattr(dtype, "scale", None),
)

return _POLARS_TYPE_TO_CONSTRUCTOR[base_type]
except KeyError: # pragma: no cover
Expand Down Expand Up @@ -143,7 +148,7 @@ def numpy_type_to_constructor(dtype: type[np.dtype[Any]]) -> Callable[..., PySer
int: PySeries.new_opt_i64,
str: PySeries.new_str,
bool: PySeries.new_opt_bool,
PyDecimal: PySeries.new_decimal,
PyDecimal: functools.partial(PySeries.new_decimal, None, None),
}


Expand Down
4 changes: 2 additions & 2 deletions py-polars/polars/expr/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,12 @@ def to_decimal(
"""
Convert a String column into a Decimal column.

This method infers the needed parameters `precision` and `scale`.
This method infers the needed parameter `scale`, using default precision.

Parameters
----------
inference_length
Number of elements to parse to determine the `precision` and `scale`.
Number of elements to parse to determine the `scale`.

Examples
--------
Expand Down
2 changes: 1 addition & 1 deletion py-polars/src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ impl FromPyObject<'_> for Wrap<DataType> {
"Decimal" => {
let precision = ob.getattr(intern!(py, "precision"))?.extract()?;
let scale = ob.getattr(intern!(py, "scale"))?.extract()?;
DataType::Decimal(precision, Some(scale))
DataType::Decimal(precision, scale)
},
"List" => {
let inner = ob.getattr(intern!(py, "inner")).unwrap();
Expand Down
6 changes: 3 additions & 3 deletions py-polars/src/datatypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub(crate) enum PyDataType {
Categorical,
Struct,
Binary,
Decimal(Option<usize>, usize),
Decimal(Option<usize>, Option<usize>),
Array(usize),
Enum(Utf8Array<i64>),
}
Expand All @@ -50,7 +50,7 @@ impl From<&DataType> for PyDataType {
DataType::UInt64 => UInt64,
DataType::Float32 => Float32,
DataType::Float64 => Float64,
DataType::Decimal(p, s) => Decimal(*p, s.expect("unexpected null decimal scale")),
DataType::Decimal(p, s) => Decimal(*p, *s),
DataType::Boolean => Bool,
DataType::String => String,
DataType::Binary => Binary,
Expand Down Expand Up @@ -113,7 +113,7 @@ impl From<PyDataType> for DataType {
PyDataType::Categorical => Categorical(None, Default::default()),
PyDataType::Enum(categories) => create_enum_data_type(categories),
PyDataType::Struct => Struct(vec![]),
PyDataType::Decimal(p, s) => Decimal(p, Some(s)),
PyDataType::Decimal(p, s) => Decimal(p, s),
PyDataType::Array(width) => Array(DataType::Null.into(), width),
}
}
Expand Down
11 changes: 9 additions & 2 deletions py-polars/src/series/construction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,19 @@ impl PySeries {
}
}

#[pyo3(signature = (precision, scale, name, val, strict))]
#[staticmethod]
fn new_decimal(name: &str, val: Vec<Wrap<AnyValue<'_>>>, strict: bool) -> PyResult<PySeries> {
fn new_decimal(
precision: Option<usize>,
scale: Option<usize>,
name: &str,
val: Vec<Wrap<AnyValue<'_>>>,
strict: bool,
) -> PyResult<PySeries> {
// TODO: do we have to respect 'strict' here? It's possible if we want to.
let avs = slice_extract_wrapped(&val);
// Create a fake dtype with a placeholder "none" scale, to be inferred later.
let dtype = DataType::Decimal(None, None);
let dtype = DataType::Decimal(precision, scale);
let s = Series::from_any_values_and_dtype(name, avs, &dtype, strict)
.map_err(PyPolarsErr::from)?;
Ok(s.into())
Expand Down