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