Skip to main content

rustpython_vm/builtins/
float.rs

1use super::{
2    PyByteArray, PyBytes, PyInt, PyIntRef, PyStr, PyType, PyTypeRef, PyUtf8StrRef,
3    try_bigint_to_f64,
4};
5use crate::{
6    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
7    TryFromBorrowedObject, TryFromObject, VirtualMachine,
8    class::PyClassImpl,
9    common::{float_ops, format::FormatSpec, hash, wtf8::Wtf8Buf},
10    convert::{IntoPyException, ToPyObject, ToPyResult},
11    function::{
12        ArgBytesLike, FuncArgs, OptionalArg, OptionalOption, PyArithmeticValue::*,
13        PyComparisonValue,
14    },
15    protocol::PyNumberMethods,
16    types::{AsNumber, Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
17};
18use core::cell::Cell;
19use core::ptr::NonNull;
20use malachite_bigint::{BigInt, ToBigInt};
21use num_complex::Complex64;
22use num_traits::{Signed, ToPrimitive, Zero};
23use rustpython_common::int::float_to_ratio;
24
25#[pyclass(module = false, name = "float")]
26#[derive(Debug, Copy, Clone, PartialEq)]
27pub struct PyFloat {
28    value: f64,
29}
30
31impl PyFloat {
32    pub const fn to_f64(&self) -> f64 {
33        self.value
34    }
35}
36
37thread_local! {
38    static FLOAT_FREELIST: Cell<crate::object::FreeList<PyFloat>> = const { Cell::new(crate::object::FreeList::new()) };
39}
40
41impl PyPayload for PyFloat {
42    const MAX_FREELIST: usize = 100;
43    const HAS_FREELIST: bool = true;
44
45    #[inline]
46    fn class(ctx: &Context) -> &'static Py<PyType> {
47        ctx.types.float_type
48    }
49
50    #[inline]
51    unsafe fn freelist_push(obj: *mut PyObject) -> bool {
52        FLOAT_FREELIST
53            .try_with(|fl| {
54                let mut list = fl.take();
55                let stored = if list.len() < Self::MAX_FREELIST {
56                    list.push(obj);
57                    true
58                } else {
59                    false
60                };
61                fl.set(list);
62                stored
63            })
64            .unwrap_or(false)
65    }
66
67    #[inline]
68    unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
69        FLOAT_FREELIST
70            .try_with(|fl| {
71                let mut list = fl.take();
72                let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
73                fl.set(list);
74                result
75            })
76            .ok()
77            .flatten()
78    }
79}
80
81impl ToPyObject for f64 {
82    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
83        vm.ctx.new_float(self).into()
84    }
85}
86impl ToPyObject for f32 {
87    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
88        vm.ctx.new_float(f64::from(self)).into()
89    }
90}
91
92impl From<f64> for PyFloat {
93    fn from(value: f64) -> Self {
94        Self { value }
95    }
96}
97
98pub(crate) fn to_op_float(obj: &PyObject, vm: &VirtualMachine) -> PyResult<Option<f64>> {
99    let v = if let Some(float) = obj.downcast_ref::<PyFloat>() {
100        Some(float.value)
101    } else if let Some(int) = obj.downcast_ref::<PyInt>() {
102        Some(try_bigint_to_f64(int.as_bigint(), vm)?)
103    } else {
104        None
105    };
106    Ok(v)
107}
108
109macro_rules! impl_try_from_object_float {
110    ($($t:ty),*) => {
111        $(impl TryFromObject for $t {
112            fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
113                PyRef::<PyFloat>::try_from_object(vm, obj).map(|f| f.to_f64() as $t)
114            }
115        })*
116    };
117}
118
119impl_try_from_object_float!(f32, f64);
120
121fn inner_div(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<f64> {
122    float_ops::div(v1, v2).ok_or_else(|| vm.new_zero_division_error("division by zero"))
123}
124
125fn inner_mod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<f64> {
126    float_ops::mod_(v1, v2).ok_or_else(|| vm.new_zero_division_error("division by zero"))
127}
128
129pub fn try_to_bigint(value: f64, vm: &VirtualMachine) -> PyResult<BigInt> {
130    match value.to_bigint() {
131        Some(int) => Ok(int),
132        None => {
133            if value.is_infinite() {
134                Err(vm
135                    .new_overflow_error("OverflowError: cannot convert float infinity to integer"))
136            } else if value.is_nan() {
137                Err(vm.new_value_error("ValueError: cannot convert float NaN to integer"))
138            } else {
139                // unreachable unless BigInt has a bug
140                unreachable!(
141                    "A finite float value failed to be converted to bigint: {}",
142                    value
143                )
144            }
145        }
146    }
147}
148
149fn inner_floordiv(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<f64> {
150    float_ops::floordiv(v1, v2).ok_or_else(|| vm.new_zero_division_error("division by zero"))
151}
152
153fn inner_divmod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<(f64, f64)> {
154    float_ops::divmod(v1, v2).ok_or_else(|| vm.new_zero_division_error("division by zero"))
155}
156
157pub fn float_pow(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult {
158    if v1.is_zero() && v2.is_sign_negative() {
159        let msg = "zero to a negative power";
160        Err(vm.new_zero_division_error(msg.to_owned()))
161    } else if v1.is_sign_negative() && (v2.floor() - v2).abs() > f64::EPSILON {
162        let v1 = Complex64::new(v1, 0.);
163        let v2 = Complex64::new(v2, 0.);
164        Ok(v1.powc(v2).to_pyobject(vm))
165    } else {
166        Ok(v1.powf(v2).to_pyobject(vm))
167    }
168}
169
170impl Constructor for PyFloat {
171    type Args = OptionalArg<PyObjectRef>;
172
173    fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
174        // Optimization: return exact float as-is
175        if cls.is(vm.ctx.types.float_type)
176            && args.kwargs.is_empty()
177            && let Some(first) = args.args.first()
178            && first.class().is(vm.ctx.types.float_type)
179        {
180            return Ok(first.clone());
181        }
182
183        let arg: Self::Args = args.bind(vm)?;
184        let payload = Self::py_new(&cls, arg, vm)?;
185        payload.into_ref_with_type(vm, cls).map(Into::into)
186    }
187
188    fn py_new(_cls: &Py<PyType>, arg: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
189        let float_val = match arg {
190            OptionalArg::Missing => 0.0,
191            OptionalArg::Present(val) => {
192                if let Some(f) = val.try_float_opt(vm) {
193                    f?.value
194                } else {
195                    float_from_string(val, vm)?
196                }
197            }
198        };
199        Ok(Self::from(float_val))
200    }
201}
202
203fn float_from_string(val: PyObjectRef, vm: &VirtualMachine) -> PyResult<f64> {
204    let (bytearray, buffer, buffer_lock, mapped_string);
205    let b = if let Some(s) = val.downcast_ref::<PyStr>() {
206        use crate::common::str::PyKindStr;
207        match s.as_str_kind() {
208            PyKindStr::Ascii(s) => s.trim().as_bytes(),
209            PyKindStr::Utf8(s) => {
210                mapped_string = s
211                    .trim()
212                    .chars()
213                    .map(|c| {
214                        if let Some(n) = rustpython_common::str::char_to_decimal(c) {
215                            char::from_digit(n.into(), 10).unwrap()
216                        } else if c.is_whitespace() {
217                            ' '
218                        } else {
219                            c
220                        }
221                    })
222                    .collect::<String>();
223                mapped_string.as_bytes()
224            }
225            // if there are surrogates, it's not gonna parse anyway,
226            // so we can just choose a known bad value
227            PyKindStr::Wtf8(_) => b"",
228        }
229    } else if let Some(bytes) = val.downcast_ref::<PyBytes>() {
230        bytes.as_bytes()
231    } else if let Some(buf) = val.downcast_ref::<PyByteArray>() {
232        bytearray = buf.borrow_buf();
233        &*bytearray
234    } else if let Ok(b) = ArgBytesLike::try_from_borrowed_object(vm, &val) {
235        buffer = b;
236        buffer_lock = buffer.borrow_buf();
237        &*buffer_lock
238    } else {
239        return Err(vm.new_type_error(format!(
240            "float() argument must be a string or a number, not '{}'",
241            val.class().name()
242        )));
243    };
244    crate::literal::float::parse_bytes(b).ok_or_else(|| {
245        val.repr(vm)
246            .map(|repr| vm.new_value_error(format!("could not convert string to float: {repr}")))
247            .unwrap_or_else(|e| e)
248    })
249}
250
251#[pyclass(
252    flags(BASETYPE, _MATCH_SELF),
253    with(Comparable, Hashable, Constructor, AsNumber, Representable)
254)]
255impl PyFloat {
256    #[pymethod]
257    fn __format__(zelf: &Py<Self>, spec: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
258        // Empty format spec: equivalent to str(self)
259        if spec.is_empty() {
260            return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
261        }
262        let format_spec =
263            FormatSpec::parse(spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
264        let result = if format_spec.has_locale_format() {
265            let locale = crate::format::get_locale_info();
266            format_spec.format_float_locale(zelf.value, &locale)
267        } else {
268            format_spec.format_float(zelf.value)
269        };
270        result
271            .map(Wtf8Buf::from_string)
272            .map_err(|err| err.into_pyexception(vm))
273    }
274
275    #[pystaticmethod]
276    fn __getformat__(spec: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<String> {
277        if !matches!(spec.as_str(), "double" | "float") {
278            return Err(
279                vm.new_value_error("__getformat__() argument 1 must be 'double' or 'float'")
280            );
281        }
282
283        const BIG_ENDIAN: bool = cfg!(target_endian = "big");
284
285        Ok(if BIG_ENDIAN {
286            "IEEE, big-endian"
287        } else {
288            "IEEE, little-endian"
289        }
290        .to_owned())
291    }
292
293    #[pymethod]
294    fn __trunc__(&self, vm: &VirtualMachine) -> PyResult<BigInt> {
295        try_to_bigint(self.value, vm)
296    }
297
298    #[pymethod]
299    fn __floor__(&self, vm: &VirtualMachine) -> PyResult<BigInt> {
300        try_to_bigint(self.value.floor(), vm)
301    }
302
303    #[pymethod]
304    fn __ceil__(&self, vm: &VirtualMachine) -> PyResult<BigInt> {
305        try_to_bigint(self.value.ceil(), vm)
306    }
307
308    #[pymethod]
309    fn __round__(&self, ndigits: OptionalOption<PyIntRef>, vm: &VirtualMachine) -> PyResult {
310        let ndigits = ndigits.flatten();
311        let value = if let Some(ndigits) = ndigits {
312            let ndigits = ndigits.as_bigint();
313            let ndigits = match ndigits.to_i32() {
314                Some(n) => n,
315                None if ndigits.is_positive() => i32::MAX,
316                None => i32::MIN,
317            };
318            let float = float_ops::round_float_digits(self.value, ndigits)
319                .ok_or_else(|| vm.new_overflow_error("overflow occurred during round"))?;
320            vm.ctx.new_float(float).into()
321        } else {
322            let fract = self.value.fract();
323            let value = if (fract.abs() - 0.5).abs() < f64::EPSILON {
324                if self.value.trunc() % 2.0 == 0.0 {
325                    self.value - fract
326                } else {
327                    self.value + fract
328                }
329            } else {
330                self.value.round()
331            };
332            let int = try_to_bigint(value, vm)?;
333            vm.ctx.new_int(int).into()
334        };
335        Ok(value)
336    }
337
338    #[pygetset]
339    const fn real(zelf: PyRef<Self>) -> PyRef<Self> {
340        zelf
341    }
342
343    #[pygetset]
344    const fn imag(&self) -> f64 {
345        0.0f64
346    }
347
348    #[pymethod]
349    const fn conjugate(zelf: PyRef<Self>) -> PyRef<Self> {
350        zelf
351    }
352
353    #[pymethod]
354    fn is_integer(&self) -> bool {
355        crate::literal::float::is_integer(self.value)
356    }
357
358    #[pymethod]
359    fn as_integer_ratio(&self, vm: &VirtualMachine) -> PyResult<(PyIntRef, PyIntRef)> {
360        let value = self.value;
361
362        float_to_ratio(value)
363            .map(|(numer, denom)| (vm.ctx.new_bigint(&numer), vm.ctx.new_bigint(&denom)))
364            .ok_or_else(|| {
365                if value.is_infinite() {
366                    vm.new_overflow_error("cannot convert Infinity to integer ratio")
367                } else if value.is_nan() {
368                    vm.new_value_error("cannot convert NaN to integer ratio")
369                } else {
370                    unreachable!("finite float must able to convert to integer ratio")
371                }
372            })
373    }
374
375    #[pyclassmethod]
376    fn from_number(cls: PyTypeRef, number: PyObjectRef, vm: &VirtualMachine) -> PyResult {
377        if number.class().is(vm.ctx.types.float_type) && cls.is(vm.ctx.types.float_type) {
378            return Ok(number);
379        }
380
381        let value = number.try_float(vm)?.to_f64();
382        let result = vm.ctx.new_float(value);
383        if cls.is(vm.ctx.types.float_type) {
384            Ok(result.into())
385        } else {
386            PyType::call(&cls, vec![result.into()].into(), vm)
387        }
388    }
389
390    #[pyclassmethod]
391    fn fromhex(cls: PyTypeRef, string: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult {
392        let result = crate::literal::float::from_hex(string.as_str().trim())
393            .ok_or_else(|| vm.new_value_error("invalid hexadecimal floating-point string"))?;
394        PyType::call(&cls, vec![vm.ctx.new_float(result).into()].into(), vm)
395    }
396
397    #[pymethod]
398    fn hex(&self) -> String {
399        crate::literal::float::to_hex(self.value)
400    }
401
402    #[pymethod]
403    fn __getnewargs__(&self, vm: &VirtualMachine) -> PyObjectRef {
404        (self.value,).to_pyobject(vm)
405    }
406}
407
408impl Comparable for PyFloat {
409    fn cmp(
410        zelf: &Py<Self>,
411        other: &PyObject,
412        op: PyComparisonOp,
413        _vm: &VirtualMachine,
414    ) -> PyResult<PyComparisonValue> {
415        let ret = if let Some(other) = other.downcast_ref::<Self>() {
416            zelf.value
417                .partial_cmp(&other.value)
418                .map_or_else(|| op == PyComparisonOp::Ne, |ord| op.eval_ord(ord))
419        } else if let Some(other) = other.downcast_ref::<PyInt>() {
420            let a = zelf.to_f64();
421            let b = other.as_bigint();
422            match op {
423                PyComparisonOp::Lt => float_ops::lt_int(a, b),
424                PyComparisonOp::Le => {
425                    if let (Some(a_int), Some(b_float)) = (a.to_bigint(), b.to_f64()) {
426                        a <= b_float && a_int <= *b
427                    } else {
428                        float_ops::lt_int(a, b)
429                    }
430                }
431                PyComparisonOp::Eq => float_ops::eq_int(a, b),
432                PyComparisonOp::Ne => !float_ops::eq_int(a, b),
433                PyComparisonOp::Ge => {
434                    if let (Some(a_int), Some(b_float)) = (a.to_bigint(), b.to_f64()) {
435                        a >= b_float && a_int >= *b
436                    } else {
437                        float_ops::gt_int(a, b)
438                    }
439                }
440                PyComparisonOp::Gt => float_ops::gt_int(a, b),
441            }
442        } else {
443            return Ok(NotImplemented);
444        };
445        Ok(Implemented(ret))
446    }
447}
448
449impl Hashable for PyFloat {
450    #[inline]
451    fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<hash::PyHash> {
452        Ok(hash::hash_float(zelf.to_f64()).unwrap_or_else(|| hash::hash_object_id(zelf.get_id())))
453    }
454}
455
456impl AsNumber for PyFloat {
457    fn as_number() -> &'static PyNumberMethods {
458        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
459            add: Some(|a, b, vm| PyFloat::number_op(a, b, |a, b, _vm| a + b, vm)),
460            subtract: Some(|a, b, vm| PyFloat::number_op(a, b, |a, b, _vm| a - b, vm)),
461            multiply: Some(|a, b, vm| PyFloat::number_op(a, b, |a, b, _vm| a * b, vm)),
462            remainder: Some(|a, b, vm| PyFloat::number_op(a, b, inner_mod, vm)),
463            divmod: Some(|a, b, vm| PyFloat::number_op(a, b, inner_divmod, vm)),
464            power: Some(|a, b, c, vm| {
465                if vm.is_none(c) {
466                    PyFloat::number_op(a, b, float_pow, vm)
467                } else {
468                    Err(vm.new_type_error(
469                        "pow() 3rd argument not allowed unless all arguments are integers",
470                    ))
471                }
472            }),
473            negative: Some(|num, vm| {
474                let value = PyFloat::number_downcast(num).value;
475                (-value).to_pyresult(vm)
476            }),
477            positive: Some(|num, vm| PyFloat::number_downcast_exact(num, vm).to_pyresult(vm)),
478            absolute: Some(|num, vm| {
479                let value = PyFloat::number_downcast(num).value;
480                value.abs().to_pyresult(vm)
481            }),
482            boolean: Some(|num, _vm| Ok(!PyFloat::number_downcast(num).value.is_zero())),
483            int: Some(|num, vm| {
484                let value = PyFloat::number_downcast(num).value;
485                try_to_bigint(value, vm).map(|x| PyInt::from(x).into_pyobject(vm))
486            }),
487            float: Some(|num, vm| Ok(PyFloat::number_downcast_exact(num, vm).into())),
488            floor_divide: Some(|a, b, vm| PyFloat::number_op(a, b, inner_floordiv, vm)),
489            true_divide: Some(|a, b, vm| PyFloat::number_op(a, b, inner_div, vm)),
490            ..PyNumberMethods::NOT_IMPLEMENTED
491        };
492        &AS_NUMBER
493    }
494
495    #[inline]
496    fn clone_exact(zelf: &Py<Self>, vm: &VirtualMachine) -> PyRef<Self> {
497        vm.ctx.new_float(zelf.value)
498    }
499}
500
501impl Representable for PyFloat {
502    #[inline]
503    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
504        Ok(crate::literal::float::to_string(zelf.value))
505    }
506}
507
508impl PyFloat {
509    fn number_op<F, R>(a: &PyObject, b: &PyObject, op: F, vm: &VirtualMachine) -> PyResult
510    where
511        F: FnOnce(f64, f64, &VirtualMachine) -> R,
512        R: ToPyResult,
513    {
514        if let (Some(a), Some(b)) = (to_op_float(a, vm)?, to_op_float(b, vm)?) {
515            op(a, b, vm).to_pyresult(vm)
516        } else {
517            Ok(vm.ctx.not_implemented())
518        }
519    }
520}
521
522// Retrieve inner float value:
523#[cfg(feature = "serde")]
524pub(crate) fn get_value(obj: &PyObject) -> f64 {
525    obj.downcast_ref::<PyFloat>().unwrap().value
526}
527
528fn vectorcall_float(
529    zelf_obj: &PyObject,
530    args: Vec<PyObjectRef>,
531    nargs: usize,
532    kwnames: Option<&[PyObjectRef]>,
533    vm: &VirtualMachine,
534) -> PyResult {
535    let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
536    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
537    (zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
538}
539
540#[rustfmt::skip] // to avoid line splitting
541pub fn init(context: &'static Context) {
542    PyFloat::extend_class(context, context.types.float_type);
543    context.types.float_type.slots.vectorcall.store(Some(vectorcall_float));
544}