num_valid/kernels/native64.rs
1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! # Native 64-bit Floating-Point Kernel
4//!
5//! This module provides a numerical kernel implementation based on Rust's native
6//! 64-bit floating-point numbers ([`f64`]) and complex numbers ([`num::Complex<f64>`]).
7//! It serves as the standard, high-performance double-precision backend for the
8//! [`num-valid`](crate) library.
9//!
10//! ## Purpose and Role
11//!
12//! The primary role of this module is to act as a **bridge** between the abstract traits
13//! defined by [`num-valid`](crate) (like [`RealScalar`] and [`ComplexScalar`]) and Rust's
14//! concrete, hardware-accelerated numeric types. It adapts [`f64`] and [`Complex<f64>`]
15//! so they can be used seamlessly in generic code written against the library's traits.
16//!
17//! ### `f64` as a `RealScalar`
18//!
19//! By implementing [`RealScalar`] for [`f64`], this module makes Rust's native float
20//! a "first-class citizen" in the [`num-valid`](crate)ecosystem. This implementation
21//! provides concrete logic for all the operations required by [`RealScalar`] and its
22//! super-traits (e.g., [`FpScalar`], [`Rounding`], [`Sign`], [`Constants`]).
23//! Most of these implementations simply delegate to the highly optimized methods
24//! available in Rust's standard library (e.g., `f64::sin`, `f64::sqrt`).
25//!
26//! This allows generic functions bounded by `T: RealScalar` to be instantiated with `f64`,
27//! leveraging direct CPU floating-point support for maximum performance.
28//!
29//! ### `Complex<f64>` as a `ComplexScalar`
30//!
31//! Similarly, this module implements [`ComplexScalar`] for [`num::Complex<f64>`]. This
32//! makes the standard complex number type from the `num` crate compatible with the
33//! [`num-valid`](crate)abstraction for complex numbers. It implements all required traits,
34//! including [`FpScalar`], [`ComplexScalarGetParts`], [`ComplexScalarSetParts`], and
35//! [`ComplexScalarMutateParts`], by delegating to the methods of `num::Complex`.
36//!
37//! This enables generic code written against `T: ComplexScalar` to operate on
38//! `Complex<f64>` values, providing a performant backend for complex arithmetic.
39//!
40//! ## Core Components
41//!
42//! - **[`Native64`]**: A marker struct that implements the [`NumKernel`](crate::NumKernel) trait. It is used
43//! in generic contexts to select this native kernel, specifying [`f64`] as its
44//! [`Real`](crate::NumKernel::Real) type and [`num::Complex<f64>`] as its
45//! [`Complex`](crate::NumKernel::Complex) type.
46//! - **Trait Implementations**: This module is primarily composed of `impl` blocks that
47//! satisfy the contracts of core traits (`FpScalar`, `RealScalar`, `ComplexScalar`,
48//! `Arithmetic`, `MulAddRef`, etc.) for `f64` and `Complex<f64>`.
49//!
50//! ## Validation
51//!
52//! It is crucial to understand that the base types `f64` and `Complex<f64>` do not
53//! inherently enforce properties like finiteness (they can be `NaN` or `Infinity`).
54//!
55//! The [`num-valid`](crate) library introduces validation at the **trait and wrapper level**,
56//! not at the base type level. For instance, [`RealScalar::try_from_f64`] for `f64`
57//! uses a [`StrictFinitePolicy`] to ensure the input is finite before creating an instance.
58//! This design separates the raw, high-performance types from the validated, safer
59//! abstractions built on top of them.
60//!
61//! For scenarios requiring types that are guaranteed to be valid by construction,
62//! consider using the validated wrappers from the [`native64_validated`](crate::kernels::native64_validated) module.
63
64use crate::{
65 Arithmetic, ComplexScalar, Constants, FpScalar, MulAddRef, RealScalar,
66 functions::{
67 Clamp, Classify, ExpM1, Hypot, Ln1p, Rounding, Sign, TotalCmp,
68 complex::{
69 ComplexScalarConstructors, ComplexScalarGetParts, ComplexScalarMutateParts,
70 ComplexScalarSetParts,
71 },
72 },
73 kernels::RawKernel,
74 scalar_kind,
75 validation::{
76 ErrorsTryFromf64, ErrorsValidationRawComplex, ErrorsValidationRawReal,
77 Native64RawRealStrictFinitePolicy, StrictFinitePolicy, validate_complex,
78 },
79};
80use num::Complex;
81use num_traits::MulAddAssign;
82use std::{cmp::Ordering, num::FpCategory};
83use try_create::ValidationPolicy;
84
85//----------------------------------------------------------------------------------------------
86/// Numerical kernel specifier for Rust's native 64-bit floating-point types.
87///
88/// This struct is used as a generic argument or associated type to indicate
89/// that the floating-point values for computations should be [`f64`] for real numbers
90/// and [`num::Complex<f64>`] for complex numbers.
91///
92/// It implements the [`RawKernel`] trait, bridging the native raw types to the
93/// [`num-valid`](crate)'s generic numerical abstraction.
94#[derive(Debug, Clone, PartialEq, PartialOrd)]
95pub struct Native64;
96
97impl RawKernel for Native64 {
98 /// The type for a real scalar value in this numerical kernel.
99 type RawReal = f64;
100
101 /// The type for a complex scalar value in this numerical kernel.
102 type RawComplex = Complex<f64>;
103}
104
105//----------------------------------------------------------------------------------------------
106
107//----------------------------------------------------------------------------------------------
108/*
109/// Implements the [`NumKernel`] trait for [`Native64`].
110impl NumKernel for Native64 {
111 /// The type for a real scalar value in this numerical kernel.
112 type Real = f64;
113
114 /// The type for a complex scalar value in this numerical kernel.
115 type Complex = Complex<f64>;
116}
117*/
118//----------------------------------------------------------------------------------------------
119
120//----------------------------------------------------------------------------------------------
121/// Marker implementation of [`Arithmetic`] for [`f64`].
122///
123/// This signifies that [`f64`] supports the standard arithmetic operations
124/// (addition, subtraction, multiplication, division, remainder, negation)
125/// as defined by the traits aggregated within [`Arithmetic`].
126impl Arithmetic for f64 {}
127
128/// Marker implementation of [`Arithmetic`] for [`num::Complex<f64>`].
129///
130/// This signifies that [`Complex<f64>`] supports the standard arithmetic operations
131/// (addition, subtraction, multiplication, division, negation)
132/// as defined by the traits aggregated within [`Arithmetic`].
133impl Arithmetic for Complex<f64> {}
134//----------------------------------------------------------------------------------------------
135
136//----------------------------------------------------------------------------------------------
137/// Implements the [`FpScalar`] trait for [`f64`].
138///
139/// This trait provides a common interface for floating-point scalar types within
140/// the [`num-valid`](crate) ecosystem.
141impl FpScalar for f64 {
142 type Kind = scalar_kind::Real;
143
144 /// Defines the associated real type, which is [`f64`] itself.
145 type RealType = f64;
146}
147
148/// Implements the [`FpScalar`] trait for [`num::Complex<f64>`].
149///
150/// This trait provides a common interface for floating-point scalar types within
151/// the [`num-valid`](crate) ecosystem.
152impl FpScalar for Complex<f64> {
153 type Kind = scalar_kind::Complex;
154
155 /// Defines the associated real type for complex numbers, which is [`f64`].
156 type RealType = f64;
157}
158//----------------------------------------------------------------------------------------------
159
160//----------------------------------------------------------------------------------------------
161impl Sign for f64 {
162 /// Returns a number with the magnitude of `self` and the sign of `sign`.
163 #[inline(always)]
164 fn kernel_copysign(self, sign: &Self) -> Self {
165 self.copysign(*sign)
166 }
167
168 /// Returns `true` if the value is negative, −0 or NaN with a negative sign.
169 #[inline(always)]
170 fn kernel_is_sign_negative(&self) -> bool {
171 self.is_sign_negative()
172 }
173
174 /// Returns `true` if the value is positive, +0 or NaN with a positive sign.
175 #[inline(always)]
176 fn kernel_is_sign_positive(&self) -> bool {
177 self.is_sign_positive()
178 }
179
180 /// Computes the signum.
181 ///
182 /// The returned value is:
183 /// - `1.0` if the value is positive, +0.0 or +∞
184 /// - `−1.0` if the value is negative, −0.0 or −∞
185 /// - `NaN` if the value is NaN
186 #[inline(always)]
187 fn kernel_signum(self) -> Self {
188 self.signum()
189 }
190}
191
192impl Rounding for f64 {
193 /// Returns the smallest integer greater than or equal to `self`.
194 #[inline(always)]
195 fn kernel_ceil(self) -> Self {
196 self.ceil()
197 }
198
199 /// Returns the largest integer smaller than or equal to `self`.
200 #[inline(always)]
201 fn kernel_floor(self) -> Self {
202 self.floor()
203 }
204
205 /// Returns the fractional part of `self`.
206 #[inline(always)]
207 fn kernel_fract(self) -> Self {
208 self.fract()
209 }
210
211 /// Rounds `self` to the nearest integer, rounding half-way cases away from zero.
212 #[inline(always)]
213 fn kernel_round(self) -> Self {
214 self.round()
215 }
216
217 /// Returns the nearest integer to a number. Rounds half-way cases to the number with an even least significant digit.
218 ///
219 /// This function always returns the precise result.
220 ///
221 /// # Examples
222 /// ```
223 /// use num_valid::{RealScalar, functions::Rounding};
224 ///
225 /// let f = 3.3_f64;
226 /// let g = -3.3_f64;
227 /// let h = 3.5_f64;
228 /// let i = 4.5_f64;
229 ///
230 /// assert_eq!(f.kernel_round_ties_even(), 3.0);
231 /// assert_eq!(g.kernel_round_ties_even(), -3.0);
232 /// assert_eq!(h.kernel_round_ties_even(), 4.0);
233 /// assert_eq!(i.kernel_round_ties_even(), 4.0);
234 /// ```
235 #[inline(always)]
236 fn kernel_round_ties_even(self) -> Self {
237 self.round_ties_even()
238 }
239
240 /// Returns the integer part of `self`. This means that non-integer numbers are always truncated towards zero.
241 ///
242 /// # Examples
243 /// ```
244 /// use num_valid::{RealScalar, functions::Rounding};
245 ///
246 /// let f = 3.7_f64;
247 /// let g = 3.0_f64;
248 /// let h = -3.7_f64;
249 ///
250 /// assert_eq!(f.kernel_trunc(), 3.0);
251 /// assert_eq!(g.kernel_trunc(), 3.0);
252 /// assert_eq!(h.kernel_trunc(), -3.0);
253 /// ```
254 #[inline(always)]
255 fn kernel_trunc(self) -> Self {
256 self.trunc()
257 }
258}
259
260impl Constants for f64 {
261 /// [Machine epsilon] value for `f64`.
262 ///
263 /// This is the difference between `1.0` and the next larger representable number.
264 ///
265 /// [Machine epsilon]: https://en.wikipedia.org/wiki/Machine_epsilon
266 #[inline(always)]
267 fn epsilon() -> Self {
268 f64::EPSILON
269 }
270
271 /// Build and return the (floating point) value -1. represented by a `f64`.
272 #[inline(always)]
273 fn negative_one() -> Self {
274 -1.
275 }
276
277 /// Build and return the (floating point) value 0.5 represented by the proper type.
278 #[inline(always)]
279 fn one_div_2() -> Self {
280 0.5
281 }
282
283 /// Build and return the (floating point) value `2.0`.
284 #[inline(always)]
285 fn two() -> Self {
286 2.
287 }
288
289 /// Build and return the maximum finite value allowed by the current floating point representation.
290 #[inline(always)]
291 fn max_finite() -> Self {
292 f64::MAX
293 }
294
295 /// Build and return the minimum finite (i.e., the most negative) value allowed by the current floating point representation.
296 #[inline(always)]
297 fn min_finite() -> Self {
298 f64::MIN
299 }
300
301 /// Build and return the (floating point) value `π`.
302 #[inline(always)]
303 fn pi() -> Self {
304 std::f64::consts::PI
305 }
306
307 /// Build and return the (floating point) value `2 π`.
308 #[inline(always)]
309 fn two_pi() -> Self {
310 std::f64::consts::PI * 2.
311 }
312
313 /// Build and return the (floating point) value `π/2`.
314 #[inline(always)]
315 fn pi_div_2() -> Self {
316 std::f64::consts::FRAC_PI_2
317 }
318
319 /// Build and return the natural logarithm of 2, i.e. the (floating point) value `ln(2)`.
320 #[inline(always)]
321 fn ln_2() -> Self {
322 std::f64::consts::LN_2
323 }
324
325 /// Build and return the natural logarithm of 10, i.e. the (floating point) value `ln(10)`.
326 #[inline(always)]
327 fn ln_10() -> Self {
328 std::f64::consts::LN_10
329 }
330
331 /// Build and return the base-10 logarithm of 2, i.e. the (floating point) value `Log_10(2)`.
332 #[inline(always)]
333 fn log10_2() -> Self {
334 std::f64::consts::LOG10_2
335 }
336
337 /// Build and return the base-2 logarithm of 10, i.e. the (floating point) value `Log_2(10)`.
338 #[inline(always)]
339 fn log2_10() -> Self {
340 std::f64::consts::LOG2_10
341 }
342
343 /// Build and return the base-2 logarithm of `e`, i.e. the (floating point) value `Log_2(e)`.
344 #[inline(always)]
345 fn log2_e() -> Self {
346 std::f64::consts::LOG2_E
347 }
348
349 /// Build and return the base-10 logarithm of `e`, i.e. the (floating point) value `Log_10(e)`.
350 #[inline(always)]
351 fn log10_e() -> Self {
352 std::f64::consts::LOG10_E
353 }
354
355 /// Build and return the (floating point) value `e` represented by the proper type.
356 #[inline(always)]
357 fn e() -> Self {
358 std::f64::consts::E
359 }
360}
361
362impl Clamp for f64 {
363 /// Clamp the value within the specified bounds.
364 ///
365 /// Returns `max` if `self` is greater than `max`, and `min` if `self` is less than `min`.
366 /// Otherwise this returns `self`.
367 ///
368 /// Note that this function returns `NaN` if the initial value was `NaN` as well.
369 ///
370 /// # Panics
371 /// Panics if `min` > `max`, `min` is `NaN`, or `max` is `NaN`.
372 /// ```
373 /// use num_valid::functions::Clamp;
374 ///
375 /// assert!((-3.0f64).kernel_clamp(&-2.0, &1.0) == -2.0);
376 /// assert!((0.0f64).kernel_clamp(&-2.0, &1.0) == 0.0);
377 /// assert!((2.0f64).kernel_clamp(&-2.0, &1.0) == 1.0);
378 /// assert!((f64::NAN).kernel_clamp(&-2.0, &1.0).is_nan());
379 /// ```
380 #[inline(always)]
381 fn kernel_clamp(self, min: &Self, max: &Self) -> Self {
382 self.clamp(*min, *max)
383 }
384}
385
386impl Classify for f64 {
387 /// Returns the floating point category of the number. If only one property is going to be tested,
388 /// it is generally faster to use the specific predicate instead.
389 /// ```
390 /// use num_valid::functions::Classify;
391 /// use std::num::FpCategory;
392 ///
393 /// let num = 12.4_f64;
394 /// let inf = f64::INFINITY;
395 ///
396 /// assert_eq!(num.kernel_classify(), FpCategory::Normal);
397 /// assert_eq!(inf.kernel_classify(), FpCategory::Infinite);
398 /// ```
399 #[inline(always)]
400 fn kernel_classify(&self) -> FpCategory {
401 self.classify()
402 }
403}
404
405impl ExpM1 for f64 {
406 /// Returns `e^(self) - 1`` in a way that is accurate even if the number is close to zero.
407 #[inline(always)]
408 fn kernel_exp_m1(self) -> Self {
409 self.exp_m1()
410 }
411}
412
413impl Hypot for f64 {
414 /// Compute the distance between the origin and a point (`self`, `other`) on the Euclidean plane.
415 /// Equivalently, compute the length of the hypotenuse of a right-angle triangle with other sides having length `self.abs()` and `other.abs()`.
416 #[inline(always)]
417 fn kernel_hypot(self, other: &Self) -> Self {
418 self.hypot(*other)
419 }
420}
421
422impl Ln1p for f64 {
423 /// Returns `ln(1. + self)` (natural logarithm) more accurately than if the operations were performed separately.
424 #[inline(always)]
425 fn kernel_ln_1p(self) -> Self {
426 self.ln_1p()
427 }
428}
429
430impl TotalCmp for f64 {
431 #[inline(always)]
432 fn total_cmp(&self, other: &Self) -> Ordering {
433 self.total_cmp(other)
434 }
435}
436
437impl RealScalar for f64 {
438 type RawReal = f64;
439
440 /// Multiplies two products and adds them in one fused operation, rounding to the nearest with only one rounding error.
441 /// `a.kernel_mul_add_mul_mut(&b, &c, &d)` produces a result like `&a * &b + &c * &d`, but stores the result in `a` using its precision.
442 #[inline(always)]
443 fn kernel_mul_add_mul_mut(&mut self, mul: &Self, add_mul1: &Self, add_mul2: &Self) {
444 self.mul_add_assign(*mul, add_mul1 * add_mul2);
445 }
446
447 /// Multiplies two products and subtracts them in one fused operation, rounding to the nearest with only one rounding error.
448 /// `a.kernel_mul_sub_mul_mut(&b, &c, &d)` produces a result like `&a * &b - &c * &d`, but stores the result in `a` using its precision.
449 #[inline(always)]
450 fn kernel_mul_sub_mul_mut(&mut self, mul: &Self, sub_mul1: &Self, sub_mul2: &Self) {
451 self.mul_add_assign(*mul, -sub_mul1 * sub_mul2);
452 }
453
454 /// Try to build a [`f64`] instance from a [`f64`].
455 /// The returned value is `Ok` if the input `value` is finite,
456 /// otherwise the returned value is `ErrorsTryFromf64`.
457 #[inline(always)]
458 fn try_from_f64(value: f64) -> Result<Self, ErrorsTryFromf64<f64>> {
459 StrictFinitePolicy::<f64, 53>::validate(value)
460 .map_err(|e| ErrorsTryFromf64::Output { source: e })
461 }
462}
463
464impl ComplexScalarConstructors for Complex<f64> {
465 type RawComplex = Complex<f64>;
466
467 fn try_new_complex(
468 real_part: f64,
469 imag_part: f64,
470 ) -> Result<Self, ErrorsValidationRawComplex<ErrorsValidationRawReal<f64>>> {
471 validate_complex::<Native64RawRealStrictFinitePolicy>(&real_part, &imag_part)
472 .map(|_| Complex::new(real_part, imag_part))
473 }
474}
475
476/// Implement the [`ComplexScalarGetParts`] trait for the `Complex<f64>` type.
477impl ComplexScalarGetParts for Complex<f64> {
478 /// Get the real part of the complex number.
479 #[inline(always)]
480 fn real_part(&self) -> f64 {
481 self.re
482 }
483
484 /// Get the imaginary part of the complex number.
485 #[inline(always)]
486 fn imag_part(&self) -> f64 {
487 self.im
488 }
489
490 /// Returns a reference to the raw real part of the complex number.
491 #[inline(always)]
492 fn raw_real_part(&self) -> &f64 {
493 &self.re
494 }
495
496 /// Returns a reference to the raw imaginary part of the complex number.
497 #[inline(always)]
498 fn raw_imag_part(&self) -> &f64 {
499 &self.im
500 }
501}
502
503/// Implement the [`ComplexScalarSetParts`] trait for the `Complex<f64>` type.
504impl ComplexScalarSetParts for Complex<f64> {
505 /// Set the value of the real part.
506 ///
507 /// # Panics
508 /// In **debug builds**, this will panic if `real_part` is not finite.
509 /// This check is disabled in release builds for performance.
510 #[inline(always)]
511 fn set_real_part(&mut self, real_part: f64) {
512 debug_assert!(
513 real_part.is_finite(),
514 "The real part is not finite (i.e. is infinite or NaN)."
515 );
516 self.re = real_part;
517 }
518
519 /// Set the value of the imaginary part.
520 ///
521 /// # Panics
522 /// In **debug builds**, this will panic if `imag_part` is not finite.
523 /// This check is disabled in release builds for performance.
524 #[inline(always)]
525 fn set_imaginary_part(&mut self, imag_part: f64) {
526 debug_assert!(
527 imag_part.is_finite(),
528 "The imaginary part is not finite (i.e. is infinite or NaN)."
529 );
530 self.im = imag_part;
531 }
532}
533
534/// Implement the [`ComplexScalarMutateParts`] trait for the `Complex<f64>` type.
535impl ComplexScalarMutateParts for Complex<f64> {
536 /// Add the value of the the real coefficient `c` to real part of `self`.
537 ///
538 /// # Panics
539 /// In **debug builds**, this will panic if the real part of `self` is not finite after the addition.
540 /// This check is disabled in release builds for performance.
541 #[inline(always)]
542 fn add_to_real_part(&mut self, c: &f64) {
543 self.re += c;
544
545 debug_assert!(
546 self.re.is_finite(),
547 "The real part is not finite (i.e. is infinite or NaN)."
548 );
549 }
550
551 /// Add the value of the the real coefficient `c` to imaginary part of `self`.
552 ///
553 /// # Panics
554 /// In **debug builds**, this will panic if the imaginary part of `self` is not finite after the addition.
555 /// This check is disabled in release builds for performance.
556 #[inline(always)]
557 fn add_to_imaginary_part(&mut self, c: &f64) {
558 self.im += c;
559
560 debug_assert!(
561 self.im.is_finite(),
562 "The imaginary part is not finite (i.e. is infinite or NaN)."
563 );
564 }
565
566 /// Multiply the value of the real part by the real coefficient `c`.
567 ///
568 /// # Panics
569 /// In **debug builds**, this will panic if the real part of `self` is not finite after the multiplication.
570 /// This check is disabled in release builds for performance.
571 #[inline(always)]
572 fn multiply_real_part(&mut self, c: &f64) {
573 self.re *= c;
574
575 debug_assert!(
576 self.re.is_finite(),
577 "The real part is not finite (i.e. is infinite or NaN)."
578 );
579 }
580
581 /// Multiply the value of the imaginary part by the real coefficient `c`.
582 ///
583 /// # Panics
584 /// In **debug builds**, this will panic if the imaginary part of `self` is not finite after the multiplication.
585 /// This check is disabled in release builds for performance.
586 #[inline(always)]
587 fn multiply_imaginary_part(&mut self, c: &f64) {
588 self.im *= c;
589
590 debug_assert!(
591 self.im.is_finite(),
592 "The imaginary part is not finite (i.e. is infinite or NaN)."
593 );
594 }
595}
596
597/// Implement the [`ComplexScalar`] trait for the `Complex<f64>` type.
598impl ComplexScalar for Complex<f64> {}
599
600//----------------------------------------------------------------------------------------------
601
602//----------------------------------------------------------------------------------------------
603#[duplicate::duplicate_item(
604 T trait_comment;
605 [f64] ["Implementation of the [`MulAddRef`] trait for `f64`."];
606 [Complex<f64>] ["Implementation of the [`MulAddRef`] trait for `Complex<f64>`."];
607)]
608#[doc = trait_comment]
609impl MulAddRef for T {
610 /// Multiplies and adds in one fused operation, rounding to the nearest with only one rounding error.
611 ///
612 /// `a.mul_add(b, c)` produces a result like `a * &b + &c`.
613 #[inline(always)]
614 fn mul_add_ref(self, b: &Self, c: &Self) -> Self {
615 <Self as num::traits::MulAdd>::mul_add(self, *b, *c)
616 }
617}
618//----------------------------------------------------------------------------------------------
619
620#[cfg(test)]
621mod tests {
622 use crate::{
623 functions::TotalCmp,
624 validation::{ErrorsValidationRawComplex, ErrorsValidationRawReal},
625 };
626
627 mod real {
628 use super::*;
629 use crate::Constants;
630
631 #[test]
632 fn test_constants() {
633 assert_eq!(<f64 as Constants>::epsilon(), f64::EPSILON);
634 assert_eq!(<f64 as Constants>::negative_one(), -1.0);
635 assert_eq!(<f64 as Constants>::one_div_2(), 0.5);
636 assert_eq!(<f64 as Constants>::two(), 2.0);
637 assert_eq!(<f64 as Constants>::max_finite(), f64::MAX);
638 assert_eq!(<f64 as Constants>::min_finite(), f64::MIN);
639 assert_eq!(<f64 as Constants>::pi(), std::f64::consts::PI);
640 assert_eq!(<f64 as Constants>::two_pi(), std::f64::consts::PI * 2.0);
641 assert_eq!(<f64 as Constants>::pi_div_2(), std::f64::consts::FRAC_PI_2);
642 assert_eq!(<f64 as Constants>::ln_2(), std::f64::consts::LN_2);
643 assert_eq!(<f64 as Constants>::ln_10(), std::f64::consts::LN_10);
644 assert_eq!(<f64 as Constants>::log10_2(), std::f64::consts::LOG10_2);
645 assert_eq!(<f64 as Constants>::log2_10(), std::f64::consts::LOG2_10);
646 assert_eq!(<f64 as Constants>::log2_e(), std::f64::consts::LOG2_E);
647 assert_eq!(<f64 as Constants>::log10_e(), std::f64::consts::LOG10_E);
648 assert_eq!(<f64 as Constants>::e(), std::f64::consts::E);
649 }
650
651 #[test]
652 #[allow(clippy::op_ref)]
653 fn multiply_ref() {
654 let a = 2.0f64;
655 let b = 3.0f64;
656 let result = a * &b;
657 assert_eq!(result, 6.0);
658 }
659
660 #[test]
661 fn total_cmp() {
662 let a = 2.0f64;
663 let b = 3.0f64;
664 assert_eq!(
665 <f64 as TotalCmp>::total_cmp(&a, &b),
666 std::cmp::Ordering::Less
667 );
668 assert_eq!(
669 <f64 as TotalCmp>::total_cmp(&b, &a),
670 std::cmp::Ordering::Greater
671 );
672 assert_eq!(
673 <f64 as TotalCmp>::total_cmp(&a, &a),
674 std::cmp::Ordering::Equal
675 );
676 }
677 }
678
679 mod complex {
680 use super::*;
681 use crate::{
682 ComplexScalarConstructors, ComplexScalarGetParts, ComplexScalarMutateParts,
683 ComplexScalarSetParts,
684 };
685 use num::{Complex, Zero};
686
687 #[test]
688 fn real_part() {
689 let c1 = Complex::new(1.23, 4.56);
690 assert_eq!(c1.real_part(), 1.23);
691
692 let c2 = Complex::new(-7.89, 0.12);
693 assert_eq!(c2.real_part(), -7.89);
694
695 let c3 = Complex::new(0.0, 10.0);
696 assert_eq!(c3.real_part(), 0.0);
697
698 let c_nan_re = Complex::new(f64::NAN, 5.0);
699 assert!(c_nan_re.real_part().is_nan());
700
701 let c_inf_re = Complex::new(f64::INFINITY, 5.0);
702 assert!(c_inf_re.real_part().is_infinite());
703 assert!(c_inf_re.real_part().is_sign_positive());
704
705 let c_neg_inf_re = Complex::new(f64::NEG_INFINITY, 5.0);
706 assert!(c_neg_inf_re.real_part().is_infinite());
707 assert!(c_neg_inf_re.real_part().is_sign_negative());
708 }
709
710 #[test]
711 fn imag_part() {
712 let c1 = Complex::new(1.23, 4.56);
713 assert_eq!(c1.imag_part(), 4.56);
714
715 let c2 = Complex::new(7.89, -0.12);
716 assert_eq!(c2.imag_part(), -0.12);
717
718 let c3 = Complex::new(10.0, 0.0);
719 assert_eq!(c3.imag_part(), 0.0);
720
721 let c_nan_im = Complex::new(5.0, f64::NAN);
722 assert!(c_nan_im.imag_part().is_nan());
723
724 let c_inf_im = Complex::new(5.0, f64::INFINITY);
725 assert!(c_inf_im.imag_part().is_infinite());
726 assert!(c_inf_im.imag_part().is_sign_positive());
727
728 let c_neg_inf_im = Complex::new(5.0, f64::NEG_INFINITY);
729 assert!(c_neg_inf_im.imag_part().is_infinite());
730 assert!(c_neg_inf_im.imag_part().is_sign_negative());
731 }
732
733 #[test]
734 fn try_new_complex() {
735 let r1 = 1.23;
736 let i1 = 4.56;
737 let c1 = Complex::<f64>::try_new_complex(r1, i1).unwrap();
738 assert_eq!(c1.re, r1);
739 assert_eq!(c1.im, i1);
740 assert_eq!(c1.real_part(), r1);
741 assert_eq!(c1.imag_part(), i1);
742
743 let r2 = -7.89;
744 let i2 = -0.12;
745 let c2 = Complex::<f64>::try_new_complex(r2, i2).unwrap();
746 assert_eq!(c2.re, r2);
747 assert_eq!(c2.im, i2);
748 assert_eq!(c2.real_part(), r2);
749 assert_eq!(c2.imag_part(), i2);
750
751 let r3 = 0.0;
752 let i3 = 0.0;
753 let c3 = Complex::<f64>::try_new_complex(r3, i3).unwrap();
754 assert_eq!(c3.re, r3);
755 assert_eq!(c3.im, i3);
756 assert!(c3.is_zero()); // Assuming Zero trait is available and implemented
757
758 let c_nan_re = Complex::<f64>::try_new_complex(f64::NAN, 5.0).unwrap_err();
759 assert!(matches!(
760 c_nan_re,
761 ErrorsValidationRawComplex::InvalidRealPart { .. }
762 ));
763
764 let c_inf_im = Complex::<f64>::try_new_complex(10.0, f64::INFINITY).unwrap_err();
765 assert!(matches!(
766 c_inf_im,
767 ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
768 ));
769
770 let c_nan_re_inf_im =
771 Complex::<f64>::try_new_complex(f64::NAN, f64::INFINITY).unwrap_err();
772 assert!(matches!(
773 c_nan_re_inf_im,
774 ErrorsValidationRawComplex::InvalidBothParts { .. }
775 ));
776 }
777
778 #[test]
779 fn try_new_pure_real() {
780 let r1 = 1.23;
781 let c1 = Complex::<f64>::try_new_pure_real(r1).unwrap();
782 assert_eq!(c1.re, r1);
783 assert_eq!(c1.im, 0.0);
784
785 let c_nan = Complex::<f64>::try_new_pure_real(f64::NAN).unwrap_err();
786 assert!(matches!(
787 c_nan,
788 ErrorsValidationRawComplex::InvalidRealPart {
789 source: ErrorsValidationRawReal::IsNaN { .. }
790 }
791 ));
792 }
793
794 #[test]
795 fn try_new_pure_imaginary() {
796 let i1 = 1.23;
797 let c1 = Complex::<f64>::try_new_pure_imaginary(i1).unwrap();
798 assert_eq!(c1.re, 0.0);
799 assert_eq!(c1.im, i1);
800
801 let c_nan = Complex::<f64>::try_new_pure_imaginary(f64::NAN).unwrap_err();
802 assert!(matches!(
803 c_nan,
804 ErrorsValidationRawComplex::InvalidImaginaryPart {
805 source: ErrorsValidationRawReal::IsNaN { .. }
806 }
807 ));
808 }
809
810 #[test]
811 fn add_to_real_part() {
812 let mut c = Complex::new(1.0, 2.0);
813 c.add_to_real_part(&3.0);
814 assert_eq!(c.re, 4.0);
815 assert_eq!(c.im, 2.0);
816
817 c.add_to_real_part(&-5.0);
818 assert_eq!(c.re, -1.0);
819 assert_eq!(c.im, 2.0);
820 }
821
822 #[cfg(debug_assertions)]
823 #[test]
824 #[should_panic(expected = "The real part is not finite (i.e. is infinite or NaN).")]
825 fn add_to_real_part_nan() {
826 let mut c = Complex::new(1.0, 2.0);
827 c.add_to_real_part(&f64::NAN);
828 }
829
830 #[test]
831 fn add_to_imaginary_part() {
832 let mut c = Complex::new(1.0, 2.0);
833 c.add_to_imaginary_part(&3.0);
834 assert_eq!(c.re, 1.0);
835 assert_eq!(c.im, 5.0);
836
837 c.add_to_imaginary_part(&-4.0);
838 assert_eq!(c.re, 1.0);
839 assert_eq!(c.im, 1.0);
840 }
841
842 #[cfg(debug_assertions)]
843 #[test]
844 #[should_panic(expected = "The imaginary part is not finite (i.e. is infinite or NaN).")]
845 fn add_to_imaginary_part_nan() {
846 let mut c = Complex::new(1.0, 2.0);
847 c.add_to_imaginary_part(&f64::NAN);
848 }
849
850 #[test]
851 fn multiply_real_part() {
852 let mut c = Complex::new(1.0, 2.0);
853 c.multiply_real_part(&3.0);
854 assert_eq!(c.re, 3.0);
855 assert_eq!(c.im, 2.0);
856
857 c.multiply_real_part(&-2.0);
858 assert_eq!(c.re, -6.0);
859 assert_eq!(c.im, 2.0);
860 }
861
862 #[cfg(debug_assertions)]
863 #[test]
864 #[should_panic(expected = "The real part is not finite (i.e. is infinite or NaN).")]
865 fn multiply_real_part_nan() {
866 let mut c = Complex::new(1.0, 2.0);
867 c.multiply_real_part(&f64::NAN);
868 }
869
870 #[test]
871 fn multiply_imaginary_part() {
872 let mut c = Complex::new(1.0, 2.0);
873 c.multiply_imaginary_part(&3.0);
874 assert_eq!(c.re, 1.0);
875 assert_eq!(c.im, 6.0);
876
877 c.multiply_imaginary_part(&-0.5);
878 assert_eq!(c.re, 1.0);
879 assert_eq!(c.im, -3.0);
880 }
881
882 #[cfg(debug_assertions)]
883 #[test]
884 #[should_panic(expected = "The imaginary part is not finite (i.e. is infinite or NaN).")]
885 fn multiply_imaginary_part_nan() {
886 let mut c = Complex::new(1.0, 2.0);
887 c.multiply_imaginary_part(&f64::NAN);
888 }
889
890 #[test]
891 fn set_real_part() {
892 let mut c = Complex::new(1.0, 2.0);
893 c.set_real_part(3.0);
894 assert_eq!(c.re, 3.0);
895 assert_eq!(c.im, 2.0);
896
897 c.set_real_part(-4.0);
898 assert_eq!(c.re, -4.0);
899 assert_eq!(c.im, 2.0);
900 }
901
902 #[cfg(debug_assertions)]
903 #[test]
904 #[should_panic(expected = "The real part is not finite (i.e. is infinite or NaN).")]
905 fn set_real_part_nan() {
906 let mut c = Complex::new(1.0, 2.0);
907 c.set_real_part(f64::NAN);
908 }
909
910 #[test]
911 fn set_imaginary_part() {
912 let mut c = Complex::new(1.0, 2.0);
913 c.set_imaginary_part(3.0);
914 assert_eq!(c.re, 1.0);
915 assert_eq!(c.im, 3.0);
916
917 c.set_imaginary_part(-4.0);
918 assert_eq!(c.re, 1.0);
919 assert_eq!(c.im, -4.0);
920 }
921
922 #[cfg(debug_assertions)]
923 #[test]
924 #[should_panic(expected = "The imaginary part is not finite (i.e. is infinite or NaN).")]
925 fn set_imaginary_part_nan() {
926 let mut c = Complex::new(1.0, 2.0);
927 c.set_imaginary_part(f64::NAN);
928 }
929
930 #[test]
931 #[allow(clippy::op_ref)]
932 fn multiply_ref() {
933 let c1 = Complex::new(1.0, 2.0);
934 let c2 = Complex::new(3.0, 4.0);
935 let result = c1 * &c2;
936 assert_eq!(result, Complex::new(-5.0, 10.0)); // (1*3 - 2*4) + (1*4 + 2*3)i
937 }
938 }
939}