1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
45//! Helper functions for common mathematical operations.
67use core::f32;
89/// Get closest power of two greater than the given number.
10pub fn closest_power_of_two(mut num: u32) -> u32 {
11 num -= 1;
12 num |= num >> 1;
13 num |= num >> 2;
14 num |= num >> 4;
15 num |= num >> 8;
16 num |= num >> 16;
17 num += 1;
18 num
19}
2021/// Represents an integral power-of-two as an exponent.
22#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
23pub struct PowerOfTwo(u32);
2425impl PowerOfTwo {
26/// Returns the base-2 exponent as a numeric type
27pub fn exp<R>(self) -> R
28where
29R: From<u32>,
30 {
31 From::from(self.0)
32 }
3334/// Converts a number two the nearest [`PowerOfTwo`] less-than-or-equal to it.
35pub fn floor<F: Into<u32>>(f: F) -> PowerOfTwo {
36 PowerOfTwo(log_base_two(f.into()))
37 }
3839/// Converts a number two the nearest [`PowerOfTwo`] greater-than-or-equal to
40 /// it.
41pub fn ceiling<F: Into<u32>>(f: F) -> PowerOfTwo {
42 PowerOfTwo(log_base_two(closest_power_of_two(f.into())))
43 }
4445/// Creates a new [`PowerOfTwo`] representing the number zero.
46pub fn zero() -> PowerOfTwo {
47 PowerOfTwo(0)
48 }
4950/// Converts a [`PowerOfTwo`] to a number.
51pub fn as_num<F: From<u32>>(self) -> F {
52 (1 << self.0).into()
53 }
54}
5556/// Get log base 2 of a number.
57///
58/// Note: this is the floor of the result. Also, an input of 0 results in an
59/// output of 0.
60pub fn log_base_two(num: u32) -> u32 {
61if num == 0 {
620
63} else {
6431 - num.leading_zeros()
65 }
66}
6768/// Log base 2 of 64 bit unsigned integers.
69pub fn log_base_two_u64(num: u64) -> u32 {
70if num == 0 {
710
72} else {
7363 - num.leading_zeros()
74 }
75}
7677// f32 log10 function adapted from [micromath](https://github.com/NeoBirth/micromath)
78const EXPONENT_MASK: u32 = 0b01111111_10000000_00000000_00000000;
79const EXPONENT_BIAS: u32 = 127;
8081/// Return the absolute value of the floating point number.
82pub fn abs(n: f32) -> f32 {
83 f32::from_bits(n.to_bits() & 0x7FFF_FFFF)
84}
8586fn extract_exponent_bits(x: f32) -> u32 {
87 (x.to_bits() & EXPONENT_MASK).overflowing_shr(23).0
88}
8990fn extract_exponent_value(x: f32) -> i32 {
91 (extract_exponent_bits(x) as i32) - EXPONENT_BIAS as i32
92}
9394fn ln_1to2_series_approximation(x: f32) -> f32 {
95// Idea from https://stackoverflow.com/a/44232045/. Modified to not be
96 // restricted to int range and only values of x above 1.0 and got rid of
97 // most of the slow conversions, should work for all positive values of x.
9899 // x may essentially be 1.0 but, as clippy notes, these kinds of floating
100 // point comparisons can fail when the bit pattern is not the same.
101if abs(x - 1.0_f32) < f32::EPSILON {
102return 0.0_f32;
103 }
104let x_less_than_1: bool = x < 1.0;
105// Note: we could use the fast inverse approximation here found in
106 // super::inv::inv_approx, but the precision of such an approximation is
107 // assumed not good enough.
108let x_working: f32 = if x_less_than_1 { 1.0 / x } else { x };
109// According to the SO post:
110 // ln(x) = ln((2^n)*y)= ln(2^n) + ln(y) = ln(2) * n + ln(y)
111 // Get exponent value.
112let base2_exponent: u32 = extract_exponent_value(x_working) as u32;
113let divisor: f32 = f32::from_bits(x_working.to_bits() & EXPONENT_MASK);
114// Supposedly normalizing between 1.0 and 2.0.
115let x_working: f32 = x_working / divisor;
116// Approximate polynomial generated from maple in the post using Remez
117 // Algorithm: https://en.wikipedia.org/wiki/Remez_algorithm.
118let ln_1to2_polynomial: f32 = -1.741_793_9_f32
119+ (2.821_202_6_f32
120+ (-1.469_956_8_f32 + (0.447_179_55_f32 - 0.056_570_85_f32 * x_working) * x_working)
121 * x_working)
122 * x_working;
123// ln(2) * n + ln(y)
124let result: f32 = (base2_exponent as f32) * f32::consts::LN_2 + ln_1to2_polynomial;
125if x_less_than_1 {
126 -result
127 } else {
128 result
129 }
130}
131132/// Compute the base 10 logarithm of `f`.
133pub fn log10(x: f32) -> f32 {
134// Using change of base log10(x) = ln(x)/ln(10)
135let ln10_recip = f32::consts::LOG10_E;
136let fract_base_ln = ln10_recip;
137let value_ln = ln_1to2_series_approximation(x);
138 value_ln * fract_base_ln
139}