rustpython_vm/builtins/
float.rs

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