1use 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!(
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#[cfg(feature = "serde")]
610pub(crate) fn get_value(obj: &PyObject) -> f64 {
611 obj.payload::<PyFloat>().unwrap().value
612}
613
614#[rustfmt::skip] pub fn init(context: &Context) {
616 PyFloat::extend_class(context, context.types.float_type);
617}