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!(
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 #[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 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 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 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#[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] pub(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}