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