frame_suite/fixedpoint.rs
1// SPDX-License-Identifier: MPL-2.0
2//
3// Part of Auguth Labs open-source softwares.
4// Built for the Substrate framework.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9//
10// Copyright (c) 2026 Auguth Labs (OPC) Pvt Ltd, India
11
12// ===============================================================================
13// `````````````````````````````````` FIXED-POINT `````````````````````````````````
14// ===============================================================================
15
16//! Deterministic, `no_std`-compatible mathematical primitives for Substrate's
17//! fixed-point numeric tower ([`FixedU64`], [`FixedU128`], [`FixedI64`], [`FixedI128`]).
18//!
19//! All arithmetic is implemented without floating-point instructions, making
20//! every operation fully deterministic and suitable for on-chain execution where
21//! bit-identical results across heterogeneous validator hardware are required.
22//!
23//! ## Type System
24//!
25//! Three layered abstractions bridge raw integers and fixed-point values:
26//!
27//! | Trait | Role |
28//! |----------------------|-------------------------------------------------------------|
29//! | [`FixedForInteger`] | Associates each primitive integer with its natural fixed-point counterpart |
30//! | [`IntegerToFixed`] | Round-trip `to_fixed` / `from_fixed` with saturation at type boundaries |
31//! | [`FixedSignedCast`] | Lifts unsigned types into signed arithmetic space for operations that require negative intermediates, then projects the result back |
32//!
33//! ## Operations
34//!
35//! | Function | Description |
36//! |-----------------|----------------------------------------------------------|
37//! | `fixed_sqrt` | Square root - real domain; returns `None` for negatives |
38//! | `complex_sqrt` | Square root - complex domain; imaginary output for `x<0` |
39//! | `fixed_exp` | Natural exponential `e^x` |
40//! | `fixed_ln` | Natural logarithm `ln(x)`, defined for `x > 0` |
41//! | `fixed_pow` | General power `x^p` - integer and fractional exponents |
42//!
43//! Operations are exposed through the [`FixedOp`] and [`FixedComplexOp`] trait
44//! facades, so generic code can be written against a single trait bound and
45//! work across all four fixed-point types without specialisation.
46//!
47//! ## Design Notes
48//!
49//! - **No panics.** All public entry-points return `Option<T>` so that undefined
50//! inputs (negative logarithm, zero base with negative exponent, etc.) are
51//! expressed as `None` rather than a runtime abort.
52//! - **Saturating internal arithmetic.** Intermediate overflow clamps to the
53//! type's representable range rather than wrapping or panicking.
54//! - **Convergence guarantees.** Every iterative algorithm is hard-capped at
55//! `MAX_ITERATIONS` and also checks for stagnation, so no function can loop
56//! indefinitely regardless of input.
57//!
58//! ## Planned Extensions
59//!
60//! Trigonometric, hyperbolic, special (gamma, erf), and additional root / power
61//! functions are outlined in the `PLANNED EXTENSIONS` section at the bottom of
62//! this file. New operations should implement the corresponding method on
63//! [`FixedOp`] (or a new companion trait) and follow the same `Option`-returning,
64//! saturation-safe conventions established here.
65
66// ===============================================================================
67// ``````````````````````````````````` IMPORTS ```````````````````````````````````
68// ===============================================================================
69
70// --- Core ---
71use core::ops::Shr;
72use core::convert::TryInto;
73
74// --- Substrate crates ---
75use sp_arithmetic::{FixedI128, FixedI64, FixedU128, FixedU64};
76use sp_runtime::{
77 FixedPointNumber,
78 traits::Bounded
79};
80
81// ===============================================================================
82// ```````````````````````````` INTEGER-FIXED MAPPING ````````````````````````````
83// ===============================================================================
84
85/// Trait mapping **primitive integer types** to an appropriate **fixed-point type**.
86///
87/// This is useful in generic algorithms where a numeric type might need to be converted
88/// to a fixed-point representation for deterministic arithmetic, scaling, or computations.
89pub trait FixedForInteger {
90 /// The fixed-point type corresponding to the integer type.
91 ///
92 /// - Small unsigned integers (u8, u16, u32) map to `FixedU64`
93 /// - Large unsigned integers (u64, u128, usize) map to `FixedU128`
94 /// - Signed integers follow a similar mapping with `FixedI64` or `FixedI128`.
95 type FixedPoint: FixedPointNumber;
96}
97
98/// Macro to conveniently implement [`FixedForInteger`] for multiple integer types at once.
99///
100macro_rules! int_best_fixed {
101 // Accepts pairs of integer type => fixed-point type
102 ($($t:ty => $fixed:ty),* $(,)?) => {
103 $(
104 // Implement the FixedForInteger trait for the integer type
105 impl FixedForInteger for $t {
106 // Associate the chosen fixed-point type with this integer
107 type FixedPoint = $fixed;
108 }
109 )*
110 };
111}
112
113// Implement [`FixedForInteger`] for all primitive integer types.
114//
115// Provides sensible defaults:
116// - **Unsigned small integers (u8, u16, u32)** -> `FixedU64`
117// - **Unsigned large integers (u64, u128, usize)** -> `FixedU128`
118// - **Signed small integers (i8, i16, i32)** -> `FixedI64`
119// - **Signed large integers (i64, i128, isize)** -> `FixedI128`
120//
121// This ensures consistent fixed-point conversions across different integer sizes,
122// particularly in algorithms involving weighting, or normalized calculations.
123int_best_fixed! {
124 u8 => FixedU64,
125 u16 => FixedU64,
126 u32 => FixedU64,
127 u64 => FixedU128,
128 u128 => FixedU128,
129 usize => FixedU128,
130 i8 => FixedI64,
131 i16 => FixedI64,
132 i32 => FixedI64,
133 i64 => FixedI128,
134 i128 => FixedI128,
135 isize => FixedI128,
136}
137
138// ===============================================================================
139// ``````````````````````````` INTEGER-FIXED CONVERSION ``````````````````````````
140// ===============================================================================
141
142/// Trait for converting a numeric type to and from its **associated fixed-point type**.
143///
144/// This is intended for integer types that implement [`FixedForInteger`],
145/// allowing deterministic fixed-point arithmetic while preserving the original type.
146pub trait IntegerToFixed: Sized + FixedForInteger {
147 /// Convert the current value to the mapped fixed-point type.
148 fn to_fixed(&self) -> <Self as FixedForInteger>::FixedPoint;
149
150 /// Convert a value in the mapped fixed-point type back to the original type.
151 fn from_fixed(f: &<Self as FixedForInteger>::FixedPoint) -> Self;
152}
153
154/// Implements `IntegerToFixed` conversion for **all unsigned integer types** in the list.
155///
156/// - `to_fixed`: Converts the integer into the corresponding fixed-point type using
157/// saturating conversion to prevent overflow.
158/// - `from_fixed`: Converts back from fixed-point to the integer, clamping values to
159/// the integer's max if the fixed-point inner value exceeds it.
160///
161/// Usage: `impl_fixed_convert_unsigned!(u8, u16, u32 => FixedU64);`
162macro_rules! impl_fixed_convert_unsigned {
163 // Accepts a comma-separated list of unsigned types ($t) and a fixed-point type ($fixed)
164 ($($t:ty),* => $fixed:ty) => {
165 $(
166 impl IntegerToFixed for $t {
167 /// Convert integer to fixed-point
168 fn to_fixed(&self) -> <$t as FixedForInteger>::FixedPoint {
169 // Saturating conversion ensures no overflow when casting integer to fixed
170 <$fixed>::saturating_from_integer(*self as $t)
171 }
172
173 /// Convert fixed-point back to integer
174 fn from_fixed(f: &<$t as FixedForInteger>::FixedPoint) -> Self {
175 // Extract the underlying integer from the fixed-point type
176 let inner = f.into_inner().saturating_div(<$fixed>::DIV);
177 // Clamp to the maximum value of the integer type
178 if inner > <$t>::MAX as _ {
179 <$t>::MAX
180 } else {
181 // Safe cast for unsigned integers
182 inner as $t
183 }
184 }
185 }
186 )*
187 };
188}
189
190/// Implements `IntegerToFixed` conversion for **all signed integer types** in the list.
191///
192/// - `to_fixed`: Converts the integer into the corresponding fixed-point type using
193/// saturating conversion.
194/// - `from_fixed`: Converts back from fixed-point to the integer, clamping to
195/// both the integer's min and max if the fixed-point inner value is out of bounds.
196///
197/// Usage: `impl_fixed_convert_signed!(i8, i16, i32 => FixedI64);`
198macro_rules! impl_fixed_convert_signed {
199 // Accepts a comma-separated list of signed types ($t) and a fixed-point type ($fixed)
200 ($($t:ty),* => $fixed:ty) => {
201 $(
202 impl IntegerToFixed for $t {
203 /// Convert signed integer to fixed-point
204 fn to_fixed(&self) -> <$t as FixedForInteger>::FixedPoint {
205 // Saturating conversion prevents overflow when converting signed integer to fixed
206 <$fixed>::saturating_from_integer(*self as $t)
207 }
208
209 /// Convert fixed-point back to signed integer
210 fn from_fixed(f: &<$t as FixedForInteger>::FixedPoint) -> Self {
211 // Extract the underlying integer from the fixed-point type
212 let inner = f.into_inner().saturating_div(<$fixed>::DIV);
213
214 // Clamp to the maximum value of the integer type
215 if inner > <$t>::MAX as _ {
216 <$t>::MAX
217 }
218 // Clamp to the minimum value of the integer type
219 else if inner < <$t>::MIN as _ {
220 <$t>::MIN
221 }
222 // Safe cast for values within the integer range
223 else {
224 inner as $t
225 }
226 }
227 }
228 )*
229 };
230}
231
232// Apply conversions for small unsigned integers
233impl_fixed_convert_unsigned!(u8, u16, u32 => FixedU64);
234// Apply conversions for large unsigned integers
235impl_fixed_convert_unsigned!(u64, u128, usize => FixedU128);
236// Apply conversions for small signed integers
237impl_fixed_convert_signed!(i8, i16, i32 => FixedI64);
238// Apply conversions for large signed integers
239impl_fixed_convert_signed!(i64, i128, isize => FixedI128);
240
241// ===============================================================================
242// ```````````````````````````` SIGNED CAST BRIDGE ```````````````````````````````
243// ===============================================================================
244
245/// A bridge that allows any [`FixedPointNumber`] type - including unsigned ones -
246/// to perform arithmetic in a signed intermediate space, then project the result
247/// back to the original type.
248///
249/// ## Motivation
250///
251/// Several mathematical operations (logarithm of a fraction, negative exponents,
252/// complex-domain arithmetic) require signed intermediates even when the input
253/// and final result are both representable as unsigned values. Rather than
254/// duplicating signed-aware implementations for every function, `FixedSignedCast`
255/// provides a single seam:
256///
257/// - **Signed types** (`FixedI64`, `FixedI128`) implement this trait as a
258/// pure identity: the associated `Signed` type is `Self`, and every conversion
259/// is a no-op.
260/// - **Unsigned types** (`FixedU64`, `FixedU128`) map to a wider signed
261/// counterpart (`FixedI128`) that can represent the full unsigned range as
262/// non-negative values. Conversions to/from `Signed` clamp or fail gracefully
263/// when a result is negative (i.e. not representable by the unsigned type).
264///
265/// ## Associated Type
266///
267/// - `Signed` - the signed fixed-point type used as the arithmetic workspace.
268/// For signed types this is `Self`, for unsigned types it is `FixedI128`.
269///
270/// ## Methods
271///
272/// | Method | Behaviour on error / out-of-range |
273/// |--------------------|------------------------------------------------|
274/// | `saturating` | Clamps the result to the target type's bounds |
275/// | `checked` | Returns `None` when the result is out-of-range |
276/// | `checked_into` | `Self -> Option<Signed>` |
277/// | `saturated_into` | `Self -> Signed` (clamping on overflow) |
278/// | `checked_from` | `Signed -> Option<Self>` |
279/// | `saturated_from` | `Signed -> Self` (clamping on underflow/overflow)|
280///
281/// ## Usage
282///
283/// Prefer [`FixedSignedCast::saturating`] for operations where out-of-range
284/// results should clamp silently, and [`FixedSignedCast::checked`] where
285/// out-of-range results must be propagated to the caller as `None`.
286pub trait FixedSignedCast : FixedPointNumber {
287 /// The signed fixed-point workspace type for intermediate arithmetic.
288 ///
289 /// - Signed types (`FixedI64`, `FixedI128`): `type Signed = Self`.
290 /// - Unsigned types (`FixedU64`, `FixedU128`): `type Signed = FixedI128`.
291 type Signed: FixedPointNumber;
292
293 /// Applies the closure `f` in `Signed` space and converts the result back
294 /// to `Self`, **clamping** at the type's representable bounds on overflow
295 /// or underflow.
296 ///
297 /// Useful when signed arithmetic may produce a value outside the target
298 /// range but a best-effort saturated answer is acceptable.
299 fn saturating<F>(x: Self, f: F) -> Self where F: FnOnce(Self::Signed)->Self::Signed;
300
301 /// Applies the closure `f` in `Signed` space and converts the result back
302 /// to `Self`, returning `None` when the result cannot be represented.
303 ///
304 /// The closure receives an `Option<Signed>` - `None` signals that the
305 /// initial conversion from `Self` into `Signed` already failed (only
306 /// possible for `FixedU128` values exceeding `i128::MAX`).
307 fn checked<F>(x: Self, f: F) -> Option<Self> where F: FnOnce(Option<Self::Signed>)->Self::Signed;
308
309 /// Converts `Self` into `Signed`, returning `None` if the value cannot
310 /// be represented in `Signed`.
311 ///
312 /// For signed types this is always `Some(x)`. For unsigned types, this
313 /// fails only when `x.into_inner() > i128::MAX` (only reachable with
314 /// `FixedU128` values in the upper half of its range).
315 fn checked_into(x: Self) -> Option<Self::Signed>;
316
317 /// Converts `Self` into `Signed`, clamping to `Signed::max_value()` on
318 /// overflow.
319 ///
320 /// For signed types this is a zero-cost identity. For unsigned types the
321 /// inner `u64`/`u128` value is reinterpreted as `i128`; values that exceed
322 /// `i128::MAX` clamp to `i128::MAX`.
323 fn saturated_into(x: Self) -> Self::Signed;
324
325 /// Converts a `Signed` value into `Self`, returning `None` if the value
326 /// falls outside the representable range of `Self`.
327 ///
328 /// For signed types this is always `Some(x)`. For unsigned types, a
329 /// negative `Signed` inner value means the result is negative and therefore
330 /// unrepresentable - `None` is returned.
331 fn checked_from(x: Self::Signed) -> Option<Self>;
332
333 /// Converts a `Signed` value into `Self`, clamping at the type bounds.
334 ///
335 /// For signed types this is a zero-cost identity. For unsigned types,
336 /// negative values clamp to `0`. No upper clamp is needed: a non-negative
337 /// `i128` inner value is at most `i128::MAX = 2^127 - 1`, which is always
338 /// less than `u128::MAX = 2^128 - 1`, so it always fits in the unsigned
339 /// inner type without loss.
340 fn saturated_from(x: Self::Signed) -> Self;
341}
342
343/// Identity implementation for `FixedI64`.
344///
345/// `FixedI64` is already signed, so `checked_into`, `saturated_into`,
346/// `checked_from`, and `saturated_from` are all zero-cost identity operations.
347///
348/// `saturating` delegates directly to `f` - any saturation that occurs inside
349/// the closure is the closure's own saturating arithmetic, which is the
350/// expected behaviour for this variant.
351///
352/// `checked` delegates to `f` and returns `None` only when the result has
353/// saturated to `min_value()` or `max_value()`, which are the two sentinel
354/// values that saturating arithmetic produces on overflow. If the closure
355/// legitimately computes exactly `min_value()` or `max_value()`, `None` is
356/// returned conservatively. For cases where that distinction matters, prefer
357/// the saturating variant and handle clamping at the call site.
358impl FixedSignedCast for FixedI64 {
359 type Signed = FixedI64;
360
361 fn saturating<F>(x: Self, f: F) -> Self
362 where
363 F: FnOnce(Self::Signed) -> Self::Signed,
364 {
365 f(x)
366 }
367
368 fn checked<F>(x: Self, f: F) -> Option<Self>
369 where
370 F: FnOnce(Option<Self::Signed>) -> Self::Signed,
371 {
372 let result = f(Some(x));
373 // Detect saturation: saturating arithmetic clamps to min/max on overflow.
374 // Treat either sentinel as evidence that the result is out of range.
375 if result == Self::min_value() || result == Self::max_value() {
376 None
377 } else {
378 Some(result)
379 }
380 }
381
382 fn checked_into(x: Self) -> Option<Self::Signed> {
383 Some(x)
384 }
385
386 fn saturated_into(x: Self) -> Self::Signed {
387 x
388 }
389
390 fn checked_from(x: Self::Signed) -> Option<Self> {
391 Some(x)
392 }
393
394 fn saturated_from(x: Self::Signed) -> Self {
395 x
396 }
397}
398
399/// Identity implementation for `FixedI128`.
400///
401/// `FixedI128` is already signed, so `checked_into`, `saturated_into`,
402/// `checked_from`, and `saturated_from` are all zero-cost identity operations.
403///
404/// `saturating` delegates directly to `f` - any saturation that occurs inside
405/// the closure is the closure's own saturating arithmetic, which is the
406/// expected behaviour for this variant.
407///
408/// `checked` delegates to `f` and returns `None` only when the result has
409/// saturated to `min_value()` or `max_value()`, which are the two sentinel
410/// values that saturating arithmetic produces on overflow. If the closure
411/// legitimately computes exactly `min_value()` or `max_value()`, `None` is
412/// returned conservatively. For cases where that distinction matters, prefer
413/// the saturating variant and handle clamping at the call site.
414impl FixedSignedCast for FixedI128 {
415 type Signed = FixedI128;
416
417 fn saturating<F>(x: Self, f: F) -> Self
418 where
419 F: FnOnce(Self::Signed) -> Self::Signed,
420 {
421 f(x)
422 }
423
424 fn checked<F>(x: Self, f: F) -> Option<Self>
425 where
426 F: FnOnce(Option<Self::Signed>) -> Self::Signed,
427 {
428 let result = f(Some(x));
429 // Detect saturation: saturating arithmetic clamps to min/max on overflow.
430 // Treat either sentinel as evidence that the result is out of range.
431 if result == Self::min_value() || result == Self::max_value() {
432 None
433 } else {
434 Some(result)
435 }
436 }
437
438 fn checked_into(x: Self) -> Option<Self::Signed> {
439 Some(x)
440 }
441
442 fn saturated_into(x: Self) -> Self::Signed {
443 x
444 }
445
446 fn checked_from(x: Self::Signed) -> Option<Self> {
447 Some(x)
448 }
449
450 fn saturated_from(x: Self::Signed) -> Self {
451 x
452 }
453}
454
455/// Unsigned-to-signed bridge for `FixedU64`.
456///
457/// Uses `FixedI128` as the signed workspace. A `FixedU64` inner value is a
458/// `u64`, which always fits in `i128`, so `checked_into` / `saturated_into`
459/// are infallible. The reverse (`checked_from` / `saturated_from`) can fail
460/// or clamp when the signed result is negative or exceeds `u64::MAX`.
461impl FixedSignedCast for FixedU64 {
462 type Signed = FixedI128;
463
464 fn saturating<F>(x: Self, f: F) -> Self
465 where
466 F: FnOnce(Self::Signed) -> Self::Signed,
467 {
468 // u64 inner always fits in i128 - cast is infallible.
469 let signed = FixedI128::from_inner(x.into_inner() as i128);
470 let result = f(signed);
471 Self::saturated_from(result)
472 }
473
474 fn checked<F>(x: Self, f: F) -> Option<Self>
475 where
476 F: FnOnce(Option<Self::Signed>) -> Self::Signed,
477 {
478 // u64 inner always fits in i128 - cast is infallible.
479 let signed = FixedI128::from_inner(x.into_inner() as i128);
480 let result = f(Some(signed));
481 Self::checked_from(result)
482 }
483
484 fn checked_into(x: Self) -> Option<Self::Signed> {
485 Some(FixedI128::from_inner(x.into_inner() as i128))
486 }
487
488 fn saturated_into(x: Self) -> Self::Signed {
489 FixedI128::from_inner(x.into_inner() as i128)
490 }
491
492 fn checked_from(x: Self::Signed) -> Option<Self> {
493 let inner = x.into_inner();
494 // Negative values are not representable as FixedU64.
495 // Values above u64::MAX cannot fit in the u64 inner type.
496 match inner < 0 || inner > u64::MAX as i128 {
497 true => None,
498 false => Some(FixedU64::from_inner(inner as u64)),
499 }
500 }
501
502 fn saturated_from(x: Self::Signed) -> Self {
503 let inner = x.into_inner();
504
505 let clamped = match inner {
506 b if b < 0 => 0,
507 b if b > u64::MAX as i128 => u64::MAX,
508 b => b as u64,
509 };
510
511 FixedU64::from_inner(clamped)
512 }
513}
514
515/// Unsigned-to-signed bridge for `FixedU128`.
516///
517/// Uses `FixedI128` as the signed workspace. Unlike `FixedU64`, a `FixedU128`
518/// inner value is a `u128` whose upper half (`> i128::MAX`) cannot be
519/// represented in `i128`. `checked_into` therefore returns `None` for those
520/// values, and `saturated_into` clamps them to `i128::MAX`.
521impl FixedSignedCast for FixedU128 {
522 type Signed = FixedI128;
523
524 fn saturating<F>(x: Self, f: F) -> Self
525 where
526 F: FnOnce(Self::Signed) -> Self::Signed,
527 {
528 let signed = Self::saturated_into(x);
529 let result = f(signed);
530 Self::saturated_from(result)
531 }
532
533 fn checked<F>(x: Self, f: F) -> Option<Self>
534 where
535 F: FnOnce(Option<Self::Signed>) -> Self::Signed,
536 {
537 let signed = Self::checked_into(x);
538 let result = f(signed);
539 Self::checked_from(result)
540 }
541
542 fn checked_into(x: Self) -> Option<Self::Signed> {
543 let inner = x.into_inner();
544 // u128 values above i128::MAX cannot be represented in FixedI128.
545 match inner > i128::MAX as u128 {
546 true => None,
547 false => Some(FixedI128::from_inner(inner as i128)),
548 }
549 }
550
551 fn saturated_into(x: Self) -> Self::Signed {
552 let inner = x.into_inner();
553 // Values in the upper half of u128 clamp to i128::MAX.
554 match inner > i128::MAX as u128 {
555 true => FixedI128::from_inner(i128::MAX),
556 false => FixedI128::from_inner(inner as i128),
557 }
558 }
559
560 fn checked_from(x: Self::Signed) -> Option<Self> {
561 let inner = x.into_inner();
562 // Negative values are not representable as FixedU128.
563 match inner < 0 {
564 true => None,
565 false => Some(FixedU128::from_inner(inner as u128))
566 }
567 }
568
569 fn saturated_from(x: Self::Signed) -> Self {
570 let inner = x.into_inner();
571 // Negative signed results clamp to zero, non-negative values fit in u128.
572 let clamped = match inner < 0 {
573 true => 0,
574 false => inner as u128
575 };
576
577 FixedU128::from_inner(clamped)
578 }
579}
580
581
582// ===============================================================================
583// ```````````````````````````````` COMPLEX NUMBER ```````````````````````````````
584// ===============================================================================
585
586/// A simple, generic **complex number** representation.
587///
588/// Holds a **real** and an **imaginary** (`imgn`) component of any numeric type `T`.
589///
590/// This structure is lightweight and can be used for mathematical, financial, or
591/// signal-processing computations that require complex arithmetic.
592///
593/// ### Type Parameters
594/// - `T`: A numeric type (e.g. `f32`, `f64`, or a custom numeric type)
595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
596pub struct Complex<T> {
597 /// The **real component** of the complex number.
598 pub real: T,
599
600 /// The **imaginary component** of the complex number.
601 pub imgn: T,
602}
603
604impl<T> Complex<T> {
605 fn new(real: T, imgn: T) -> Self {
606 Self { real, imgn }
607 }
608}
609
610// ===============================================================================
611// ``````````````````````````````` PRECISION MODEL ```````````````````````````````
612// ===============================================================================
613
614/// Provides precision metadata for fixed-point types used in numerical
615/// series computations.
616///
617/// # Constants
618///
619/// - `DECIMAL_PLACES`: the number of decimal digits after the point that
620/// the type can represent, derived from its `DIV` value. Used to compute
621/// underflow thresholds and convergence bounds in `fixed_exp` and
622/// related functions.
623///
624/// # Note
625///
626/// These types use **decimal** fixed-point representation, not binary.
627/// `DIV` is a power of 10, so precision is measured in decimal places
628/// rather than fractional bits. `INNER_BITS` records the total bit width
629/// of the underlying integer storage and is reserved for potential future
630/// use with binary fixed-point types; it is not used internally.
631pub trait FixedPointInfo {
632 /// Total bit width of the inner integer storage type.
633 ///
634 /// For example, `FixedU64` wraps a `u64`, so `INNER_BITS = 64`.
635 ///
636 /// This constant is reserved for potential future
637 /// binary fixed-point support and is not used internally.
638 const INNER_BITS: u32;
639
640 /// Number of representable decimal places, equal to `log10(DIV)`.
641 ///
642 /// | Type | DIV | DECIMAL_PLACES |
643 /// |-------------|---------|----------------|
644 /// | `FixedU64` | `10^9` | `9` |
645 /// | `FixedI64` | `10^9` | `9` |
646 /// | `FixedU128` | `10^18` | `18` |
647 /// | `FixedI128` | `10^18` | `18` |
648 const DECIMAL_PLACES: u32;
649}
650
651/// `FixedU64`: inner type `u64` (64-bit), `DIV = 10^9` - 9 decimal places.
652impl FixedPointInfo for FixedU64 {
653 const INNER_BITS: u32 = 64; // bit width of u64
654 const DECIMAL_PLACES: u32 = 9;
655}
656
657/// `FixedI64`: inner type `i64` (64-bit), `DIV = 10^9` - 9 decimal places.
658impl FixedPointInfo for FixedI64 {
659 const INNER_BITS: u32 = 64; // bit width of i64, sign bit included
660 const DECIMAL_PLACES: u32 = 9;
661}
662
663/// `FixedU128`: inner type `u128` (128-bit), `DIV = 10^18` - 18 decimal places.
664impl FixedPointInfo for FixedU128 {
665 const INNER_BITS: u32 = 128; // bit width of u128
666 const DECIMAL_PLACES: u32 = 18;
667}
668
669/// `FixedI128`: inner type `i128` (128-bit), `DIV = 10^18` - 18 decimal places.
670impl FixedPointInfo for FixedI128 {
671 const INNER_BITS: u32 = 128; // bit width of i128, sign bit included
672 const DECIMAL_PLACES: u32 = 18;
673}
674
675// ===============================================================================
676// ````````````````````````````` NUMERICAL UTILITIES `````````````````````````````
677// ===============================================================================
678
679/// Returns the smallest positive increment representable by the fixed-point generic
680/// [`FixedPointNumber`].
681///
682/// For a fixed-point number defined as:
683/// ```text
684/// value = inner / DIV
685/// ```
686/// the ULP equals `1 / DIV`.
687///
688/// ## Behavior
689/// - Uses `FixedPoint::from_inner(1)` to construct the smallest step value.
690/// - Useful as a numerical tolerance in convergence or rounding checks.
691///
692/// ## Example
693/// ```ignore
694/// let ulp_val = ulp::<FixedU128>();
695/// assert_eq!(ulp_val, FixedU128::from_inner(1)); // represents 1e-18 if DIV = 1e18
696/// ```
697///
698/// ## Notes
699/// - The exact decimal size of the ULP depends on `FixedPoint::DIV`.
700/// - If `DIV = 10^6`, then `ULP = 1e-6`.
701/// - Works for all fixed-point types whose `Inner` implements `From<u8>`.
702fn ulp<F: FixedPointNumber>() -> F
703where
704 F::Inner: From<u8>,
705{
706 F::from_inner(F::Inner::from(1u8))
707}
708
709/// Maximum allowed number of iterations for iterative numerical methods.
710///
711/// This constant caps the number of iterations in functions to:
712/// - Prevent infinite or excessively long loops during convergence.
713/// - Provide a reasonable trade-off between accuracy and computation time.
714///
715/// Typical value (50) is chosen empirically to balance precision and performance,
716/// but can be adjusted depending on application requirements.
717///
718/// ## Note
719/// - Functions also should implement early stopping conditions based on tolerance or stagnation,
720/// so the actual number of iterations is often fewer than this maximum.
721const MAX_ITERATIONS: u32 = 50;
722
723
724/// Extracts the integer part of a fixed-point number as a `u32`,
725/// truncating the fractional component toward zero.
726///
727/// Fixed-point numbers store their value as `inner / DIV`, where `DIV`
728/// is a power of 10 (`10^9` for 64-bit types, `10^18` for 128-bit types).
729/// Dividing `inner` by `DIV` removes the fractional portion, leaving
730/// the integer part.
731///
732/// ## Behavior
733///
734/// | Condition | Returns |
735/// |------------------------|--------------|
736/// | Integer part negative | `0` |
737/// | Integer part > u32::MAX| `u32::MAX` |
738/// | Otherwise | Integer part |
739///
740/// ## Arguments
741///
742/// * `x` - The fixed-point value to truncate.
743///
744/// ## Returns
745///
746/// The integer portion of `x`, clamped to `[0, u32::MAX]`.
747///
748/// ## Examples
749///
750/// ```ignore
751/// // FixedU64 with DIV = 10^9: inner value 300_750_000_000 represents 300.75
752/// let x = FixedU64::from_inner(300_750_000_000);
753/// assert_eq!(to_u32_floor(&x), 300);
754///
755/// // Negative values clamp to 0
756/// let x = FixedI64::saturating_from_integer(-5);
757/// assert_eq!(to_u32_floor(&x), 0);
758/// ```
759fn to_u32_floor<T>(x: &T) -> u32
760where
761 T: FixedPointNumber + Copy + FixedPointInfo,
762 // TryInto<i128> required to work with the fixed-point inner value
763 // in a common signed type regardless of whether T is u64 or i128 based.
764 T::Inner: Copy + PartialOrd + TryInto<i128>,
765{
766 // Extract the raw inner integer representation.
767 let inner = x.into_inner();
768
769 // Convert inner to i128 for arithmetic. For unsigned inner types (u64, u128),
770 // try_into() fails only if the value exceeds i128::MAX - astronomically large,
771 // treated as overflow and clamped to u32::MAX.
772 let inner_i128: i128 = match inner.try_into() {
773 Ok(val) => val,
774 Err(_) => return u32::MAX,
775 };
776
777 // Convert DIV to i128. For sp_arithmetic types, DIV is at most 10^18
778 // which fits comfortably in i128 (max ~1.7 * 10^38). Failure here is
779 // unreachable in practice, but we return 0 conservatively rather than panic.
780 let div: i128 = match T::DIV.try_into() {
781 Ok(val) => val,
782 Err(_) => return 0,
783 };
784
785 // Integer part = inner / DIV, truncated toward zero.
786 let int_part = inner_i128 / div;
787
788 if int_part < 0 {
789 // Negative integer part - not representable as u32, clamp to 0.
790 0
791 } else if int_part > u32::MAX as i128 {
792 // Exceeds u32 range - clamp to maximum.
793 u32::MAX
794 } else {
795 // Safe cast: int_part is in [0, u32::MAX].
796 int_part as u32
797 }
798}
799
800/// Extracts the exact integer value of a fixed-point number as `i128`,
801/// returning `None` if the value has a non-zero fractional component.
802///
803/// A fixed-point number represents `inner / DIV`. This function returns
804/// `inner / DIV` only when `inner` is exactly divisible by `DIV` -
805/// i.e. when the fixed-point value is a whole number with no fractional part.
806///
807/// ## Arguments
808///
809/// * `x` - The fixed-point number to inspect.
810///
811/// ## Returns
812///
813/// * `Some(n)` if `x` represents the exact integer `n`
814/// * `None` if `x` has a fractional component, or if internal conversion fails
815///
816/// ## Examples
817///
818/// ```ignore
819/// let x = FixedU64::saturating_from_integer(5);
820/// assert_eq!(fixed_to_i128(&x), Some(5));
821///
822/// let x = FixedU64::saturating_from_rational(3, 2); // 1.5
823/// assert_eq!(fixed_to_i128(&x), None);
824/// ```
825fn fixed_to_i128<T>(x: &T) -> Option<i128>
826where
827 T: FixedPointNumber,
828 T::Inner: TryInto<i128> + Copy,
829{
830 // Convert inner representation and DIV to i128 for arithmetic.
831 // Both conversions fail only in pathological cases - DIV for sp_arithmetic
832 // types is at most 10^18, well within i128 range.
833 let inner: i128 = x.into_inner().try_into().ok()?;
834 let div: i128 = T::DIV.try_into().ok()?;
835
836 // Exact integer check: inner must be perfectly divisible by DIV.
837 // Any remainder means the value has a fractional component.
838 if inner % div == 0 {
839 Some(inner / div)
840 } else {
841 None
842 }
843}
844
845#[allow(dead_code)]
846fn fixed_pi<T>() -> T
847where
848 T: FixedPointNumber,
849{
850 T::saturating_from_rational(355, 113)
851}
852
853/// Computes an adaptive iteration count for series expansions based on
854/// the magnitude of `x` and the precision of the fixed-point type.
855///
856/// Larger inputs converge more slowly in series expansions, and higher
857/// precision types require more terms to reach their representable accuracy.
858/// This function combines both factors into a single iteration budget:
859///
860/// ```text
861/// iterations = floor(|x|) * DECIMAL_PLACES + 1
862/// ```
863///
864/// The `+ 1` guarantees at least one iteration for any input, including
865/// `x = 0`.
866///
867/// ## Arguments
868///
869/// * `x` - The fixed-point value whose magnitude drives the iteration count.
870///
871/// ## Returns
872///
873/// A `u32` iteration count, always `>= 1`. Uses saturating arithmetic
874/// throughout so overflow on very large inputs produces `u32::MAX` rather
875/// than wrapping.
876#[allow(dead_code)]
877fn dynamic_max_iterations<T>(x: &T) -> u32
878where
879 T: FixedPointNumber + Copy + FixedPointInfo,
880 T::Inner: Shr<u32, Output = T::Inner> + TryInto<i128> + Copy,
881{
882 // Work with |x| so negative inputs produce the same iteration count
883 // as their positive equivalents.
884 let abs_x = x.saturating_abs();
885
886 // Integer part of |x| - the fractional component does not affect
887 // convergence speed meaningfully.
888 let int_part = to_u32_floor(&abs_x);
889
890 // Scale by DECIMAL_PLACES to account for type precision, then add 1
891 // to ensure at least one iteration. saturating_mul and saturating_add
892 // prevent overflow on extreme inputs.
893 int_part
894 .saturating_mul(T::DECIMAL_PLACES)
895 .saturating_add(1)
896}
897
898// ===============================================================================
899// ```````````````````````````` SQRT - NEWTON-RAPHSON ````````````````````````````
900// ===============================================================================
901
902/// Approximates the square root of a fixed-point number using the
903/// Newton-Raphson method.
904///
905/// This is the core computational primitive for square root operations.
906/// The public API is [`fixed_sqrt`], which adds domain checking and exact
907/// fast paths before delegating here.
908///
909/// ## Algorithm
910///
911/// Newton-Raphson iteration for square roots:
912/// ```text
913/// guess_{n+1} = (guess_n + x / guess_n) / 2
914/// ```
915///
916/// Converges quadratically - the number of correct digits roughly doubles
917/// each iteration. For fixed-point types, convergence is detected when the
918/// change between iterations falls within `2 * ULP`, which is the tightest
919/// meaningful threshold: the Newton step cannot improve beyond `1 ULP` on
920/// each side, so tighter tolerances would cause infinite oscillation between
921/// adjacent representable values.
922///
923/// Iteration stops early on stagnation (improvement stops or reverses),
924/// and is hard-capped at `MAX_ITERATIONS` to guarantee termination.
925///
926/// ## Initial Guess Strategy
927///
928/// | Input range | Initial guess | Reason |
929/// |-------------|-----------------------|-------------------------------------|
930/// | `x > 1` | `(x + 1) / 2` | Midpoint above 1, closer to result |
931/// | `x = 1` | `1` | Exact, no iteration needed |
932/// | `x in (0.25, 1)` | `x` | Already a reasonable approximation |
933/// | `x <= 0.25` | `0.25` | Avoids starting too close to zero |
934///
935/// ## Arguments
936///
937/// * `x` - A non-negative fixed-point number to compute the square root of.
938/// Caller is responsible for ensuring `x >= 0`. Negative inputs
939/// return zero - use [`fixed_sqrt`] for proper domain handling.
940///
941/// ## Returns
942///
943/// An approximation of `sqrt(x)`, accurate to within `2 * ULP` of the
944/// true value for well-behaved inputs.
945fn fixed_sqrt_newton<F: FixedPointNumber>(x: &F) -> F
946where
947 F::Inner: From<u8>,
948{
949 let zero = F::zero();
950
951 if *x <= zero {
952 return zero;
953 }
954
955 let one = F::one();
956 let two = one.saturating_add(one);
957
958 // 2 * ULP is the principled convergence bound: the Newton step cannot
959 // improve beyond 1 ULP on either side, so anything tighter causes
960 // oscillation between adjacent representable values.
961 let tol = ulp::<F>().saturating_add(ulp::<F>());
962
963 let mut guess = match x.cmp(&one) {
964 // x > 1: midpoint of [1, x] is above sqrt(x), a safe starting point.
965 core::cmp::Ordering::Greater => {
966 x.saturating_add(one).checked_div(&two).unwrap_or(one)
967 }
968 // x = 1: sqrt(1) = 1 exactly, no iteration needed.
969 core::cmp::Ordering::Equal => return one,
970 // x < 1: use x itself if it's above 0.25, otherwise use 0.25.
971 core::cmp::Ordering::Less => {
972 let quarter = F::saturating_from_rational(1, 4);
973 if *x > quarter { *x } else { quarter }
974 }
975 };
976
977 let mut prev_diff: Option<F> = None;
978
979 for _ in 0..MAX_ITERATIONS {
980 // Compute x / guess. If this fails (degenerate state at fixed-point
981 // boundaries), return the best approximation computed so far.
982 let div = match x.checked_div(&guess) {
983 Some(d) => d,
984 None => return guess,
985 };
986
987 // Next guess: average of current guess and x/guess.
988 // Falls back to current guess if the addition overflows.
989 let next = guess.saturating_add(div)
990 .checked_div(&two)
991 .unwrap_or(guess);
992
993 // Absolute difference between successive guesses.
994 let diff = if next > guess {
995 next.saturating_sub(guess)
996 } else {
997 guess.saturating_sub(next)
998 };
999
1000 // Converged: improvement is within 2 * ULP.
1001 // Return `next`, not `guess` - next is the result of this iteration
1002 // and is always at least as accurate as guess.
1003 if diff <= tol {
1004 return next;
1005 }
1006
1007 // Stagnation: improvement has stopped or reversed.
1008 // Return next for the same reason as above.
1009 if let Some(pd) = prev_diff {
1010 if diff >= pd {
1011 return next;
1012 }
1013 }
1014
1015 prev_diff = Some(diff);
1016 guess = next;
1017 }
1018
1019 // Iteration limit reached - return best approximation found.
1020 guess
1021}
1022
1023// ===============================================================================
1024// ````````````````````````````` LN - RANGE REDUCTION ````````````````````````````
1025// ===============================================================================
1026
1027/// Reduces a fixed-point value `y` toward `1` by repeatedly taking its
1028/// square root, returning the reduced value and the number of reductions applied.
1029///
1030/// ## Purpose
1031///
1032/// Series expansions for `ln(y)` converge fastest when `y` is close to `1`.
1033/// This function brings `y` into the band `[0.5, 1.5]` where [`ln_near_one`]
1034/// is both accurate and efficient.
1035///
1036/// ## Algorithm
1037///
1038/// Each iteration replaces `y` with `sqrt(y)`, halving the distance to `1`
1039/// in logarithmic space. After `k` reductions:
1040///
1041/// ```text
1042/// y_original = y_reduced ^ (2^k)
1043/// ln(y_original) = 2^k * ln(y_reduced)
1044/// ```
1045///
1046/// The caller uses `k` to undo the reduction after computing `ln(y_reduced)`.
1047///
1048/// ## Stopping Conditions
1049///
1050/// Iteration stops when any of the following occur:
1051/// - `|y - 1| <= 0.5` - `y` is in `[0.5, 1.5]`, close enough for [`ln_near_one`]
1052/// - `ny == y` - Newton-Raphson stagnated, no further reduction is possible
1053/// - `ny == 0` - degenerate input at fixed-point boundaries; result will be approximate
1054/// - [`MAX_ITERATIONS`] reached - hard cap to guarantee termination
1055///
1056/// ## Arguments
1057///
1058/// * `y` - A positive fixed-point value to reduce. Behaviour for `y <= 0`
1059/// is undefined - caller is responsible for domain validation.
1060///
1061/// ## Returns
1062///
1063/// A tuple `(y_reduced, k)` where:
1064/// - `y_reduced` is in `[0.5, 1.5]` (or as close as the stopping conditions allow)
1065/// - `k` is the number of square root reductions applied
1066fn range_reduce_sqrt<T>(mut y: T) -> (T, u32)
1067where
1068 T: FixedPointNumber + Copy + PartialOrd,
1069 T::Inner: From<u8> + Shr<u32, Output = T::Inner> + TryInto<i128> + Copy,
1070{
1071 let one = T::one();
1072
1073 let half = T::saturating_from_rational(1, 2);
1074
1075 let mut k: u32 = 0;
1076
1077 for _ in 0..MAX_ITERATIONS {
1078 let diff = if y > one {
1079 y.saturating_sub(one)
1080 } else {
1081 one.saturating_sub(y)
1082 };
1083
1084 // y is within [0.5, 1.5] - close enough for ln_near_one.
1085 if diff <= half {
1086 break;
1087 }
1088
1089 let ny = fixed_sqrt_newton::<T>(&y);
1090
1091 // Stagnation: Newton-Raphson could not improve further.
1092 if ny == y {
1093 break;
1094 }
1095
1096 // Degenerate: sqrt collapsed to zero at fixed-point boundaries.
1097 if ny == T::zero() {
1098 break;
1099 }
1100
1101 y = ny;
1102 k += 1;
1103 }
1104
1105 (y, k)
1106}
1107
1108/// Computes `ln(y)` for a fixed-point value `y` near `1` using the
1109/// arctanh series identity:
1110///
1111/// ```text
1112/// ln(y) = 2 * sum_{k=0}^{inf} t^(2k+1) / (2k+1)
1113///
1114/// where t = (y - 1) / (y + 1)
1115/// ```
1116///
1117/// Converges for all `y > 0`, with convergence rate determined by `|t|`.
1118/// The closer `y` is to `1`, the smaller `|t|` and the faster convergence.
1119/// [`range_reduce_sqrt`] ensures `y` is in `[0.5, 1.5]` before calling
1120/// this function, keeping `|t| <= 1/3` for fast, reliable convergence.
1121///
1122/// ## Arguments
1123///
1124/// * `y` - A fixed-point value near `1`. Caller must ensure `y > 0`.
1125/// Results are inaccurate for `y` far from `1`.
1126///
1127/// ## Returns
1128///
1129/// An approximation of `ln(y)`, accurate to within the type's ULP for
1130/// inputs in `[0.5, 1.5]`.
1131///
1132/// ## Note
1133///
1134/// On unsigned types, `y < 1` produces `t = 0` (since `y - 1` saturates
1135/// to zero), returning `ln(y) = 0`. This is incorrect for `y < 1`, but
1136/// the unsigned type guard in [`fixed_ln`] ensures this branch is never
1137/// reached for unsigned types with `y < 1`.
1138fn ln_near_one<T>(y: T) -> T
1139where
1140 T: FixedPointNumber + Copy + PartialOrd + FixedPointInfo,
1141 T::Inner: From<u8> + Shr<u32, Output = T::Inner> + TryInto<i128> + Copy,
1142{
1143 let one = T::one();
1144 let two = one.saturating_add(one);
1145 let eps = ulp::<T>();
1146
1147 // t = (y - 1) / (y + 1)
1148 // For signed types: saturating_sub produces a negative result when y < 1,
1149 // giving a negative t - correct.
1150 // For unsigned types: saturating_sub returns 0 when y < 1 - guarded in fixed_ln.
1151 let num = y.saturating_sub(one); // y - 1
1152 let denom = y.saturating_add(one); // y + 1, always positive for y > 0
1153 let t = num.checked_div(&denom).unwrap_or(T::zero());
1154
1155 // t^2, used to advance the power each iteration: t, t^3, t^5, ...
1156 let t_sq = t.checked_mul(&t).unwrap_or(T::zero());
1157
1158 let mut sum = T::zero();
1159 let mut power = t;
1160
1161 for i in 0u32..MAX_ITERATIONS {
1162 let denom_fp = T::saturating_from_integer(2 * i + 1);
1163
1164 // Current term: t^(2k+1) / (2k+1)
1165 let term = power.checked_div(&denom_fp).unwrap_or(T::zero());
1166 if term.saturating_abs() <= eps {
1167 break;
1168 }
1169
1170 let new_sum = sum.saturating_add(term);
1171
1172 // Stagnation: sum is no longer changing at ULP level.
1173 if new_sum == sum {
1174 break;
1175 }
1176
1177 sum = new_sum;
1178 power = power.checked_mul(&t_sq).unwrap_or(T::zero());
1179 }
1180
1181 // ln(y) = 2 * sum.
1182 sum.saturating_mul(two)
1183}
1184
1185// ===============================================================================
1186// ``````````````````````````` POWER - INTEGER & BINARY ``````````````````````````
1187// ===============================================================================
1188
1189/// Raises a fixed-point number `x` to an integer power `n` using
1190/// binary exponentiation.
1191///
1192/// ## Behavior
1193///
1194/// | Case | Result |
1195/// |--------------------|---------------------------------|
1196/// | `n = 0` | `Some(1)` - `x^0 = 1` always |
1197/// | `n > 0` | `Some(x^n)` |
1198/// | `n < 0, x != 0` | `Some(1 / x^|n|)` |
1199/// | `n < 0, x = 0` | `None` - division by zero |
1200///
1201/// Uses saturating arithmetic throughout, so intermediate overflow clamps
1202/// to the type's maximum rather than wrapping or panicking. Returns `None`
1203/// only for division-by-zero or when `1 / x^|n|` is unrepresentable.
1204///
1205/// ## Arguments
1206///
1207/// * `x` - The fixed-point base.
1208/// * `n` - The integer exponent, including `i128::MIN`.
1209fn fixed_powi<T>(x: T, n: i128) -> Option<T>
1210where
1211 T: FixedPointNumber + Copy,
1212{
1213 let one = T::one();
1214 let zero = T::zero();
1215
1216 // x^0 = 1 for all x, including x = 0.
1217 // The caller (fixed_pow) guards 0^0 before reaching here
1218 if n == 0 {
1219 return Some(one);
1220 }
1221
1222 if n < 0 {
1223 // 0^(-n) is division by zero - undefined.
1224 if x == zero {
1225 return None;
1226 }
1227
1228 // x^(-n) = 1 / x^|n|.
1229 // unsigned_abs() handles n = i128::MIN without overflow.
1230 let pos = fixed_powi_positive(x, n.unsigned_abs());
1231 return one.checked_div(&pos);
1232 }
1233
1234 Some(fixed_powi_positive(x, n as u128))
1235}
1236
1237/// Core binary exponentiation for non-negative integer powers.
1238///
1239/// Computes `x^n` in `O(log n)` multiplications using the
1240/// square-and-multiply algorithm. Extracted as a separate function
1241/// so both the positive and negative paths of [`fixed_powi`] can
1242/// share the same implementation.
1243///
1244/// Uses saturating arithmetic - intermediate overflow clamps to the
1245/// type's maximum rather than wrapping or panicking.
1246///
1247/// ## Arguments
1248///
1249/// * `x` - The fixed-point base.
1250/// * `n` - The non-negative exponent as `u128`.
1251fn fixed_powi_positive<T>(x: T, mut n: u128) -> T
1252where
1253 T: FixedPointNumber + Copy,
1254{
1255 let one = T::one();
1256 let mut result = one;
1257 let mut base = x;
1258
1259 while n > 0 {
1260 // If the current bit is set, multiply result by the current base power.
1261 if (n & 1) == 1 {
1262 result = result.saturating_mul(base);
1263 }
1264 n >>= 1;
1265 // Square the base for the next bit position.
1266 // Guard avoids a redundant squaring on the final iteration.
1267 if n > 0 {
1268 base = base.saturating_mul(base);
1269 }
1270 }
1271
1272 result
1273}
1274
1275// ===============================================================================
1276// `````````````````````````````````` FIXED-SQRT `````````````````````````````````
1277// ===============================================================================
1278
1279/// Computes the square root of a fixed-point number [`FixedPointNumber`] `x`.
1280///
1281/// Uses the Newton-Raphson method internally via [`fixed_sqrt_newton`] for
1282/// the general case, with exact fast paths for the common values `0` and `1`.
1283///
1284/// ## Domain
1285///
1286/// Defined only for `x >= 0`. Returns `None` for negative inputs, as the square
1287/// root of a negative number is not real-valued.
1288///
1289/// # Arguments
1290///
1291/// * `x` - The fixed-point number to compute the square root of.
1292///
1293/// # Returns
1294///
1295/// * `Some(sqrt(x))` for `x >= 0`
1296/// * `None` for `x < 0`
1297///
1298/// # Examples
1299///
1300/// ```ignore
1301/// let x = FixedU64::saturating_from_integer(4);
1302/// assert_eq!(fixed_sqrt(&x), Some(FixedU64::saturating_from_integer(2)));
1303///
1304/// let x = FixedI64::saturating_from_integer(-1);
1305/// assert_eq!(fixed_sqrt(&x), None);
1306/// ```
1307fn fixed_sqrt<F: FixedPointNumber>(x: &F) -> Option<F>
1308where
1309 // Require the inner integer type to be constructible from u8 literals,
1310 // which is common for fixed-point arithmetic types.
1311 F::Inner: From<u8>,
1312{
1313 let zero = F::zero();
1314 let one = F::one();
1315
1316 // --- DOMAIN CHECK ---
1317 // sqrt(x) is undefined for x < 0 in real arithmetic.
1318 if *x < zero {
1319 return None;
1320 }
1321
1322 // --- FAST PATHS ---
1323 // Exact results for boundary values, avoids unnecessary Newton iterations.
1324
1325 // sqrt(0) = 0 exactly.
1326 if *x == zero {
1327 return Some(zero);
1328 }
1329
1330 // sqrt(1) = 1 exactly.
1331 if *x == one {
1332 return Some(one);
1333 }
1334
1335 // Delegates to Newton-Raphson for all other values.
1336 // See [`fixed_sqrt_newton`] for convergence details.
1337 Some(fixed_sqrt_newton::<F>(x))
1338}
1339
1340// ===============================================================================
1341// ````````````````````````````````` COMPLEX-SQRT ````````````````````````````````
1342// ===============================================================================
1343
1344/// Computes the principal square root of a fixed-point number, returning
1345/// a [`Complex`] result.
1346///
1347/// Unlike [`fixed_sqrt`], this function is defined for all inputs including
1348/// negative numbers. For negative inputs, the result is a purely imaginary
1349/// number representing the principal square root in the complex plane.
1350///
1351/// Internally delegates to [`fixed_sqrt_newton`] for the real square root
1352/// computation, which is only valid for non-negative inputs. The sign of `x`
1353/// is handled here before dispatching.
1354///
1355/// ## Domain
1356///
1357/// Defined for all fixed-point values. Never returns `None`.
1358///
1359/// ## Arguments
1360///
1361/// * `x` - The fixed-point number to compute the complex square root of.
1362///
1363/// ## Returns
1364///
1365/// | Input | Result |
1366/// |----------|--------------------------|
1367/// | `x > 0` | `sqrt(x) + 0i` |
1368/// | `x = 0` | `0 + 0i` |
1369/// | `x < 0` | `0 + sqrt(|x|)i` |
1370///
1371/// ## Note
1372///
1373/// On unsigned types (`FixedU64`, `FixedU128`), negative values are not
1374/// representable, so the imaginary branch is never reached. Only the real
1375/// and zero branches apply.
1376///
1377/// ## Examples
1378///
1379/// ```ignore
1380/// // Positive input - purely real result
1381/// let x = FixedI64::saturating_from_integer(4);
1382/// assert_eq!(complex_sqrt(&x), Some(Complex { real: FixedI64::saturating_from_integer(2), imgn: FixedI64::zero() }));
1383///
1384/// // Negative input - purely imaginary result
1385/// let x = FixedI64::saturating_from_integer(-4);
1386/// assert_eq!(complex_sqrt(&x), Some(Complex { real: FixedI64::zero(), imgn: FixedI64::saturating_from_integer(2) }));
1387/// ```
1388fn complex_sqrt<F: FixedPointNumber>(x: &F) -> Option<Complex<F>>
1389where
1390 // Require the inner integer type to be constructible from u8 literals,
1391 // which is common for fixed-point arithmetic types.
1392 F::Inner: From<u8>,
1393{
1394 let zero = F::zero();
1395
1396 // --- FAST PATH ---
1397 // sqrt(0) = 0 + 0i exactly.
1398 if *x == zero {
1399 return Some(Complex::new(zero, zero));
1400 }
1401
1402 // --- NEGATIVE INPUT ---
1403 // sqrt(x) for x < 0 is purely imaginary: sqrt(x) = 0 + sqrt(|x|)i.
1404 // Take the magnitude first since fixed_sqrt_newton requires a non-negative input.
1405 if *x < zero {
1406 let mag = x.saturating_abs();
1407 let imgn = fixed_sqrt_newton::<F>(&mag);
1408 return Some(Complex::new(zero, imgn));
1409 }
1410
1411 // sqrt(x) for x > 0 is purely real: sqrt(x) = sqrt(x) + 0i.
1412 let real = fixed_sqrt_newton::<F>(x);
1413 Some(Complex::new(real, zero))
1414}
1415
1416// ===============================================================================
1417// `````````````````````````````````` FIXED-EXP ``````````````````````````````````
1418// ===============================================================================
1419
1420/// Computes `e^x` for a fixed-point number using argument reduction and
1421/// a Taylor series expansion.
1422///
1423/// ## Algorithm
1424///
1425/// Splits `x = n + r` where `n` is the integer part and `|r| <= 0.5`:
1426///
1427/// ```text
1428/// exp(x) = exp(n) * exp(r)
1429/// ```
1430///
1431/// `exp(r)` is computed via Taylor series (fast for small `|r|`).
1432/// `exp(n)` is computed by raising `e ~= 2.718281828459045235` to integer
1433/// power `n` via binary exponentiation ([`fixed_powi`]).
1434///
1435/// `e` is approximated as `2_718_281_828_459_045_235 / 10^18`, giving
1436/// 18 significant figures - matching the full precision of `FixedU128`
1437/// and over-specified but harmless for the other three types.
1438///
1439/// ## Domain
1440///
1441/// Defined for all fixed-point values, but:
1442/// - Large positive `x` overflows the fixed-point range - returns `None`.
1443/// - Large negative `x` underflows to zero - returns `Some(0)`.
1444/// Threshold: `x < -(DECIMAL_PLACES * 10)`.
1445///
1446/// ## Arguments
1447///
1448/// * `x` - The fixed-point exponent value.
1449///
1450/// ## Returns
1451///
1452/// * `Some(exp(x))` on success
1453/// * `Some(0)` when `x` is below the underflow threshold
1454/// * `None` on overflow, or if internal arithmetic fails
1455///
1456/// # Examples
1457///
1458/// ```ignore
1459/// let x = FixedU64::saturating_from_integer(1);
1460/// let result = fixed_exp(&x).unwrap();
1461/// // result ~= 2.718281828
1462/// ```
1463fn fixed_exp<T>(x: &T) -> Option<T>
1464where
1465 T: FixedPointNumber + Copy + PartialOrd + FixedPointInfo,
1466 T::Inner: From<u8> + Shr<u32, Output = T::Inner> + TryInto<i128> + Copy,
1467{
1468 let zero = T::zero();
1469 let one = T::one();
1470
1471 // --- FAST PATH ---
1472 // exp(0) = 1 exactly.
1473 if *x == zero {
1474 return Some(one);
1475 }
1476
1477 // Underflow guard: for sufficiently large negative x, exp(x) is below
1478 // the smallest representable value. Return zero rather than iterating
1479 // toward an unrepresentable result.
1480 let neg_threshold = T::saturating_from_integer(
1481 -((T::DECIMAL_PLACES as i32).saturating_mul(10))
1482 );
1483 if *x < neg_threshold {
1484 return Some(zero);
1485 }
1486
1487 // Argument reduction: split x = n + r, |r| <= 0.5.
1488 // The Taylor series for exp(r) converges much faster for small |r|.
1489 let n_i128: i128 = {
1490 let inner: i128 = x.into_inner().try_into().ok()?;
1491 let div: i128 = T::DIV.try_into().ok()?;
1492 // Truncate toward zero - standard Rust integer division semantics.
1493 inner / div
1494 };
1495 let n = n_i128.clamp(i32::MIN as i128, i32::MAX as i128) as i32;
1496 let n_fixed = T::saturating_from_integer(n);
1497
1498 // r = x - n, guaranteed |r| <= 0.5 by construction.
1499 let r = x.saturating_sub(n_fixed);
1500
1501 // Taylor series: exp(r) = 1 + r + r^2/2! + r^3/3! + ...
1502 // Terms are computed incrementally: term_i = term_{i-1} * r / i.
1503 let epsilon = ulp::<T>();
1504 let mut sum = one; // Accumulates the series result, starts at the i=0 term (1).
1505 let mut term = one; // Tracks the current series term, starts at 1.
1506
1507 for i in 1u32..=MAX_ITERATIONS {
1508 let i_fixed = T::saturating_from_integer(i);
1509
1510 // next_term = term * r / i.
1511 // Both operations fall back to zero on failure rather than propagating
1512 // None - a failed multiply or divide means the term is negligibly small.
1513 let next_term = term.checked_mul(&r)
1514 .unwrap_or(zero)
1515 .checked_div(&i_fixed)
1516 .unwrap_or(zero);
1517
1518 // saturating_abs() correctly handles negative terms (negative x alternates
1519 // term signs). A plain comparison without abs() would miss converged
1520 // negative terms entirely.
1521 if next_term.saturating_abs() <= epsilon {
1522 break;
1523 }
1524
1525 let new_sum = sum.saturating_add(next_term);
1526
1527 // Sum is no longer changing - saturated or converged at ULP boundary.
1528 if new_sum == sum {
1529 break;
1530 }
1531
1532 sum = new_sum;
1533 term = next_term;
1534 }
1535
1536 // exp(r) is now in `sum`.
1537
1538 // No integer scaling needed when the integer part is zero.
1539 if n == 0 {
1540 return Some(sum);
1541 }
1542
1543 // Scale back: exp(x) = exp(r) * exp(n) = sum * e^n.
1544 let e = T::saturating_from_rational(
1545 2_718_281_828_459_045_235u128,
1546 1_000_000_000_000_000_000u128,
1547 );
1548
1549 let exp_n = fixed_powi(e, n as i128)?;
1550
1551 // If exp_n has already saturated to max_value, multiplying by sum (>= 1
1552 // for positive x) would overflow. Return None rather than a silent saturated result.
1553 if exp_n >= T::max_value() {
1554 return None;
1555 }
1556
1557 // Final result: exp(x) = exp(r) * exp(n).
1558 // checked_mul returns None on overflow, propagating cleanly to the caller.
1559 sum.checked_mul(&exp_n)
1560}
1561
1562// ===============================================================================
1563// ``````````````````````````````````` FIXED-LN ``````````````````````````````````
1564// ===============================================================================
1565
1566/// Computes the natural logarithm `ln(x)` for a fixed-point number.
1567///
1568/// ## Algorithm
1569///
1570/// Uses repeated square root range reduction to bring `x` near `1`,
1571/// then evaluates `ln` via the series expansion in `ln_near_one`:
1572///
1573/// ```text
1574/// ln(x) = 2^k * ln(y)
1575/// ```
1576///
1577/// where `y` is the range-reduced value near `1` and `k` is the number
1578/// of square root reductions applied. The scaling back is done with a
1579/// single multiply by `2^k` to avoid accumulated rounding error from
1580/// repeated multiplication.
1581///
1582/// ## Domain
1583///
1584/// - Defined only for `x > 0`. Returns `None` for `x <= 0`.
1585/// - On unsigned types (`FixedU64`, `FixedU128`), `ln(x)` for `x < 1`
1586/// produces a negative result which is unrepresentable. Returns `None`
1587/// in this case rather than silently returning a wrong answer.
1588/// Use a signed type (`FixedI64`, `FixedI128`) if `ln` of fractional
1589/// values is needed.
1590///
1591/// ## Arguments
1592///
1593/// * `x` - The fixed-point number to compute the natural logarithm of.
1594///
1595/// ## Returns
1596///
1597/// * `Some(ln(x))` for valid inputs
1598/// * `None` for `x <= 0`
1599/// * `None` for unsigned types where `x < 1` (result not representable)
1600///
1601/// ## Examples
1602///
1603/// ```ignore
1604/// let x = FixedU64::saturating_from_integer(1);
1605/// assert_eq!(fixed_ln(&x), Some(FixedU64::zero())); // ln(1) = 0
1606///
1607/// let x = FixedU64::saturating_from_integer(2);
1608/// let result = fixed_ln(&x).unwrap();
1609/// // result ~= 0.693147180
1610///
1611/// // Negative input - always None
1612/// let x = FixedI64::saturating_from_integer(-1);
1613/// assert_eq!(fixed_ln(&x), None);
1614///
1615/// // Fractional input on unsigned type - None (unrepresentable result)
1616/// let x = FixedU64::saturating_from_rational(1, 2);
1617/// assert_eq!(fixed_ln(&x), None);
1618///
1619/// // Fractional input on signed type - correct negative result
1620/// let x = FixedI64::saturating_from_rational(1, 2);
1621/// let result = fixed_ln(&x).unwrap();
1622/// // result ~= -0.693147180
1623/// ```
1624fn fixed_ln<T>(x: &T) -> Option<T>
1625where
1626 T: FixedPointNumber + Copy + PartialOrd + FixedPointInfo ,
1627 T::Inner: From<u8> + Shr<u32, Output = T::Inner> + TryInto<i128> + Copy,
1628{
1629 let zero = T::zero();
1630 let one = T::one();
1631
1632 // --- DOMAIN CHECK ---
1633 // ln(x) is undefined for x <= 0 in real arithmetic.
1634 if *x <= zero {
1635 return None;
1636 }
1637
1638 // --- FAST PATH ---
1639 // ln(1) = 0 exactly.
1640 if *x == one {
1641 return Some(zero);
1642 }
1643
1644 // Detect unsigned types: on unsigned types, `0 - 1` saturates to `0`
1645 // rather than wrapping to `-1`. For such types, ln(x < 1) would return
1646 // the incorrect `Some(0)` from the series - return None instead.
1647 let is_unsigned = zero.saturating_sub(one) == zero;
1648 if is_unsigned && *x < one {
1649 return None;
1650 }
1651
1652 let (y_reduced, k) = range_reduce_sqrt(*x);
1653 let mut ln_val = ln_near_one(y_reduced);
1654
1655 // Recover ln(x) = 2^k * ln(y_reduced).
1656 // k can reach up to MAX_ITERATIONS (50). Shifting by more than 31 would
1657 // panic in debug mode (1u32 << 32 is UB). When k > 31 the true result
1658 // is 2^k * ln(y_reduced) with k >= 32, meaning the result exceeds
1659 // 2^32 * ln(y_reduced) - astronomically large for any fixed-point type.
1660 // Return None rather than a silently wrong clamped value.
1661 if k > 31 {
1662 return None;
1663 }
1664
1665 if k > 0 {
1666 let scale = T::saturating_from_integer(1u32 << k);
1667 ln_val = ln_val.saturating_mul(scale);
1668 }
1669
1670 Some(ln_val)
1671}
1672
1673// ===============================================================================
1674// ``````````````````````````````````` FIXED-POW `````````````````````````````````
1675// ===============================================================================
1676
1677/// Computes `x^p` for fixed-point numbers.
1678///
1679/// ## Algorithm
1680///
1681/// Three computation paths depending on the inputs:
1682///
1683/// - **Integer exponent**: uses binary exponentiation via `fixed_powi`
1684/// for exact, efficient results.
1685/// - **Fractional exponent**: uses the identity `x^p = exp(p * ln(x))`
1686/// via [`fixed_exp`] and [`fixed_ln`].
1687/// - **Special cases**: handled directly with exact results.
1688///
1689/// ## Domain
1690///
1691/// | Input condition | Result | Reason |
1692/// |------------------------------|---------------------|-------------------------------------|
1693/// | `x = 0, p > 0` | `Some(0)` | Mathematical limit |
1694/// | `x = 0, p = 0` | `None` | Indeterminate form |
1695/// | `x = 0, p < 0` | `None` | Division by zero |
1696/// | `x < 0, p` non-integer | `None` | Not real-valued |
1697/// | `x < 0, p` integer | `Some(x^p)` | Real-valued, handled by `fixed_powi` |
1698/// | `p = 0` | `Some(1)` | `x^0 = 1` for all non-zero `x` |
1699/// | `x = 1` | `Some(1)` | `1^p = 1` for all `p` |
1700///
1701/// ## Arguments
1702///
1703/// * `x` - The base as a fixed-point number.
1704/// * `p` - The exponent as a fixed-point number.
1705///
1706/// ## Returns
1707///
1708/// * `Some(x^p)` on success
1709/// * `None` for indeterminate or undefined inputs (see domain table above)
1710/// * `None` on overflow
1711///
1712/// ## Examples
1713///
1714/// ```ignore
1715/// // Integer exponent
1716/// let x = FixedU64::saturating_from_integer(2);
1717/// let p = FixedU64::saturating_from_integer(3);
1718/// assert_eq!(fixed_pow(&x, &p), Some(FixedU64::saturating_from_integer(8)));
1719///
1720/// // Fractional exponent
1721/// let x = FixedU64::saturating_from_integer(4);
1722/// let p = FixedU64::saturating_from_rational(1, 2);
1723/// let result = fixed_pow(&x, &p).unwrap();
1724/// // result ~= 2.0 (square root of 4)
1725///
1726/// // Undefined cases
1727/// let zero = FixedU64::zero();
1728/// assert_eq!(fixed_pow(&zero, &zero), None); // 0^0 indeterminate
1729/// ```
1730fn fixed_pow<T>(x: &T, p: &T) -> Option<T>
1731where
1732 T: FixedPointNumber + Copy + PartialOrd + FixedPointInfo,
1733 T::Inner: From<u8> + Shr<u32, Output = T::Inner> + TryInto<i128> + Copy,
1734{
1735 let zero = T::zero();
1736 let one = T::one();
1737
1738 // --- DOMAIN VALIDATION ---
1739 // 0^0 is indeterminate; 0^(negative) is division by zero.
1740 if *x == zero && *p <= zero {
1741 return None;
1742 }
1743
1744 // 0^(positive) = 0. Handles both integer and fractional positive p,
1745 // consistent with the mathematical limit. Guarded explicitly because
1746 // the general path would call ln(0) which is undefined.
1747 if *x == zero {
1748 return Some(zero);
1749 }
1750
1751 // Negative base with a fractional exponent is not real-valued.
1752 // Integer exponents are handled below by fixed_powi.
1753 let int_exp = fixed_to_i128(p);
1754 if *x < zero && int_exp.is_none() {
1755 return None;
1756 }
1757
1758 // --- FAST PATHS ---
1759 // x^0 = 1 for all non-zero x (zero case already handled above).
1760 if *p == zero {
1761 return Some(one);
1762 }
1763
1764 // 1^p = 1 for all p.
1765 if *x == one {
1766 return Some(one);
1767 }
1768
1769 // Binary exponentiation is exact and significantly cheaper than
1770 // the general exp(p * ln(x)) path. Also the only valid path for
1771 // negative bases, where ln(x) is undefined.
1772 if let Some(n) = int_exp {
1773 return fixed_powi(*x, n);
1774 }
1775
1776 // General case: x^p = exp(p * ln(x)).
1777 // Requires x > 0, which is guaranteed at this point:
1778 // - x = 0 was handled above
1779 // - x < 0 with fractional p was rejected above
1780 //
1781 // Overflow: if p * ln(x) exceeds the fixed-point range, saturating_mul
1782 // clamps it. fixed_exp then receives a saturated value and returns either
1783 // None (overflow guard) or Some(0) (underflow guard), both of which
1784 // propagate correctly to the caller.
1785 let ln_x = fixed_ln(x)?;
1786 let exponent = p.saturating_mul(ln_x);
1787 fixed_exp(&exponent)
1788}
1789
1790// ===============================================================================
1791// ````````````````````````````````` TRAIT FACADES ```````````````````````````````
1792// ===============================================================================
1793
1794/// Unified interface for core fixed-point mathematical operations.
1795///
1796/// Implemented for all four fixed-point types: [`FixedU64`], [`FixedU128`],
1797/// [`FixedI64`], [`FixedI128`]. Enables generic code that works across the
1798/// entire fixed-point family through a single trait bound.
1799pub trait FixedOp
1800where
1801 Self: Sized,
1802{
1803 /// Square root (real domain).
1804 fn fixed_sqrt(f: &Self) -> Option<Self>;
1805 /// General power `x^p` (integer and fractional exponents).
1806 fn fixed_pow(f: &Self, p: &Self) -> Option<Self>;
1807 /// Natural exponential ( e^x ).
1808 fn fixed_exp(f: &Self) -> Option<Self>;
1809 /// Natural logarithm ( ln(x) ).
1810 fn fixed_ln(f: &Self) -> Option<Self>;
1811}
1812
1813/// Interface for complex-valued fixed-point operations.
1814///
1815/// Extends the real-domain operations in [`FixedOp`] with functions whose
1816/// results may be complex-valued.
1817pub trait FixedComplexOp
1818where
1819 Self: Sized,
1820{
1821 /// Square root in complex domain.
1822 fn complex_sqrt(f: &Self) -> Option<Complex<Self>>;
1823}
1824
1825// --- FixedOp Implementations ---
1826
1827/// FixedOp implementation for FixedU64.
1828impl FixedOp for FixedU64 {
1829 fn fixed_sqrt(f: &Self) -> Option<Self> {
1830 fixed_sqrt(f)
1831 }
1832 fn fixed_pow(f: &Self, p: &Self) -> Option<Self> {
1833 fixed_pow(f, p)
1834 }
1835 fn fixed_exp(f: &Self) -> Option<Self> {
1836 fixed_exp(f)
1837 }
1838 fn fixed_ln(f: &Self) -> Option<Self> {
1839 fixed_ln(f)
1840 }
1841}
1842
1843/// FixedOp implementation for FixedU128.
1844impl FixedOp for FixedU128 {
1845 fn fixed_sqrt(f: &Self) -> Option<Self> {
1846 fixed_sqrt(f)
1847 }
1848 fn fixed_pow(f: &Self, p: &Self) -> Option<Self> {
1849 fixed_pow(f, p)
1850 }
1851 fn fixed_exp(f: &Self) -> Option<Self> {
1852 fixed_exp(f)
1853 }
1854 fn fixed_ln(f: &Self) -> Option<Self> {
1855 fixed_ln(f)
1856 }
1857}
1858
1859/// FixedOp implementation for FixedI64.
1860impl FixedOp for FixedI64 {
1861 fn fixed_sqrt(f: &Self) -> Option<Self> {
1862 fixed_sqrt(f)
1863 }
1864 fn fixed_pow(f: &Self, p: &Self) -> Option<Self> {
1865 fixed_pow(f, p)
1866 }
1867 fn fixed_exp(f: &Self) -> Option<Self> {
1868 fixed_exp(f)
1869 }
1870 fn fixed_ln(f: &Self) -> Option<Self> {
1871 fixed_ln(f)
1872 }
1873}
1874
1875/// FixedOp implementation for FixedI128.
1876impl FixedOp for FixedI128 {
1877 fn fixed_sqrt(f: &Self) -> Option<Self> {
1878 fixed_sqrt(f)
1879 }
1880 fn fixed_pow(f: &Self, p: &Self) -> Option<Self> {
1881 fixed_pow(f, p)
1882 }
1883 fn fixed_exp(f: &Self) -> Option<Self> {
1884 fixed_exp(f)
1885 }
1886 fn fixed_ln(f: &Self) -> Option<Self> {
1887 fixed_ln(f)
1888 }
1889}
1890
1891// --- FixedComplexOp Implementations ---
1892
1893/// FixedComplexOp implementation for FixedU64.
1894impl FixedComplexOp for FixedU64 {
1895 fn complex_sqrt(f: &Self) -> Option<Complex<Self>> {
1896 complex_sqrt(f)
1897 }
1898}
1899
1900/// FixedComplexOp implementation for FixedI64.
1901impl FixedComplexOp for FixedI64 {
1902 fn complex_sqrt(f: &Self) -> Option<Complex<Self>> {
1903 complex_sqrt(f)
1904 }
1905}
1906
1907/// FixedComplexOp implementation for FixedU128.
1908impl FixedComplexOp for FixedU128 {
1909 fn complex_sqrt(f: &Self) -> Option<Complex<Self>> {
1910 complex_sqrt(f)
1911 }
1912}
1913
1914/// FixedComplexOp implementation for FixedI128.
1915impl FixedComplexOp for FixedI128 {
1916 fn complex_sqrt(f: &Self) -> Option<Complex<Self>> {
1917 complex_sqrt(f)
1918 }
1919}
1920
1921// ===============================================================================
1922// ```````````````````````````````` PLANNED EXTENSIONS ```````````````````````````
1923// ===============================================================================
1924
1925// pub trait FixedOp
1926// where
1927// Self: Sized,
1928// {
1929// // ------------------------
1930// // Roots & Powers
1931// // ------------------------
1932// // Cube root
1933// // fn fixed_cbrt(f: &Self) -> Self;
1934// // Integer powers
1935// // fn fixed_powi(f: &Self, n: i32) -> Self;
1936// // n-th root
1937// // fn fixed_root(f: &Self, n: &Self) -> Self;
1938// // Square of a number
1939// // fn fixed_square(f: &Self) -> Self;
1940// // Reciprocal
1941// // fn fixed_recip(f: &Self) -> Self;
1942
1943// // ------------------------
1944// // Exponential & Logarithmic Variants
1945// // ------------------------
1946// // 2^x
1947// // fn fixed_exp2(f: &Self) -> Self;
1948// // 10^x
1949// // fn fixed_exp10(f: &Self) -> Self;
1950// // Natural log
1951// // fn fixed_ln(f: &Self) -> Self;
1952// // log base 2
1953// // fn fixed_log2(f: &Self) -> Self;
1954// // log base 10
1955// // fn fixed_log10(f: &Self) -> Self;
1956// // Exponential minus 1 (exp(x) - 1)
1957// // fn fixed_expm1(f: &Self) -> Self;
1958// // Logarithm of 1+x (ln(1+x))
1959// // fn fixed_ln1p(f: &Self) -> Self;
1960// // Logarithmic gamma function
1961// // fn fixed_lgamma(f: &Self) -> Self;
1962
1963// // ------------------------
1964// // Trigonometric Functions
1965// // ------------------------
1966// // Sine
1967// // fn fixed_sin(f: &Self) -> Self;
1968// // Cosine
1969// // fn fixed_cos(f: &Self) -> Self;
1970// // Tangent
1971// // fn fixed_tan(f: &Self) -> Self;
1972// // Arc sine
1973// // fn fixed_asin(f: &Self) -> Self;
1974// // Arc cosine
1975// // fn fixed_acos(f: &Self) -> Self;
1976// // Arc tangent
1977// // fn fixed_atan(f: &Self) -> Self;
1978// // Arc tangent of y/x
1979// // fn fixed_atan2(y: &Self, x: &Self) -> Self;
1980
1981// // ------------------------
1982// // Hyperbolic Functions
1983// // ------------------------
1984// // Hyperbolic sine
1985// // fn fixed_sinh(f: &Self) -> Self;
1986// // Hyperbolic cosine
1987// // fn fixed_cosh(f: &Self) -> Self;
1988// // Hyperbolic tangent
1989// // fn fixed_tanh(f: &Self) -> Self;
1990// // Hyperbolic arc sine
1991// // fn fixed_asinh(f: &Self) -> Self;
1992// // Hyperbolic arc cosine
1993// // fn fixed_acosh(f: &Self) -> Self;
1994// // Hyperbolic arc tangent
1995// // fn fixed_atanh(f: &Self) -> Self;
1996
1997// // ------------------------
1998// // Special Functions
1999// // ------------------------
2000// // Error function
2001// // fn fixed_erf(f: &Self) -> Self;
2002// // Complementary error function
2003// // fn fixed_erfc(f: &Self) -> Self;
2004// // Gamma function
2005// // fn fixed_gamma(f: &Self) -> Self;
2006// // Factorial for integer values
2007// // fn fixed_fact(n: u32) -> Self;
2008// // Factorial for floating point (gamma variant)
2009// // fn fixed_factf(f: &Self) -> Self;
2010// // Binomial coefficient (n choose k)
2011// // fn fixed_binom(n: u32, k: u32) -> Self;
2012// // Signum function
2013// // fn fixed_sign(f: &Self) -> Self;
2014// // Clamp value between min and max
2015// // fn fixed_clamp(f: &Self, min: &Self, max: &Self) -> Self;
2016// // Floor
2017// // fn fixed_floor(f: &Self) -> Self;
2018// // Ceil
2019// // fn fixed_ceil(f: &Self) -> Self;
2020// // Round
2021// // fn fixed_round(f: &Self) -> Self;
2022// // Fractional part
2023// // fn fixed_frac(f: &Self) -> Self;
2024
2025// // ------------------------
2026// // Numeric & Scientific Utilities
2027// // ------------------------
2028// // Absolute value
2029// // fn fixed_abs(f: &Self) -> Self;
2030// // Euclidean norm for 2D or 3D (sqrt(x^2 + y^2))
2031// // fn fixed_hypot(x: &Self, y: &Self) -> Self;
2032// // Complex modulus squared
2033// // fn fixed_modsq(f: &Self) -> Self;
2034// // Power of 2 rounding (next_pow2)
2035// // fn fixed_next_pow2(f: &Self) -> Self;
2036// // Logarithm with arbitrary base
2037// // fn fixed_logb(f: &Self, base: &Self) -> Self;
2038// // Reciprocal square root (1/sqrt(x))
2039// // fn fixed_rsqrt(f: &Self) -> Self;
2040// }
2041
2042
2043// ===============================================================================
2044// `````````````````````````````````` UNIT TESTS `````````````````````````````````
2045// ===============================================================================
2046
2047#[cfg(test)]
2048mod tests {
2049 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2050 // ``````````````````````````````````` IMPORTS ```````````````````````````````````
2051 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2052
2053 // --- Module import ---
2054 use super::*;
2055
2056 // --- Substrate crates ---
2057 use sp_runtime::traits::{Bounded, One, Saturating, Zero};
2058
2059 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2060 // `````````````````````````````````` FIXED_SQRT `````````````````````````````````
2061 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2062
2063 #[test]
2064 fn fixed_sqrt_perfect_cases() {
2065 // case 1: sqrt(4) -> 2
2066 let x: FixedU64 = 4.into();
2067 let result = fixed_sqrt(&x).unwrap();
2068 let expected = FixedU64::saturating_from_integer(2);
2069 assert_eq!(result, expected);
2070
2071 // case 2: sqrt(36) -> 6
2072 let x: FixedU64 = 36.into();
2073 let result = fixed_sqrt(&x).unwrap();
2074 let expected = FixedU64::saturating_from_integer(6);
2075 assert_eq!(result, expected);
2076
2077 // case 3: sqrt(81) -> 9
2078 let x: FixedU64 = 81.into();
2079 let result = fixed_sqrt(&x).unwrap();
2080 let expected = FixedU64::saturating_from_integer(9);
2081 assert_eq!(result, expected);
2082
2083 // case 4: sqrt(1) -> 1
2084 let x: FixedU64 = 1.into();
2085 let result = fixed_sqrt(&x).unwrap();
2086 let expected = FixedU64::saturating_from_integer(1);
2087 assert_eq!(result, expected);
2088 }
2089
2090 #[test]
2091 fn fixed_sqrt_negative_perfect_cases() {
2092 // case 1: sqrt(-1) -> None
2093 let x: FixedI64 = (-1).into();
2094 let result = fixed_sqrt(&x);
2095 assert!(result.is_none());
2096
2097 // case 2: sqrt(-16) -> None
2098 let x: FixedI64 = (-16).into();
2099 let result = fixed_sqrt(&x);
2100 assert!(result.is_none());
2101
2102 // case 3: sqrt(-9) -> None
2103 let x: FixedI64 = (-9).into();
2104 let result = fixed_sqrt(&x);
2105 assert!(result.is_none());
2106
2107 // case 4: sqrt(-100) -> None
2108 let x: FixedI64 = (-100).into();
2109 let result = fixed_sqrt(&x);
2110 assert!(result.is_none());
2111
2112 // case 5: sqrt(-4) -> None
2113 let x: FixedI64 = (-4).into();
2114 let result = fixed_sqrt(&x);
2115 assert!(result.is_none());
2116 }
2117
2118 #[test]
2119 fn fixed_sqrt_large_numbers() {
2120 // case 1: sqrt(10000) -> 100
2121 let x: FixedU64 = 10000.into();
2122 let result = fixed_sqrt(&x).unwrap();
2123 let expected = FixedU64::saturating_from_integer(100);
2124 assert_eq!(result, expected);
2125
2126 // case 2: sqrt(1000000) -> 1000
2127 let x: FixedU64 = 1000000.into();
2128 let result = fixed_sqrt(&x).unwrap();
2129 let expected = FixedU64::saturating_from_integer(1000);
2130 assert_eq!(result, expected);
2131
2132 // case 3: sqrt(u32::MAX) -> 65535.999992370
2133 let x: FixedU64 = (u32::MAX as u64).into();
2134 let result = fixed_sqrt(&x).unwrap();
2135 let expected = FixedU64::from_inner(65535999992370);
2136 assert_eq!(result, expected);
2137
2138 // case 4: sqrt(u64::MAX) -> 135818.791312945
2139 let x: FixedU64 = (u64::MAX).into();
2140 let result = fixed_sqrt(&x).unwrap();
2141 let expected = FixedU64::from_inner(135818791312945);
2142 assert_eq!(result, expected);
2143 }
2144
2145 #[test]
2146 fn fixed_sqrt_non_perfect_cases() {
2147 // case 1: sqrt(2) ~= 1.414213562
2148 let x: FixedU64 = 2.into();
2149 let result = fixed_sqrt(&x).unwrap();
2150 let expected = FixedU64::from_inner(1414213562);
2151 assert_eq!(result, expected);
2152
2153 // case 2: sqrt(5) -> 2.236067977
2154 let x: FixedU64 = 5.into();
2155 let result = fixed_sqrt(&x).unwrap();
2156 let expected = FixedU64::from_inner(2236067977);
2157 assert_eq!(result, expected);
2158
2159 // case 3: sqrt(10) -> 3.162277660
2160 let x: FixedU64 = 10.into();
2161 let result = fixed_sqrt(&x).unwrap();
2162 let expected: FixedU64 = FixedU64::from_inner(3162277660);
2163 assert_eq!(result, expected);
2164
2165 // case 4: sqrt(125) -> 11.180339887
2166 let x: FixedU64 = 125.into();
2167 let result = fixed_sqrt(&x).unwrap();
2168 let expected: FixedU64 = FixedU64::from_inner(11180339887);
2169 assert_eq!(result, expected);
2170 }
2171
2172 #[test]
2173 fn fixed_sqrt_negative_non_perfect_squares() {
2174 // case 1: sqrt(-2) -> None
2175 let x: FixedI64 = (-2).into();
2176 let result = fixed_sqrt(&x);
2177 assert!(result.is_none());
2178
2179 // case 2: sqrt(-35) -> None
2180 let x: FixedI64 = (-35).into();
2181 let result = fixed_sqrt(&x);
2182 assert!(result.is_none());
2183
2184 // case 3: sqrt(-50) -> None
2185 let x: FixedI64 = (-50).into();
2186 let result = fixed_sqrt(&x);
2187 assert!(result.is_none());
2188 }
2189
2190 #[test]
2191 fn fixed_sqrt_fractional_cases() {
2192 // case 1: sqrt(0.25) -> 0.5
2193 let x = FixedU64::saturating_from_rational(1, 4);
2194 let result = fixed_sqrt(&x).unwrap();
2195 let expected = FixedU64::saturating_from_rational(1, 2);
2196 assert_eq!(result, expected);
2197
2198 // case 2: sqrt(0.01) -> 0.1
2199 let x = FixedU64::saturating_from_rational(1, 100);
2200 let result = fixed_sqrt(&x).unwrap();
2201 let expected = FixedU64::saturating_from_rational(1, 10);
2202 assert_eq!(result, expected);
2203
2204 // case 3: sqrt(0.5) -> 0.707106781
2205 let x = FixedU64::saturating_from_rational(1, 2);
2206 let result = fixed_sqrt(&x).unwrap();
2207 let expected = FixedU64::from_inner(707106781);
2208 assert_eq!(result, expected);
2209 }
2210
2211 #[test]
2212 fn fixed_sqrt_edge_cases() {
2213 // case 1: sqrt(0) -> 0
2214 let x: FixedU64 = 0.into();
2215 let result = fixed_sqrt(&x).unwrap();
2216 let expected = FixedU64::zero();
2217 assert_eq!(result, expected);
2218
2219 // case 2: sqrt(1) -> 1
2220 let x: FixedU64 = 1.into();
2221 let result = fixed_sqrt(&x).unwrap();
2222 let expected = FixedU64::one();
2223 assert_eq!(result, expected);
2224
2225 // case 3
2226 // sqrt(i64::MIN) -> saturating_abs gives i64::MAX
2227 let x: FixedI64 = (i64::MIN).into();
2228 let result = fixed_sqrt(&x);
2229 assert!(result.is_none());
2230
2231 // case 4
2232 // sqrt(i64::MAX) -> 96038.388349944
2233 let x = FixedI64::max_value();
2234 let result = fixed_sqrt(&x).unwrap();
2235 let expected = FixedI64::from_inner(96038388349944);
2236 assert_eq!(result, expected);
2237 }
2238
2239 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2240 // ````````````````````````````````` COMPLEX_SQRT ````````````````````````````````
2241 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2242
2243 #[test]
2244 fn complex_sqrt_perfect_cases() {
2245 // case 1: sqrt(4) -> 2
2246 let x: FixedU64 = 4.into();
2247 let result = complex_sqrt(&x).unwrap();
2248 let expected: Complex<FixedU64> = Complex {
2249 real: 2.into(),
2250 imgn: 0.into(),
2251 };
2252 assert_eq!(result, expected);
2253
2254 // case 2: sqrt(36) -> 6
2255 let x: FixedU64 = 36.into();
2256 let result = complex_sqrt(&x).unwrap();
2257 let expected: Complex<FixedU64> = Complex {
2258 real: 6.into(),
2259 imgn: 0.into(),
2260 };
2261 assert_eq!(result, expected);
2262
2263 // case 3: sqrt(81) -> 9
2264 let x: FixedU64 = 81.into();
2265 let result = complex_sqrt(&x).unwrap();
2266 let expected: Complex<FixedU64> = Complex {
2267 real: 9.into(),
2268 imgn: 0.into(),
2269 };
2270 assert_eq!(result, expected);
2271
2272 // case 4: sqrt(1) -> 1
2273 let x: FixedU64 = 1.into();
2274 let result = complex_sqrt(&x).unwrap();
2275 let expected = Complex {
2276 real: 1.into(),
2277 imgn: 0.into(),
2278 };
2279 assert_eq!(result, expected);
2280 }
2281
2282 #[test]
2283 fn complex_sqrt_negative_perfect_cases() {
2284 // case 1: sqrt(-1) -> 1i (imaginary)
2285 let x: FixedI64 = (-1).into();
2286 let result = complex_sqrt(&x).unwrap();
2287 let expected = Complex {
2288 real: 0.into(),
2289 imgn: 1.into(),
2290 };
2291 assert_eq!(result, expected);
2292
2293 // case 2:sqrt(-16) -> 4i (imaginary)
2294 let x: FixedI64 = (-16).into();
2295 let result = complex_sqrt(&x).unwrap();
2296 let expected = Complex {
2297 real: 0.into(),
2298 imgn: 4.into(),
2299 };
2300 assert_eq!(result, expected);
2301
2302 // case 3: sqrt(-9) -> 3i (imaginary)
2303 let x: FixedI64 = (-9).into();
2304 let result = complex_sqrt(&x).unwrap();
2305 let expected = Complex {
2306 real: 0.into(),
2307 imgn: 3.into(),
2308 };
2309 assert_eq!(result, expected);
2310
2311 // case 4: sqrt(-100) -> 10i (imaginary)
2312 let x: FixedI64 = (-100).into();
2313 let result = complex_sqrt(&x).unwrap();
2314 let expected = Complex {
2315 real: 0.into(),
2316 imgn: 10.into(),
2317 };
2318 assert_eq!(result, expected);
2319
2320 // case 5: sqrt(-4) -> 2i (imaginary)
2321 let x: FixedI64 = (-4).into();
2322 let result = complex_sqrt(&x).unwrap();
2323 let expected = Complex {
2324 real: 0.into(),
2325 imgn: 2.into(),
2326 };
2327 assert_eq!(result, expected);
2328 }
2329
2330 #[test]
2331 fn complex_sqrt_large_numbers() {
2332 // case 1: sqrt(10000) -> 100
2333 let x: FixedU64 = 10000.into();
2334 let result = complex_sqrt(&x).unwrap();
2335 let expected = Complex {
2336 real: 100.into(),
2337 imgn: 0.into(),
2338 };
2339 assert_eq!(result, expected);
2340
2341 // case 2: sqrt(1000000) -> 1000
2342 let x: FixedU64 = 1000000.into();
2343 let result = complex_sqrt(&x).unwrap();
2344 let expected = Complex {
2345 real: 1000.into(),
2346 imgn: 0.into(),
2347 };
2348 assert_eq!(result, expected);
2349
2350 // case 3: sqrt(u32::MAX) -> 65535.999992370
2351 let x: FixedU64 = (u32::MAX as u64).into();
2352 let result = complex_sqrt(&x).unwrap();
2353 let expected = Complex {
2354 real: FixedU64::from_inner(65535999992370),
2355 imgn: 0.into(),
2356 };
2357 assert_eq!(result, expected);
2358
2359 // case 4: sqrt(u64::MAX) -> 135818.791312945
2360 let x: FixedU64 = (u64::MAX).into();
2361 let result = complex_sqrt(&x).unwrap();
2362 let expected = Complex {
2363 real: FixedU64::from_inner(135818791312945),
2364 imgn: 0.into(),
2365 };
2366 assert_eq!(result, expected);
2367 }
2368
2369 #[test]
2370 fn complex_sqrt_non_perfect_cases() {
2371 // case 1: sqrt(2) ~= 1.414213562
2372 let x: FixedU64 = 2.into();
2373 let result = complex_sqrt(&x).unwrap();
2374 let expected = Complex {
2375 real: FixedU64::from_inner(1414213562),
2376 imgn: 0.into(),
2377 };
2378 assert_eq!(result, expected);
2379
2380 // case 2: sqrt(5) -> 2.236067977
2381 let x: FixedU64 = 5.into();
2382 let result = complex_sqrt(&x).unwrap();
2383 let expected = Complex {
2384 real: FixedU64::from_inner(2236067977),
2385 imgn: 0.into(),
2386 };
2387 assert_eq!(result, expected);
2388
2389 // case 3: sqrt(10) -> 3.162277660
2390 let x: FixedU64 = 10.into();
2391 let result = complex_sqrt(&x).unwrap();
2392 let expected: Complex<FixedU64> = Complex {
2393 real: FixedU64::from_inner(3162277660),
2394 imgn: 0.into(),
2395 };
2396 assert_eq!(result, expected);
2397
2398 // case 4: sqrt(125) -> 11.180339887
2399 let x: FixedU64 = 125.into();
2400 let result = complex_sqrt(&x).unwrap();
2401 let expected: Complex<FixedU64> = Complex {
2402 real: FixedU64::from_inner(11180339887),
2403 imgn: 0.into(),
2404 };
2405 assert_eq!(result, expected);
2406 }
2407
2408 #[test]
2409 fn complex_sqrt_negative_non_perfect_squares() {
2410 // case 1: sqrt(-2) -> 1.414213562i
2411 let x: FixedI64 = (-2).into();
2412 let result = complex_sqrt(&x).unwrap();
2413 let expected = Complex {
2414 real: 0.into(),
2415 imgn: FixedI64::from_inner(1414213562),
2416 };
2417 assert_eq!(result, expected);
2418
2419 // case 2: sqrt(-35) -> 5.916079783i (imaginary)
2420 let x: FixedI64 = (-35).into();
2421 let result = complex_sqrt(&x).unwrap();
2422 let expected = Complex {
2423 real: 0.into(),
2424 imgn: FixedI64::from_inner(5916079783),
2425 };
2426 assert_eq!(result, expected);
2427
2428 // case 3: sqrt(-50) -> 7.071067811i
2429 let x: FixedI64 = (-50).into();
2430 let result = complex_sqrt(&x).unwrap();
2431 let expected = Complex {
2432 real: 0.into(),
2433 imgn: FixedI64::from_inner(7071067811),
2434 };
2435 assert_eq!(result, expected);
2436 }
2437
2438 #[test]
2439 fn complex_sqrt_fractional_cases() {
2440 // case 1: sqrt(0.25) -> 0.5
2441 let x = FixedU64::saturating_from_rational(1, 4);
2442 let result = complex_sqrt(&x).unwrap();
2443 let expected = Complex {
2444 real: FixedU64::saturating_from_rational(1, 2),
2445 imgn: 0.into(),
2446 };
2447 assert_eq!(result, expected);
2448
2449 // case 2: sqrt(0.01) -> 0.1
2450 let x = FixedU64::saturating_from_rational(1, 100);
2451 let result = complex_sqrt(&x).unwrap();
2452 let expected = Complex {
2453 real: FixedU64::saturating_from_rational(1, 10),
2454 imgn: 0.into(),
2455 };
2456 assert_eq!(result, expected);
2457
2458 // case 3: sqrt(0.5) -> 0.707106781
2459 let x = FixedU64::saturating_from_rational(1, 2);
2460 let result = complex_sqrt(&x).unwrap();
2461 let expected = Complex {
2462 real: FixedU64::from_inner(707106781),
2463 imgn: 0.into(),
2464 };
2465 assert_eq!(result, expected);
2466 }
2467
2468 #[test]
2469 fn complex_sqrt_edge_cases() {
2470 // case 1: sqrt(0) -> 0
2471 let x: FixedU64 = 0.into();
2472 let result = complex_sqrt(&x).unwrap();
2473 let expected = Complex {
2474 real: 0.into(),
2475 imgn: 0.into(),
2476 };
2477 assert_eq!(result, expected);
2478
2479 // case 2: sqrt(1) -> 1
2480 let x: FixedU64 = 1.into();
2481 let result = complex_sqrt(&x).unwrap();
2482 let expected = Complex {
2483 real: 1.into(),
2484 imgn: 0.into(),
2485 };
2486 assert_eq!(result, expected);
2487
2488 // case 3
2489 // sqrt(i64::MAX) -> 96038.388349944
2490 let x = FixedI64::max_value();
2491 let result = complex_sqrt(&x).unwrap();
2492 let expected = Complex {
2493 real: FixedI64::from_inner(96038388349944),
2494 imgn: 0.into(),
2495 };
2496 assert_eq!(result, expected);
2497
2498 // case 4
2499 // sqrt(i64::MIN) -> saturating_abs gives i64::MAX
2500 let x: FixedI64 = (i64::MIN).into();
2501 let result = complex_sqrt(&x).unwrap();
2502 let expected = Complex {
2503 real: 0.into(),
2504 imgn: FixedI64::from_inner(96038388349944),
2505 };
2506 assert_eq!(result, expected);
2507 }
2508
2509 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2510 // `````````````````````````````````` FIXED_EXP ``````````````````````````````````
2511 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2512
2513 #[test]
2514 fn fixed_exp_normal_cases() {
2515 // case 1: exp(0) -> 1
2516 let x: FixedU64 = 0.into();
2517 let result = fixed_exp(&x).unwrap();
2518 let expected = 1.into();
2519 assert_eq!(result, expected);
2520
2521 // case 2: exp(1) -> 2.718281828
2522 let x: FixedU64 = 1.into();
2523 let result = fixed_exp(&x).unwrap();
2524 let expected = FixedU64::from_inner(2718281828);
2525 assert_eq!(result, expected);
2526
2527 // case 3: exp(2) -> 7.389056096
2528 let x: FixedU64 = 2.into();
2529 let result = fixed_exp(&x).unwrap();
2530 let expected = FixedU64::from_inner(7389056096);
2531 assert_eq!(result, expected);
2532
2533 // case 4: exp(3) -> 20.085536911
2534 let x: FixedU64 = 3.into();
2535 let result = fixed_exp(&x).unwrap();
2536 let expected = FixedU64::from_inner(20085536911);
2537 assert_eq!(result, expected);
2538
2539 // case 5: exp(5) -> 148.413158957
2540 let x: FixedU64 = 5.into();
2541 let result = fixed_exp(&x).unwrap();
2542 let expected = FixedU64::from_inner(148413158957);
2543 assert_eq!(result, expected);
2544 }
2545
2546 #[test]
2547 fn fixed_exp_positive_fractional_edge_cases() {
2548 // case 1: exp(0.5) -> 1.648721267
2549 let x = FixedU64::saturating_from_rational(1, 2);
2550 let result = fixed_exp(&x).unwrap();
2551 let expected = FixedU64::from_inner(1648721267);
2552 assert_eq!(result, expected);
2553
2554 // case 2: exp(0.1) -> 1.105170915
2555 let x = FixedU64::saturating_from_rational(1, 10);
2556 let result = fixed_exp(&x).unwrap();
2557 let expected = FixedU64::from_inner(1105170915);
2558 assert_eq!(result, expected);
2559
2560 // case 3: exp(0.001) -> 1.001000500
2561 let x = FixedU64::saturating_from_rational(1, 1000);
2562 let result = fixed_exp(&x).unwrap();
2563 let expected = FixedU64::from_inner(1001000500);
2564 assert_eq!(result, expected);
2565
2566 // case 4: exp(2.5) -> 12.182493928
2567 let x = FixedU64::saturating_from_rational(5, 2);
2568 let result = fixed_exp(&x).unwrap();
2569 let expected = FixedU64::from_inner(12182493928);
2570 assert_eq!(result, expected);
2571
2572 // case 5: exp(1.5) -> 4.481689059
2573 let x = FixedU64::saturating_from_rational(3, 2);
2574 let result = fixed_exp(&x).unwrap();
2575 let expected = FixedU64::from_inner(4481689059);
2576 assert_eq!(result, expected);
2577 }
2578
2579 #[test]
2580 fn fixed_exp_negative_edge_cases() {
2581 // case 1: exp(-1) -> 0.367879441
2582 let x: FixedI64 = (-1).into();
2583 let result = fixed_exp(&x).unwrap();
2584 let expected = FixedI64::from_inner(367879441);
2585 assert_eq!(result, expected);
2586
2587 // case 2: exp(-2) -> 0.135335383
2588 let x: FixedI64 = (-2).into();
2589 let result = fixed_exp(&x).unwrap();
2590 let expected = FixedI64::from_inner(135335283);
2591 assert_eq!(result, expected);
2592
2593 // case 3: exp(-0.5) -> 0.606530659
2594 let x = FixedI64::saturating_from_rational(-1, 2);
2595 let result = fixed_exp(&x).unwrap();
2596 let expected = FixedI64::from_inner(606530659);
2597 assert_eq!(result, expected);
2598
2599 // case 4: exp(-15) -> 0.000000305
2600 let x: FixedI64 = (-15).into();
2601 let result = fixed_exp(&x).unwrap();
2602 let expected = FixedI64::from_inner(000000305);
2603 assert_eq!(result, expected);
2604 }
2605
2606 #[test]
2607 fn fixed_exp_mathematical_constants() {
2608 // case 1:
2609 // exp(ln(2)) should be close to 2
2610 // ln(2) ~= 0.693147181
2611 let ln2 = FixedU64::from_inner(693147181);
2612 let result = fixed_exp(&ln2).unwrap();
2613 let expected: FixedU64 = 2.into();
2614
2615 // Allow small tolerance due to precision
2616 let diff = if result > expected {
2617 result.saturating_sub(expected)
2618 } else {
2619 expected.saturating_sub(result)
2620 };
2621 let tolerance = FixedU64::from_inner(1_000_000); // 0.001
2622 assert!(diff < tolerance);
2623
2624 // case 2:
2625 // exp(ln(10)) should be close to 10
2626 // ln(10) ~= 2.302585093
2627 let ln10 = FixedU64::from_inner(2302585093);
2628 let result = fixed_exp(&ln10).unwrap();
2629 let expected: FixedU64 = 10.into();
2630
2631 let diff = if result > expected {
2632 result.saturating_sub(expected)
2633 } else {
2634 expected.saturating_sub(result)
2635 };
2636 assert!(diff < tolerance);
2637 }
2638
2639 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2640 // ``````````````````````````````````` FIXED_LN ``````````````````````````````````
2641 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2642
2643 #[test]
2644 fn fixed_ln_at_one() {
2645 // case 1: ln(1) = 0 (real part), 0 (imaginary)
2646 let x: FixedU64 = 1.into();
2647 let result = fixed_ln(&x).unwrap();
2648 let expected = FixedU64::from_inner(0);
2649 assert_eq!(result, expected);
2650 }
2651
2652 #[test]
2653 fn fixed_ln_at_zero() {
2654 // case 1: ln(0) -> None
2655 let x: FixedU64 = 0.into();
2656 let result = fixed_ln(&x);
2657 assert!(result.is_none());
2658 }
2659
2660 #[test]
2661 fn fixed_ln_positive_integers() {
2662 // case 1: ln(2) ~= 0.693147172
2663 let x: FixedU64 = 2.into();
2664 let result = fixed_ln(&x).unwrap();
2665 let expected = FixedU64::from_inner(693147172);
2666 assert_eq!(result, expected);
2667
2668 // case 2: ln(3) ~= 1.098612248
2669 let x: FixedU64 = 3.into();
2670 let result = fixed_ln(&x).unwrap();
2671 let expected = FixedU64::from_inner(1098612248);
2672 assert_eq!(result, expected);
2673
2674 // case 3: ln(10) ~= 2.302585040
2675 let x: FixedU64 = 10.into();
2676 let result = fixed_ln(&x).unwrap();
2677 let expected = FixedU64::from_inner(2302585040);
2678 assert_eq!(result, expected);
2679
2680 // case 4: ln(100) ~= 4.605170180
2681 let x: FixedU64 = 100.into();
2682 let result = fixed_ln(&x).unwrap();
2683 let expected = FixedU64::from_inner(4605170080);
2684 assert_eq!(result, expected);
2685 }
2686
2687 #[test]
2688 fn fixed_ln_fractional_values() {
2689 // case 1: ln(0.5) ~= -0.693147174
2690 let x = FixedI64::saturating_from_rational(1, 2);
2691 let result = fixed_ln(&x).unwrap();
2692 let expected = FixedI64::from_inner(-693147174);
2693 assert_eq!(result, expected);
2694
2695 // case 2: ln(0.1) ~= -2.302585064
2696 let x = FixedI64::saturating_from_rational(1, 10);
2697 let result = fixed_ln(&x).unwrap();
2698 let expected = FixedI64::from_inner(-2302585064);
2699 assert_eq!(result, expected);
2700
2701 // case 3: ln(1.5) ~= 0.405465100
2702 let x = FixedU64::saturating_from_rational(3, 2);
2703 let result = fixed_ln(&x).unwrap();
2704 let expected = FixedU64::from_inner(405465100);
2705 assert_eq!(result, expected);
2706
2707 // case 4: ln(2.5) ~= 0.916290712
2708 let x = FixedU64::saturating_from_rational(5, 2);
2709 let result = fixed_ln(&x).unwrap();
2710 let expected = FixedU64::from_inner(916290712);
2711 assert_eq!(result, expected);
2712
2713 // case 5: ln(0.5) -> None
2714 let x = FixedU64::saturating_from_rational(1, 2);
2715 let result = fixed_ln(&x);
2716 assert!(result.is_none());
2717
2718 // case 6: ln(0.1) -> None
2719 let x = FixedU64::saturating_from_rational(1, 10);
2720 let result = fixed_ln(&x);
2721 assert!(result.is_none());
2722
2723 }
2724
2725 #[test]
2726 fn fixed_ln_negative_values() {
2727 // case 1: ln(-1) -> None
2728 let x: FixedI64 = (-1).into();
2729 let result = fixed_ln(&x);
2730 assert!(result.is_none());
2731
2732 // case 2: ln(-2) ~= 0.6931471872
2733 let x: FixedI64 = (-2).into();
2734 let result = fixed_ln(&x);
2735 assert!(result.is_none());
2736
2737 // case 3: ln(-10) ~= 2.302585040
2738 let x: FixedI64 = (-10).into();
2739 let result = fixed_ln(&x);
2740 assert!(result.is_none());
2741
2742 // case 4: ln(-0.5) ~= -0.693147174
2743 let x = FixedI64::saturating_from_rational(-1, 2);
2744 let result = fixed_ln(&x);
2745 assert!(result.is_none());
2746 }
2747
2748 #[test]
2749 fn fixed_ln_mathematical_constants() {
2750 // case 1: ln(e) should be ~= 1
2751 // e ~= 2.718281828
2752 let e = FixedU64::from_inner(2718281828);
2753 let result = fixed_ln(&e).unwrap();
2754 let expected = FixedU64::from_inner(1000000000);
2755
2756 let tolerance = FixedU64::from_inner(1_000_000); // 0.001
2757 let diff = if result > expected {
2758 result.saturating_sub(expected)
2759 } else {
2760 expected.saturating_sub(result)
2761 };
2762 assert!(diff < tolerance);
2763 }
2764
2765 #[test]
2766 fn fixed_ln_inverse_of_exp() {
2767 // case 1: x = 1
2768 let x: FixedU64 = 1.into();
2769 let exp_x = fixed_exp(&x).unwrap();
2770 let result = fixed_ln(&exp_x).unwrap();
2771
2772 let tolerance = FixedU64::from_inner(1_000_000); // 0.001
2773 let diff = if result > x {
2774 result.saturating_sub(x)
2775 } else {
2776 x.saturating_sub(result)
2777 };
2778 assert!(diff < tolerance);
2779
2780 // case 2: x = 2
2781 let x: FixedU64 = 2.into();
2782 let exp_x = fixed_exp(&x).unwrap();
2783 let result = fixed_ln(&exp_x).unwrap();
2784
2785 let diff = if result > x {
2786 result.saturating_sub(x)
2787 } else {
2788 x.saturating_sub(result)
2789 };
2790 assert!(diff < tolerance);
2791 }
2792
2793 #[test]
2794 fn fixed_ln_properties_verification() {
2795 // case 1: ln(a*b) = ln(a) + ln(b)
2796 // ln(2*3) = ln(6) should equal ln(2) + ln(3)
2797 let x: FixedU64 = 6.into();
2798 let ln_6 = fixed_ln(&x).unwrap();
2799
2800 let two: FixedU64 = 2.into();
2801 let three: FixedU64 = 3.into();
2802 let ln_2 = fixed_ln(&two).unwrap();
2803 let ln_3 = fixed_ln(&three).unwrap();
2804 let sum = ln_2.saturating_add(ln_3);
2805
2806 let tolerance = FixedU64::from_inner(1_000_000); // 0.001
2807 let diff = if ln_6 > sum {
2808 ln_6.saturating_sub(sum)
2809 } else {
2810 sum.saturating_sub(ln_6)
2811 };
2812 assert!(diff < tolerance);
2813 }
2814
2815 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2816 // `````````````````````````````````` FIXED_POW ``````````````````````````````````
2817 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2818
2819 #[test]
2820 fn fixed_pow_edge_cases() {
2821 // case 1: x = 1, p = 0
2822 let x: FixedU64 = 1.into();
2823 let p: FixedU64 = 0.into();
2824 let result = fixed_pow(&x, &p).unwrap();
2825 let expected: FixedU64 = 1.into();
2826 assert_eq!(result, expected);
2827
2828 // case 2: x = 0, p = 1
2829 let x: FixedU64 = 0.into();
2830 let p: FixedU64 = 1.into();
2831 let result = fixed_pow(&x, &p).unwrap();
2832 let expected: FixedU64 = 0.into();
2833 assert_eq!(result, expected);
2834
2835 // case 3: x = 1, p = 1
2836 let x: FixedU64 = 1.into();
2837 let p: FixedU64 = 1.into();
2838 let result = fixed_pow(&x, &p).unwrap();
2839 let expected: FixedU64 = 1.into();
2840 assert_eq!(result, expected);
2841
2842 // case 4: pow(0, -1) = 1 / 0 -> None
2843 let x: FixedI64 = 0.into();
2844 let p: FixedI64 = (-1).into();
2845 let result = fixed_pow(&x, &p);
2846 assert!(result.is_none());
2847 }
2848
2849 #[test]
2850 fn fixed_pow_normal_cases() {
2851 // case 1: x = 2, p = 3
2852 let x: FixedU64 = 2.into();
2853 let p: FixedU64 = 3.into();
2854 let result = fixed_pow(&x, &p).unwrap();
2855 let expected: FixedU64 = 8.into();
2856 assert_eq!(result, expected);
2857
2858 // case 2: x = 4, p = 5
2859 let x: FixedU64 = 4.into();
2860 let p: FixedU64 = 5.into();
2861 let result = fixed_pow(&x, &p).unwrap();
2862 let expected: FixedU64 = 1024.into();
2863 assert_eq!(result, expected);
2864
2865 // case 3: x = 8, p = 6
2866 let x: FixedU64 = 8.into();
2867 let p: FixedU64 = 6.into();
2868 let result = fixed_pow(&x, &p).unwrap();
2869 let expected: FixedU64 = 262144.into();
2870 assert_eq!(result, expected);
2871 }
2872
2873 #[test]
2874 fn fixed_pow_fractional_exponents() {
2875 let tolerance: FixedU64 = FixedU64::from_inner(1_000_000); // 0.001
2876
2877 // case 1: pow(4, 0.5) = 2
2878 let x: FixedU64 = 4.into();
2879 let p = FixedU64::saturating_from_rational(1, 2);
2880 let result = fixed_pow(&x, &p).unwrap();
2881 let expected: FixedU64 = 2.into();
2882 let diff = if result > expected {
2883 result.saturating_sub(expected)
2884 } else {
2885 expected.saturating_sub(result)
2886 };
2887 assert!(diff < tolerance);
2888
2889 // case 2: pow(9, 0.25) = sqrt(3) ~= 1.7320508
2890 let x: FixedU64 = 9.into();
2891 let p = FixedU64::saturating_from_rational(1, 4);
2892 let result = fixed_pow(&x, &p).unwrap();
2893 let expected = FixedU64::saturating_from_rational(1732051, 1_000_000); // ~= sqrt(3)
2894 let diff = if result > expected {
2895 result.saturating_sub(expected)
2896 } else {
2897 expected.saturating_sub(result)
2898 };
2899 assert!(diff < tolerance);
2900
2901 // case 3: pow(12, 0.35) ~= 2.386876
2902 let x: FixedU64 = 12.into();
2903 let p = FixedU64::saturating_from_rational(35, 100); // 0.35
2904 let result = fixed_pow(&x, &p).unwrap();
2905 let expected = FixedU64::saturating_from_rational(2_386_876, 1_000_000);
2906
2907 let diff = if result > expected {
2908 result.saturating_sub(expected)
2909 } else {
2910 expected.saturating_sub(result)
2911 };
2912 assert!(diff < tolerance);
2913 }
2914
2915 #[test]
2916 fn fixed_pow_fractional_base_integer_exp() {
2917 let tolerance = FixedU64::from_inner(1_000_000);
2918
2919 // case 1: pow(1.5, 2) = 2.25
2920 let x = FixedU64::saturating_from_rational(3, 2);
2921 let p: FixedU64 = 2.into();
2922 let result = fixed_pow(&x, &p).unwrap();
2923 let expected = FixedU64::saturating_from_rational(9, 4);
2924
2925 let diff = if result > expected {
2926 result.saturating_sub(expected)
2927 } else {
2928 expected.saturating_sub(result)
2929 };
2930 assert!(diff < tolerance);
2931
2932 // case 2: pow(3.5, 4) = 150.0625
2933 let x = FixedU64::saturating_from_rational(7, 2); // 3.5
2934 let p: FixedU64 = 4.into();
2935 let result = fixed_pow(&x, &p).unwrap();
2936 let expected = FixedU64::from_inner(150062500000);
2937 assert_eq!(result, expected);
2938 }
2939
2940 #[test]
2941 fn fixed_pow_negative_base_integer_exp() {
2942 // case 1: pow(-2, 3) = -8
2943 let x: FixedI64 = (-2).into();
2944 let p: FixedI64 = 3.into();
2945 let result = fixed_pow(&x, &p).unwrap();
2946 let expected: FixedI64 = (-8).into();
2947 assert_eq!(result, expected);
2948
2949 // case 2: pow(-6, 5) = -7776
2950 let x: FixedI64 = (-6).into();
2951 let p: FixedI64 = 5.into();
2952 let result = fixed_pow(&x, &p).unwrap();
2953 let expected: FixedI64 = (-7776).into();
2954 assert_eq!(result, expected);
2955
2956 // case 3: pow(-4, 2) = 16
2957 let x: FixedI64 = (-4).into();
2958 let p: FixedI64 = 2.into();
2959 let result = fixed_pow(&x, &p).unwrap();
2960 let expected: FixedI64 = (16).into();
2961 assert_eq!(result, expected);
2962 }
2963
2964 #[test]
2965 fn fixed_pow_negative_base_fractional_exp() {
2966 // case 1: pow(-4, 1/2) -> None
2967 let x: FixedI64 = (-4).into();
2968 let p = FixedI64::saturating_from_rational(1, 2);
2969 let result = fixed_pow(&x, &p);
2970 assert!(result.is_none());
2971
2972 // case 2: pow(-8, 1/3) -> None
2973 let x: FixedI64 = (-8).into();
2974 let p = FixedI64::saturating_from_rational(1, 3);
2975
2976 let result = fixed_pow(&x, &p);
2977 assert!(result.is_none())
2978 }
2979
2980 #[test]
2981 fn fixed_pow_large_integer_overflow_saturates() {
2982 // 2^128 overflows FixedU64
2983 let x: FixedU64 = 2.into();
2984 let p: FixedU64 = 128.into();
2985
2986 let result = fixed_pow(&x, &p).unwrap();
2987 let expected = u64::MAX.into();
2988 assert_eq!(result, expected);
2989 }
2990
2991 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2992 // `````````````````````````````` FIXED_SQRT_NEWTON ``````````````````````````````
2993 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2994
2995 #[test]
2996 fn fixed_sqrt_newton_standard_cases() {
2997 // case 1: x = 81 -> 9
2998 let x: FixedU64 = (81).into();
2999 let result = fixed_sqrt_newton(&x);
3000 let expected = 9.into();
3001 assert_eq!(result, expected);
3002
3003 // case 2: x = 49 -> 7
3004 let x: FixedU64 = (49).into();
3005 let result = fixed_sqrt_newton(&x);
3006 let expected = 7.into();
3007 assert_eq!(result, expected);
3008
3009 // case 3: x = 10 -> 3.162277660
3010 let x: FixedU64 = 10.into();
3011 let result = fixed_sqrt_newton(&x);
3012 let expected: FixedU64 = FixedU64::from_inner(3162277660);
3013 assert_eq!(result, expected);
3014
3015 // case 4: x = 7 -> 2.2645751311
3016 let x: FixedU64 = 7.into();
3017 let result = fixed_sqrt_newton(&x);
3018 let expected: FixedU64 = FixedU64::from_inner(2645751311);
3019 assert_eq!(result, expected);
3020 }
3021
3022 #[test]
3023 fn fixed_sqrt_newton_edge_cases() {
3024 // case 1: x = 0 -> 0
3025 let x: FixedU64 = (0).into();
3026 let result = fixed_sqrt_newton(&x);
3027 let expected = 0.into();
3028 assert_eq!(result, expected);
3029
3030 // case 2: x < 0 : -9 -> 0
3031 let x: FixedI64 = (-9).into();
3032 let result = fixed_sqrt_newton(&x);
3033 let expected = 0.into();
3034 assert_eq!(result, expected);
3035 }
3036
3037 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3038 // ``````````````````````````````````` FIXED_PI ``````````````````````````````````
3039 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3040
3041 #[test]
3042 fn fixed_pi_value_check() {
3043 // fixed_pi() -> 3.141592920
3044 let actual: FixedU64 = fixed_pi();
3045 let expected = FixedU64::from_inner(3141592920);
3046 assert_eq!(actual, expected);
3047 }
3048
3049 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3050 // `````````````````````````````` RANGE_REDUCES_SQRT `````````````````````````````
3051 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3052
3053 #[test]
3054 fn range_reduce_sqrt_already_in_range() {
3055 // case 1: y = 1 (already at target, no reduction needed)
3056 let y: FixedU64 = 1.into();
3057 let (reduced, k) = range_reduce_sqrt(y);
3058 assert_eq!(reduced, y);
3059 assert_eq!(k, 0);
3060
3061 // case 2: y = 1.5 (within [0.5, 1.5], no reduction)
3062 let y = FixedU64::saturating_from_rational(3, 2);
3063 let (reduced, k) = range_reduce_sqrt(y);
3064 assert_eq!(reduced, y);
3065 assert_eq!(k, 0);
3066
3067 // case 3: y = 0.5 (at lower bound, no reduction)
3068 let y = FixedU64::saturating_from_rational(1, 2);
3069 let (reduced, k) = range_reduce_sqrt(y);
3070 assert_eq!(reduced, y);
3071 assert_eq!(k, 0);
3072
3073 // case 4: y = 0.75 (within range)
3074 let y = FixedU64::saturating_from_rational(3, 4);
3075 let (reduced, k) = range_reduce_sqrt(y);
3076 assert_eq!(reduced, y);
3077 assert_eq!(k, 0);
3078 }
3079
3080 #[test]
3081 fn range_reduce_sqrt_needs_reduction_above_range() {
3082 // Reduced value should be in range [0.5, 1.5]
3083 let half = FixedU64::saturating_from_rational(1, 2);
3084 let one_half = FixedU64::saturating_from_rational(3, 2);
3085
3086 // case 1: y = 4 (needs reduction)
3087 // sqrt(4) = 2, sqrt(2) = 1.414 (within range)
3088 let y: FixedU64 = 4.into();
3089 let (reduced, k) = range_reduce_sqrt(y);
3090 // 2 reductions needed
3091 assert_eq!(k, 2);
3092 assert!(reduced >= half && reduced <= one_half);
3093
3094 // case 2: y = 16 (needs multiple reductions)
3095 // sqrt(16) = 4, sqrt(4) = 2, sqrt(2) = 1.414
3096 let y: FixedU64 = 16.into();
3097 let (reduced, k) = range_reduce_sqrt(y);
3098 assert_eq!(k, 3); // 3 reductions needed
3099 assert!(reduced >= half && reduced <= one_half);
3100
3101 // case 3: y = 100 (large value)
3102 let y: FixedU64 = 100.into();
3103 let (reduced, k) = range_reduce_sqrt(y);
3104 assert!(k > 0);
3105 assert!(reduced >= half && reduced <= one_half);
3106 }
3107
3108 #[test]
3109 fn range_reduce_sqrt_needs_reduction_below_range() {
3110 let half = FixedU64::saturating_from_rational(1, 2);
3111 let one_half = FixedU64::saturating_from_rational(3, 2);
3112
3113 // case 1: y = 0.25 (below range, needs reduction)
3114 // sqrt(0.25) = 0.5 (now in range)
3115 let y = FixedU64::saturating_from_rational(1, 4);
3116 let (reduced, k) = range_reduce_sqrt(y);
3117 assert_eq!(k, 1);
3118 assert!(reduced >= half && reduced <= one_half);
3119
3120 // case 2: y = 0.01 (very small, needs multiple reductions)
3121 let y = FixedU64::saturating_from_rational(1, 100);
3122 let (reduced, k) = range_reduce_sqrt(y);
3123 assert!(k > 0);
3124 assert_eq!(k, 3);
3125 assert!(reduced >= half && reduced <= one_half);
3126
3127 // case 3: y = 0.0001 (very small, needs multiple reductions)
3128 let y = FixedU64::saturating_from_rational(1, 10000);
3129 let (reduced, k) = range_reduce_sqrt(y);
3130 assert!(k > 0);
3131 assert_eq!(k, 4);
3132 assert!(reduced >= half && reduced <= one_half);
3133 }
3134
3135 #[test]
3136 fn range_reduce_sqrt_edge_cases() {
3137 // case 1: y = 0 (zero input)
3138 let y: FixedU64 = 0.into();
3139 let (reduced, k) = range_reduce_sqrt(y);
3140
3141 // sqrt(0) = 0, which is outside [0.5, 1.5]
3142 // But the function should handle this gracefully
3143 assert_eq!(reduced, y);
3144 assert_eq!(k, 0);
3145
3146 // case 2: y very close to 1 (minimal difference)
3147 let y = FixedU64::from_inner(1000000001); // 1.000000001
3148 let (reduced, k) = range_reduce_sqrt(y);
3149
3150 // Should not need reduction (within tolerance)
3151 assert_eq!(k, 0);
3152 assert_eq!(reduced, y);
3153
3154 // case 3: y = u32::MAX (very large value)
3155 let y: FixedU64 = (u32::MAX as u64).into();
3156 let (reduced, k) = range_reduce_sqrt(y);
3157
3158 // Should reduce many times
3159 assert!(k > 0);
3160 let half = FixedU64::saturating_from_rational(1, 2);
3161 let one_half = FixedU64::saturating_from_rational(3, 2);
3162 assert!(reduced >= half && reduced <= one_half);
3163 }
3164
3165 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3166 // ````````````````````````````````` LN_NEAR_ONE `````````````````````````````````
3167 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3168
3169 #[test]
3170 fn ln_near_one_at_one() {
3171 // case 1: y = 1, ln(1) = 0
3172 let y: FixedU64 = 1.into();
3173 let result = ln_near_one(y);
3174 let expected: FixedU64 = 0.into();
3175 assert_eq!(result, expected);
3176 }
3177
3178 #[test]
3179 fn ln_near_one_slightly_above_one() {
3180 // case 1: y = 1.1, ln(1.1) ~= 0.095310176
3181 let y = FixedU64::saturating_from_rational(11, 10);
3182 let result = ln_near_one(y);
3183 let expected = FixedU64::from_inner(95310176);
3184 assert_eq!(result, expected);
3185
3186 // case 2: y = 1.2, ln(1.2) ~= 0.182321552
3187 let y = FixedU64::saturating_from_rational(6, 5);
3188 let result = ln_near_one(y);
3189 let expected = FixedU64::from_inner(182321552);
3190 assert_eq!(result, expected);
3191
3192 // case 3: y = 1.5, ln(1.5) ~= 0.405465100
3193 let y = FixedU64::saturating_from_rational(3, 2);
3194 let result = ln_near_one(y);
3195 let expected = FixedU64::from_inner(405465100);
3196 assert_eq!(result, expected);
3197 }
3198
3199 #[test]
3200 fn ln_near_one_slightly_below_one() {
3201 // case 1: y = 0.9, ln(0.9) ~= -0.105360510
3202 let y = FixedU64::saturating_from_rational(9, 10);
3203 let _result = ln_near_one(y);
3204 // Result will be negative (but FixedU64 is unsigned, so this might underflow)
3205 // For signed types:
3206 let y_signed = FixedI64::saturating_from_rational(9, 10);
3207 let result_signed = ln_near_one(y_signed);
3208 let expected = FixedI64::from_inner(-105360510);
3209 assert_eq!(result_signed, expected);
3210
3211 // case 2: y = 0.8, ln(0.8) ~= -0.223143548
3212 let y = FixedI64::saturating_from_rational(4, 5);
3213 let result = ln_near_one(y);
3214 let expected = FixedI64::from_inner(-223143548);
3215 assert_eq!(result, expected);
3216
3217 // case 3: y = 0.5, ln(0.5) ~= -0.693147174
3218 let y = FixedI64::saturating_from_rational(1, 2);
3219 let result = ln_near_one(y);
3220 let expected = FixedI64::from_inner(-693147174);
3221 assert_eq!(result, expected);
3222 }
3223
3224 #[test]
3225 fn ln_near_one_very_close_to_one() {
3226 // case 1: y = 1.01, ln(1.01) ~= 0.009950330
3227 let y = FixedU64::saturating_from_rational(101, 100);
3228 let result = ln_near_one(y);
3229 let expected = FixedU64::from_inner(9950330);
3230 assert_eq!(result, expected);
3231
3232 // case 2: y = 1.001, ln(1.001) ~= 0.000999500
3233 let y = FixedU64::saturating_from_rational(1001, 1000);
3234 let result = ln_near_one(y);
3235 let expected = FixedU64::from_inner(999500);
3236 assert_eq!(result, expected);
3237
3238 // case 3: y = 0.99, ln(0.99) ~= -0.010050334
3239 let y = FixedI64::saturating_from_rational(99, 100);
3240 let result = ln_near_one(y);
3241 let expected = FixedI64::from_inner(-10050334);
3242 assert_eq!(result, expected);
3243
3244 // case 4: y = 0.999, ln(0.999) ~= -0.001000500
3245 let y = FixedI64::saturating_from_rational(999, 1000);
3246 let result = ln_near_one(y);
3247 let expected = FixedI64::from_inner(-1000500);
3248 assert_eq!(result, expected);
3249 }
3250
3251 #[test]
3252 fn ln_near_one_edge_cases() {
3253 // case 1: Very small positive deviation
3254 // y = 1.0001, ln(1.0001) ~= 0.000099994
3255 let y = FixedU64::saturating_from_rational(10001, 10000);
3256 let result = ln_near_one(y);
3257 let expected = FixedU64::from_inner(99994);
3258 assert_eq!(result, expected);
3259
3260 // case 2: Very small negative deviation
3261 // y = 0.9999, ln(0.9999) ~= -0.000100004
3262 let y = FixedI64::saturating_from_rational(9999, 10000);
3263 let result = ln_near_one(y);
3264 let expected = FixedI64::from_inner(-100004);
3265 assert_eq!(result, expected);
3266 }
3267
3268 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3269 // ```````````````````````````` DYNAMIC_MAX_ITERATIONS ```````````````````````````
3270 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3271
3272 #[test]
3273 fn dynamic_max_iterations_zero_input() {
3274 // case 1: x = 0, should return minimum of 1 iteration
3275 let x: FixedU64 = 0.into();
3276 let result = dynamic_max_iterations(&x);
3277 assert_eq!(result, 1);
3278 }
3279
3280 #[test]
3281 fn dynamic_max_iterations_one_input() {
3282 // case 1: x = 1, int_part = 1
3283 // iterations = 1 * DECIMAL_PLACES + 1 = 1 * 9 + 1 = 10
3284 let x: FixedU64 = 1.into();
3285 let result = dynamic_max_iterations(&x);
3286 assert_eq!(result, 10); // 1 * 9 + 1
3287 }
3288
3289 #[test]
3290 fn dynamic_max_iterations_small_integers() {
3291 // case 1: x = 2, int_part = 2
3292 // iterations = 2 * 9 + 1 = 19
3293 let x: FixedU64 = 2.into();
3294 let result = dynamic_max_iterations(&x);
3295 assert_eq!(result, 19);
3296
3297 // case 2: x = 5, int_part = 5
3298 // iterations = 5 * 9 + 1 = 46
3299 let x: FixedU64 = 5.into();
3300 let result = dynamic_max_iterations(&x);
3301 assert_eq!(result, 46);
3302
3303 // case 3: x = 10, int_part = 10
3304 // iterations = 10 * 9 + 1 = 91
3305 let x: FixedU64 = 10.into();
3306 let result = dynamic_max_iterations(&x);
3307 assert_eq!(result, 91);
3308 }
3309
3310 #[test]
3311 fn dynamic_max_iterations_fractional_values() {
3312 // case 1: x = 0.5, int_part = 0
3313 // iterations = 0 * 9 + 1 = 1
3314 let x = FixedU64::saturating_from_rational(1, 2);
3315 let result = dynamic_max_iterations(&x);
3316 assert_eq!(result, 1);
3317
3318 // case 2: x = 0.9, int_part = 0
3319 // iterations = 0 * 99 + 1 = 1
3320 let x = FixedU64::saturating_from_rational(9, 10);
3321 let result = dynamic_max_iterations(&x);
3322 assert_eq!(result, 1);
3323
3324 // case 3: x = 1.5, int_part = 1
3325 // iterations = 1 * 9 + 1 = 10
3326 let x = FixedU64::saturating_from_rational(3, 2);
3327 let result = dynamic_max_iterations(&x);
3328 assert_eq!(result, 10);
3329
3330 // case 4: x = 2.7, int_part = 2
3331 // iterations = 2 * 9 + 1 = 19
3332 let x = FixedU64::saturating_from_rational(27, 10);
3333 let result = dynamic_max_iterations(&x);
3334 assert_eq!(result, 19);
3335 }
3336
3337 #[test]
3338 fn dynamic_max_iterations_negative_values() {
3339 // case 1: x = -1, abs = 1, int_part = 1
3340 // iterations = 1 * 9 + 1 = 10
3341 let x: FixedI64 = (-1).into();
3342 let result = dynamic_max_iterations(&x);
3343 assert_eq!(result, 10);
3344
3345 // case 2: x = -5, abs = 5, int_part = 5
3346 // iterations = 5 * 9 + 1 = 46
3347 let x: FixedI64 = (-5).into();
3348 let result = dynamic_max_iterations(&x);
3349 assert_eq!(result, 46);
3350
3351 // case 3: x = -10, abs = 10, int_part = 10
3352 // iterations = 10 * 9 + 1 = 91
3353 let x: FixedI64 = (-10).into();
3354 let result = dynamic_max_iterations(&x);
3355 assert_eq!(result, 91);
3356
3357 // case 4: x = -0.5, abs = 0.5, int_part = 0
3358 // iterations = 0 * 9 + 1 = 1
3359 let x = FixedI64::saturating_from_rational(-1, 2);
3360 let result = dynamic_max_iterations(&x);
3361 assert_eq!(result, 1);
3362 }
3363
3364 #[test]
3365 fn dynamic_max_iterations_large_values() {
3366 // case 1: x = 100, int_part = 100
3367 // iterations = 100 * 9 + 1 = 901
3368 let x: FixedU64 = 100.into();
3369 let result = dynamic_max_iterations(&x);
3370 assert_eq!(result, 901);
3371
3372 // case 2: x = 1000, int_part = 1000
3373 // iterations = 1000 * 9 + 1 = 9001
3374 let x: FixedU64 = 1000.into();
3375 let result = dynamic_max_iterations(&x);
3376 assert_eq!(result, 9001);
3377
3378 // case 3: x = 10000, int_part = 10000
3379 // iterations = 10000 * 9 + 1 = 90001
3380 let x: FixedU64 = 10000.into();
3381 let result = dynamic_max_iterations(&x);
3382 assert_eq!(result, 90001);
3383 }
3384
3385 #[test]
3386 fn dynamic_max_iterations_saturation_check() {
3387 // case 1: Overflow values
3388 // This depends on the max value of the fixed-point type
3389 let x: FixedU64 = (u32::MAX as u64).into();
3390 let result = dynamic_max_iterations(&x);
3391
3392 // Should return a valid value without panicking
3393 assert!(result >= 1);
3394
3395 // Result should be reasonable (not u32::MAX due to saturation)
3396 // int_part = u32::MAX, iterations = u32::MAX * 5 + 1
3397 // This will saturate to u32::MAX
3398 assert_eq!(result, u32::MAX);
3399 }
3400
3401 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3402 // ````````````````````````````````` TO_U32_FLOOR ````````````````````````````````
3403 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3404
3405 #[test]
3406 fn to_u32_floor_zero_input() {
3407 // case 1: x = 0 -> 0
3408 let x: FixedU64 = 0.into();
3409 let result = to_u32_floor(&x);
3410 assert_eq!(result, 0);
3411
3412 // case 2: x = 0 (signed)
3413 let x: FixedI64 = 0.into();
3414 let result = to_u32_floor(&x);
3415 assert_eq!(result, 0);
3416 }
3417
3418 #[test]
3419 fn to_u32_floor_positive_integers() {
3420 // case 1: x = 1 -> 1
3421 let x: FixedU64 = 1.into();
3422 let result = to_u32_floor(&x);
3423 assert_eq!(result, 1);
3424
3425 // case 2: x = 10 -> 10
3426 let x: FixedU64 = 10.into();
3427 let result = to_u32_floor(&x);
3428 assert_eq!(result, 10);
3429
3430 // case 3: x = 100 -> 100
3431 let x: FixedU64 = 100.into();
3432 let result = to_u32_floor(&x);
3433 assert_eq!(result, 100);
3434
3435 // case 4: x = 1000 -> 1000
3436 let x: FixedU64 = 1000.into();
3437 let result = to_u32_floor(&x);
3438 assert_eq!(result, 1000);
3439
3440 // case 5: x = 42 -> 42
3441 let x: FixedU64 = 42.into();
3442 let result = to_u32_floor(&x);
3443 assert_eq!(result, 42);
3444 }
3445
3446 #[test]
3447 fn to_u32_floor_fractional_values() {
3448 // case 1: x = 1.5 -> 1 (floor)
3449 let x = FixedU64::saturating_from_rational(3, 2);
3450 let result = to_u32_floor(&x);
3451 assert_eq!(result, 1);
3452
3453 // case 2: x = 2.7 -> 2 (floor)
3454 let x = FixedU64::saturating_from_rational(27, 10);
3455 let result = to_u32_floor(&x);
3456 assert_eq!(result, 2);
3457
3458 // case 3: x = 9.999 -> 9 (floor)
3459 let x = FixedU64::saturating_from_rational(9999, 1000);
3460 let result = to_u32_floor(&x);
3461 assert_eq!(result, 9);
3462
3463 // case 4: x = 100.1 -> 100 (floor)
3464 let x = FixedU64::saturating_from_rational(1001, 10);
3465 let result = to_u32_floor(&x);
3466 assert_eq!(result, 100);
3467
3468 // case 5: x = 0.5 -> 0 (floor)
3469 let x = FixedU64::saturating_from_rational(1, 2);
3470 let result = to_u32_floor(&x);
3471 assert_eq!(result, 0);
3472
3473 // case 6: x = 0.9 -> 0 (floor)
3474 let x = FixedU64::saturating_from_rational(9, 10);
3475 let result = to_u32_floor(&x);
3476 assert_eq!(result, 0);
3477 }
3478
3479 #[test]
3480 fn to_u32_floor_negative_values() {
3481 // case 1: x = -1 -> 0 (clamped)
3482 let x: FixedI64 = (-1).into();
3483 let result = to_u32_floor(&x);
3484 assert_eq!(result, 0);
3485
3486 // case 2: x = -10 -> 0 (clamped)
3487 let x: FixedI64 = (-10).into();
3488 let result = to_u32_floor(&x);
3489 assert_eq!(result, 0);
3490
3491 // case 3: x = -0.5 -> 0 (clamped)
3492 let x = FixedI64::saturating_from_rational(-1, 2);
3493 let result = to_u32_floor(&x);
3494 assert_eq!(result, 0);
3495
3496 // case 4: x = -100.5 -> 0 (clamped)
3497 let x = FixedI64::saturating_from_rational(-201, 2);
3498 let result = to_u32_floor(&x);
3499 assert_eq!(result, 0);
3500
3501 // case 5: x = i64::MIN -> 0 (clamped)
3502 let x: FixedI64 = i64::MIN.into();
3503 let result = to_u32_floor(&x);
3504 assert_eq!(result, 0);
3505 }
3506
3507 #[test]
3508 fn to_u32_floor_boundary_values() {
3509 // case 1: x = u32::MAX (exactly at boundary)
3510 let x: FixedU64 = (u32::MAX as u64).into();
3511 let result = to_u32_floor(&x);
3512 assert_eq!(result, u32::MAX);
3513
3514 // case 2: x = u32::MAX + 1 (overflow, should saturate)
3515 let x: FixedU64 = ((u32::MAX as u64) + 1).into();
3516 let result = to_u32_floor(&x);
3517 assert_eq!(result, u32::MAX);
3518
3519 // case 3: x = u32::MAX + 100 (overflow, should saturate)
3520 let x: FixedU64 = ((u32::MAX as u64) + 100).into();
3521 let result = to_u32_floor(&x);
3522 assert_eq!(result, u32::MAX);
3523 }
3524
3525 #[test]
3526 fn to_u32_floor_large_values() {
3527 // case 1: x = 1000000 -> 1000000
3528 let x: FixedU64 = 1000000.into();
3529 let result = to_u32_floor(&x);
3530 assert_eq!(result, 1000000);
3531
3532 // case 2: x = 10000000 -> 10000000
3533 let x: FixedU64 = 10000000.into();
3534 let result = to_u32_floor(&x);
3535 assert_eq!(result, 10000000);
3536
3537 // case 3: x = u64::MAX (overflow, should saturate)
3538 let x: FixedU64 = u64::MAX.into();
3539 let result = to_u32_floor(&x);
3540 assert_eq!(result, u32::MAX);
3541 }
3542
3543 #[test]
3544 fn to_u32_floor_from_inner_representation() {
3545 // case 1: FixedU64 with DIV = 1_000_000_000
3546 // inner = 5_000_000_000 represents 5.0
3547 let x = FixedU64::from_inner(5_000_000_000);
3548 let result = to_u32_floor(&x);
3549 assert_eq!(result, 5);
3550
3551 // case 2: inner = 5_500_000_000 represents 5.5
3552 let x = FixedU64::from_inner(5_500_000_000);
3553 let result = to_u32_floor(&x);
3554 assert_eq!(result, 5);
3555
3556 // case 3: inner = 999_999_999 represents 0.999999999
3557 let x = FixedU64::from_inner(999_999_999);
3558 let result = to_u32_floor(&x);
3559 assert_eq!(result, 0);
3560
3561 // case 4: inner = 1_000_000_000 represents 1.0
3562 let x = FixedU64::from_inner(1_000_000_000);
3563 let result = to_u32_floor(&x);
3564 assert_eq!(result, 1);
3565 }
3566
3567 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3568 // ````````````````````````` FIXED_SIGNED_CAST - FixedI64 ````````````````````````
3569 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3570 //
3571 // FixedI64 is an identity implementation: Signed = Self.
3572 // Every conversion is a no-op - the value passes through unchanged.
3573
3574 #[test]
3575 fn fixed_signed_cast_i64_checked_into() {
3576 // Any FixedI64 value should always yield Some(itself).
3577 let x = FixedI64::saturating_from_integer(5);
3578 assert_eq!(FixedI64::checked_into(x), Some(x));
3579
3580 let x = FixedI64::saturating_from_integer(-7);
3581 assert_eq!(FixedI64::checked_into(x), Some(x));
3582
3583 let x = FixedI64::zero();
3584 assert_eq!(FixedI64::checked_into(x), Some(x));
3585
3586 let x = FixedI64::max_value();
3587 assert_eq!(FixedI64::checked_into(x), Some(x));
3588
3589 let x = FixedI64::min_value();
3590 assert_eq!(FixedI64::checked_into(x), Some(x));
3591 }
3592
3593 #[test]
3594 fn fixed_signed_cast_i64_saturated_into() {
3595 // Identity: saturated_into on a signed type is always a no-op.
3596 let x = FixedI64::saturating_from_integer(42);
3597 assert_eq!(FixedI64::saturated_into(x), x);
3598
3599 let x = FixedI64::saturating_from_integer(-42);
3600 assert_eq!(FixedI64::saturated_into(x), x);
3601
3602 let x = FixedI64::max_value();
3603 assert_eq!(FixedI64::saturated_into(x), x);
3604 }
3605
3606 #[test]
3607 fn fixed_signed_cast_i64_checked_from() {
3608 // Identity: checked_from on a signed type always yields Some(itself).
3609 let x = FixedI64::saturating_from_integer(3);
3610 assert_eq!(FixedI64::checked_from(x), Some(x));
3611
3612 let x = FixedI64::saturating_from_integer(-3);
3613 assert_eq!(FixedI64::checked_from(x), Some(x));
3614
3615 let x = FixedI64::min_value();
3616 assert_eq!(FixedI64::checked_from(x), Some(x));
3617 }
3618
3619 #[test]
3620 fn fixed_signed_cast_i64_saturated_from() {
3621 // Identity: saturated_from on a signed type is always a no-op.
3622 let x = FixedI64::saturating_from_integer(10);
3623 assert_eq!(FixedI64::saturated_from(x), x);
3624
3625 let x = FixedI64::saturating_from_integer(-10);
3626 assert_eq!(FixedI64::saturated_from(x), x);
3627 }
3628
3629 #[test]
3630 fn fixed_signed_cast_i64_saturating_closure() {
3631 // saturating wraps a signed closure - on FixedI64 it is a pure pass-through.
3632 let x = FixedI64::saturating_from_integer(4);
3633 let result = FixedI64::saturating(x, |v| v.saturating_add(FixedI64::saturating_from_integer(1)));
3634 assert_eq!(result, FixedI64::saturating_from_integer(5));
3635
3636 // Closure that negates: result is negative but still representable.
3637 let x = FixedI64::saturating_from_integer(3);
3638 let result = FixedI64::saturating(x, |v| v.saturating_mul(FixedI64::saturating_from_integer(-1)));
3639 assert_eq!(result, FixedI64::saturating_from_integer(-3));
3640 }
3641
3642 #[test]
3643 fn fixed_signed_cast_i64_checked_closure() {
3644 // checked wraps a signed closure - returns Some for in-range results,
3645 // None when the result saturates to min_value() or max_value().
3646 let x = FixedI64::saturating_from_integer(7);
3647 let result = FixedI64::checked(x, |v| {
3648 v.unwrap_or(FixedI64::zero()).saturating_add(FixedI64::saturating_from_integer(1))
3649 });
3650 assert_eq!(result, Some(FixedI64::saturating_from_integer(8)));
3651 }
3652
3653 #[test]
3654 fn fixed_signed_cast_i64_checked_closure_overflow_is_none() {
3655 // Closure overflows to max_value via saturating arithmetic - checked returns None.
3656 let x = FixedI64::max_value();
3657 let result = FixedI64::checked(x, |v| {
3658 v.unwrap_or(FixedI64::zero()).saturating_add(FixedI64::one())
3659 });
3660 assert_eq!(result, None);
3661 }
3662
3663 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3664 // ````````````````````````` FIXED_SIGNED_CAST - FixedI128 ```````````````````````
3665 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3666 //
3667 // FixedI128 is also an identity implementation: Signed = Self.
3668
3669 #[test]
3670 fn fixed_signed_cast_i128_checked_into() {
3671 let x = FixedI128::saturating_from_integer(100);
3672 assert_eq!(FixedI128::checked_into(x), Some(x));
3673
3674 let x = FixedI128::saturating_from_integer(-100);
3675 assert_eq!(FixedI128::checked_into(x), Some(x));
3676
3677 let x = FixedI128::zero();
3678 assert_eq!(FixedI128::checked_into(x), Some(x));
3679
3680 let x = FixedI128::max_value();
3681 assert_eq!(FixedI128::checked_into(x), Some(x));
3682
3683 let x = FixedI128::min_value();
3684 assert_eq!(FixedI128::checked_into(x), Some(x));
3685 }
3686
3687 #[test]
3688 fn fixed_signed_cast_i128_saturated_into() {
3689 let x = FixedI128::saturating_from_integer(999);
3690 assert_eq!(FixedI128::saturated_into(x), x);
3691
3692 let x = FixedI128::saturating_from_integer(-999);
3693 assert_eq!(FixedI128::saturated_into(x), x);
3694 }
3695
3696 #[test]
3697 fn fixed_signed_cast_i128_checked_from() {
3698 let x = FixedI128::saturating_from_integer(50);
3699 assert_eq!(FixedI128::checked_from(x), Some(x));
3700
3701 let x = FixedI128::saturating_from_integer(-50);
3702 assert_eq!(FixedI128::checked_from(x), Some(x));
3703 }
3704
3705 #[test]
3706 fn fixed_signed_cast_i128_saturated_from() {
3707 let x = FixedI128::saturating_from_integer(77);
3708 assert_eq!(FixedI128::saturated_from(x), x);
3709
3710 let x = FixedI128::saturating_from_integer(-77);
3711 assert_eq!(FixedI128::saturated_from(x), x);
3712 }
3713
3714 #[test]
3715 fn fixed_signed_cast_i128_saturating_closure() {
3716 let x = FixedI128::saturating_from_integer(10);
3717 let result = FixedI128::saturating(x, |v| v.saturating_add(FixedI128::saturating_from_integer(5)));
3718 assert_eq!(result, FixedI128::saturating_from_integer(15));
3719
3720 let x = FixedI128::saturating_from_integer(-10);
3721 let result = FixedI128::saturating(x, |v| v.saturating_mul(FixedI128::saturating_from_integer(2)));
3722 assert_eq!(result, FixedI128::saturating_from_integer(-20));
3723 }
3724
3725 #[test]
3726 fn fixed_signed_cast_i128_checked_closure() {
3727 let x = FixedI128::saturating_from_integer(3);
3728 let result = FixedI128::checked(x, |v| {
3729 v.unwrap_or(FixedI128::zero()).saturating_add(FixedI128::saturating_from_integer(2))
3730 });
3731 assert_eq!(result, Some(FixedI128::saturating_from_integer(5)));
3732 }
3733
3734 #[test]
3735 fn fixed_signed_cast_i128_checked_closure_overflow_is_none() {
3736 // Closure overflows to max_value via saturating arithmetic - checked returns None.
3737 let x = FixedI128::max_value();
3738 let result = FixedI128::checked(x, |v| {
3739 v.unwrap_or(FixedI128::zero()).saturating_add(FixedI128::one())
3740 });
3741 assert_eq!(result, None);
3742 }
3743
3744 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3745 // ````````````````````````` FIXED_SIGNED_CAST - FixedU64 ````````````````````````
3746 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3747 //
3748 // FixedU64 bridges to FixedI128. u64 inner always fits in i128,
3749 // so checked_into / saturated_into are always infallible.
3750 // The reverse direction can fail when the signed result is negative
3751 // or exceeds u64::MAX.
3752
3753 #[test]
3754 fn fixed_signed_cast_u64_checked_into_always_some() {
3755 // u64 inner always representable in i128.
3756 let x = FixedU64::saturating_from_integer(0);
3757 assert!(FixedU64::checked_into(x).is_some());
3758
3759 let x = FixedU64::saturating_from_integer(1);
3760 let signed = FixedU64::checked_into(x).unwrap();
3761 // The signed inner should equal the unsigned inner cast to i128.
3762 assert_eq!(signed.into_inner(), x.into_inner() as i128);
3763
3764 let x = FixedU64::max_value();
3765 assert!(FixedU64::checked_into(x).is_some());
3766 }
3767
3768 #[test]
3769 fn fixed_signed_cast_u64_saturated_into_round_trip() {
3770 // saturated_into then checked_from should recover the original value
3771 // for any representable FixedU64.
3772 let values = [
3773 FixedU64::saturating_from_integer(0),
3774 FixedU64::saturating_from_integer(1),
3775 FixedU64::saturating_from_integer(1000),
3776 FixedU64::saturating_from_rational(3, 2), // 1.5
3777 FixedU64::max_value(),
3778 ];
3779 for x in values {
3780 let signed = FixedU64::saturated_into(x);
3781 let back = FixedU64::checked_from(signed).unwrap();
3782 assert_eq!(back, x, "round-trip failed for {x:?}");
3783 }
3784 }
3785
3786 #[test]
3787 fn fixed_signed_cast_u64_checked_from_negative_is_none() {
3788 // Negative FixedI128 values cannot be represented as FixedU64.
3789 let neg = FixedI128::saturating_from_integer(-1);
3790 assert_eq!(FixedU64::checked_from(neg), None);
3791
3792 let neg = FixedI128::saturating_from_integer(-100);
3793 assert_eq!(FixedU64::checked_from(neg), None);
3794
3795 // Minimum i128 value - strongly negative.
3796 let neg = FixedI128::min_value();
3797 assert_eq!(FixedU64::checked_from(neg), None);
3798 }
3799
3800 #[test]
3801 fn fixed_signed_cast_u64_checked_from_above_u64_max_is_none() {
3802 // FixedI128 inner > u64::MAX cannot fit in FixedU64.
3803 // Construct an i128 inner that represents a value just above u64::MAX.
3804 // FixedU64::DIV = 10^9; FixedI128::DIV = 10^18.
3805 // We need inner_i128 > u64::MAX (as a raw inner value of FixedI128).
3806 // u64::MAX as i128 = 18_446_744_073_709_551_615.
3807 let too_large = FixedI128::from_inner(u64::MAX as i128 + 1);
3808 assert_eq!(FixedU64::checked_from(too_large), None);
3809 }
3810
3811 #[test]
3812 fn fixed_signed_cast_u64_checked_from_zero_is_some() {
3813 let zero = FixedI128::zero();
3814 assert_eq!(FixedU64::checked_from(zero), Some(FixedU64::zero()));
3815 }
3816
3817 #[test]
3818 fn fixed_signed_cast_u64_saturated_from_negative_clamps_to_zero() {
3819 // Negative values saturate to zero (the unsigned floor).
3820 let neg = FixedI128::saturating_from_integer(-5);
3821 assert_eq!(FixedU64::saturated_from(neg), FixedU64::zero());
3822
3823 let neg = FixedI128::min_value();
3824 assert_eq!(FixedU64::saturated_from(neg), FixedU64::zero());
3825 }
3826
3827 #[test]
3828 fn fixed_signed_cast_u64_saturated_from_above_max_clamps_to_max() {
3829 // Values above u64::MAX saturate to u64::MAX.
3830 let too_large = FixedI128::from_inner(u64::MAX as i128 + 1);
3831 assert_eq!(FixedU64::saturated_from(too_large), FixedU64::from_inner(u64::MAX));
3832 }
3833
3834 #[test]
3835 fn fixed_signed_cast_u64_saturating_closure_with_positive_result() {
3836 // Closure doubles the value - result stays within FixedU64 range.
3837 let x = FixedU64::saturating_from_integer(3);
3838 let result = FixedU64::saturating(x, |v| v.saturating_add(v));
3839 assert_eq!(result, FixedU64::saturating_from_integer(6));
3840 }
3841
3842 #[test]
3843 fn fixed_signed_cast_u64_saturating_closure_negative_result_clamps() {
3844 // Closure produces a negative signed result - saturated_from clamps to 0.
3845 let x = FixedU64::saturating_from_integer(2);
3846 let result = FixedU64::saturating(x, |v| v.saturating_mul(FixedI128::saturating_from_integer(-1)));
3847 assert_eq!(result, FixedU64::zero());
3848 }
3849
3850 #[test]
3851 fn fixed_signed_cast_u64_checked_closure_negative_result_is_none() {
3852 // Closure produces a negative signed result - checked_from returns None.
3853 let x = FixedU64::saturating_from_integer(5);
3854 let result = FixedU64::checked(x, |v| {
3855 // saturating_mul by the fixed-point -1.0 correctly negates because
3856 // fixed-point multiplication divides by DIV, cancelling the scale difference.
3857 // (Unlike saturating_add, which operates on raw inner values and would be wrong
3858 // with a FixedI128 integer constant - see the checked_closure_valid test.)
3859 v.unwrap().saturating_mul(FixedI128::saturating_from_integer(-1))
3860 });
3861 assert_eq!(result, None);
3862 }
3863
3864 #[test]
3865 fn fixed_signed_cast_u64_checked_closure_valid_result_is_some() {
3866 // Closure adds one unit in FixedU64 scale.
3867 // The bridge casts raw inner bits into FixedI128, so arithmetic inside
3868 // the closure must use FixedU64::DIV (10^9) as "one unit", not
3869 // FixedI128::saturating_from_integer(1) which uses FixedI128::DIV (10^18).
3870 let x = FixedU64::saturating_from_integer(10);
3871 let one_unit = FixedI128::from_inner(FixedU64::DIV as i128); // = 1 in FixedU64 scale
3872 let result = FixedU64::checked(x, |v| {
3873 v.unwrap().saturating_add(one_unit)
3874 });
3875 assert_eq!(result, Some(FixedU64::saturating_from_integer(11)));
3876 }
3877
3878 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3879 // ````````````````````````` FIXED_SIGNED_CAST - FixedU128 ```````````````````````
3880 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3881 //
3882 // FixedU128 bridges to FixedI128. Unlike FixedU64, the inner u128 value
3883 // can exceed i128::MAX, making checked_into fallible for large values.
3884
3885 #[test]
3886 fn fixed_signed_cast_u128_checked_into_small_values_are_some() {
3887 let x = FixedU128::saturating_from_integer(0);
3888 assert!(FixedU128::checked_into(x).is_some());
3889
3890 let x = FixedU128::saturating_from_integer(1);
3891 let signed = FixedU128::checked_into(x).unwrap();
3892 assert_eq!(signed.into_inner(), x.into_inner() as i128);
3893
3894 let x = FixedU128::saturating_from_integer(1_000_000);
3895 assert!(FixedU128::checked_into(x).is_some());
3896 }
3897
3898 #[test]
3899 fn fixed_signed_cast_u128_checked_into_above_i128_max_is_none() {
3900 // Inner value just above i128::MAX is unrepresentable in FixedI128.
3901 let too_large = FixedU128::from_inner(i128::MAX as u128 + 1);
3902 assert_eq!(FixedU128::checked_into(too_large), None);
3903
3904 // Maximum FixedU128 value is definitely unrepresentable.
3905 assert_eq!(FixedU128::checked_into(FixedU128::max_value()), None);
3906 }
3907
3908 #[test]
3909 fn fixed_signed_cast_u128_saturated_into_large_clamps_to_i128_max() {
3910 // Values above i128::MAX saturate to i128::MAX in the signed workspace.
3911 let too_large = FixedU128::from_inner(i128::MAX as u128 + 1);
3912 let signed = FixedU128::saturated_into(too_large);
3913 assert_eq!(signed, FixedI128::from_inner(i128::MAX));
3914
3915 let signed_max = FixedU128::saturated_into(FixedU128::max_value());
3916 assert_eq!(signed_max, FixedI128::from_inner(i128::MAX));
3917 }
3918
3919 #[test]
3920 fn fixed_signed_cast_u128_saturated_into_small_values_exact() {
3921 let x = FixedU128::saturating_from_integer(42);
3922 let signed = FixedU128::saturated_into(x);
3923 assert_eq!(signed.into_inner(), x.into_inner() as i128);
3924 }
3925
3926 #[test]
3927 fn fixed_signed_cast_u128_checked_from_negative_is_none() {
3928 let neg = FixedI128::saturating_from_integer(-1);
3929 assert_eq!(FixedU128::checked_from(neg), None);
3930
3931 let neg = FixedI128::min_value();
3932 assert_eq!(FixedU128::checked_from(neg), None);
3933 }
3934
3935 #[test]
3936 fn fixed_signed_cast_u128_checked_from_non_negative_is_some() {
3937 let zero = FixedI128::zero();
3938 assert_eq!(FixedU128::checked_from(zero), Some(FixedU128::zero()));
3939
3940 let pos = FixedI128::saturating_from_integer(100);
3941 let result = FixedU128::checked_from(pos).unwrap();
3942 assert_eq!(result.into_inner(), pos.into_inner() as u128);
3943 }
3944
3945 #[test]
3946 fn fixed_signed_cast_u128_saturated_from_negative_clamps_to_zero() {
3947 let neg = FixedI128::saturating_from_integer(-99);
3948 assert_eq!(FixedU128::saturated_from(neg), FixedU128::zero());
3949
3950 let neg = FixedI128::min_value();
3951 assert_eq!(FixedU128::saturated_from(neg), FixedU128::zero());
3952 }
3953
3954 #[test]
3955 fn fixed_signed_cast_u128_saturated_from_non_negative_exact() {
3956 let pos = FixedI128::saturating_from_integer(500);
3957 let result = FixedU128::saturated_from(pos);
3958 assert_eq!(result.into_inner(), pos.into_inner() as u128);
3959
3960 let zero = FixedI128::zero();
3961 assert_eq!(FixedU128::saturated_from(zero), FixedU128::zero());
3962 }
3963
3964 #[test]
3965 fn fixed_signed_cast_u128_round_trip_for_representable_values() {
3966 let values = [
3967 FixedU128::saturating_from_integer(0),
3968 FixedU128::saturating_from_integer(1),
3969 FixedU128::saturating_from_integer(1_000_000),
3970 FixedU128::saturating_from_rational(7, 3),
3971 ];
3972 for x in values {
3973 let signed = FixedU128::saturated_into(x);
3974 let back = FixedU128::checked_from(signed).unwrap();
3975 assert_eq!(back, x, "round-trip failed for {x:?}");
3976 }
3977 }
3978
3979 #[test]
3980 fn fixed_signed_cast_u128_saturating_closure_valid() {
3981 let x = FixedU128::saturating_from_integer(4);
3982 let result = FixedU128::saturating(x, |v| v.saturating_add(FixedI128::saturating_from_integer(6)));
3983 assert_eq!(result, FixedU128::saturating_from_integer(10));
3984 }
3985
3986 #[test]
3987 fn fixed_signed_cast_u128_saturating_closure_negative_clamps_to_zero() {
3988 let x = FixedU128::saturating_from_integer(3);
3989 let result = FixedU128::saturating(x, |v| v.saturating_mul(FixedI128::saturating_from_integer(-2)));
3990 assert_eq!(result, FixedU128::zero());
3991 }
3992
3993 #[test]
3994 fn fixed_signed_cast_u128_checked_closure_negative_is_none() {
3995 let x = FixedU128::saturating_from_integer(5);
3996 let result = FixedU128::checked(x, |v| {
3997 v.unwrap_or(FixedI128::zero())
3998 .saturating_mul(FixedI128::saturating_from_integer(-1))
3999 });
4000 assert_eq!(result, None);
4001 }
4002
4003 #[test]
4004 fn fixed_signed_cast_u128_checked_closure_valid_is_some() {
4005 let x = FixedU128::saturating_from_integer(20);
4006 let result = FixedU128::checked(x, |v| {
4007 v.unwrap_or(FixedI128::zero())
4008 .saturating_add(FixedI128::saturating_from_integer(5))
4009 });
4010 assert_eq!(result, Some(FixedU128::saturating_from_integer(25)));
4011 }
4012
4013 #[test]
4014 fn fixed_signed_cast_u128_checked_into_none_propagated_through_closure() {
4015 // When checked_into returns None, the closure receives None.
4016 // The closure must handle this case - here it falls back to zero.
4017 let too_large = FixedU128::from_inner(i128::MAX as u128 + 1);
4018 let result = FixedU128::checked(too_large, |v| {
4019 v.unwrap_or(FixedI128::zero()) // None -> zero -> not representable? No: zero is fine.
4020 });
4021 // zero is representable as FixedU128, so we get Some(0).
4022 assert_eq!(result, Some(FixedU128::zero()));
4023 }
4024
4025}