map_range/lib.rs
1#![no_std]
2
3//
4
5use core::ops::{Add, Div, Mul, Range, Sub};
6use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub};
7
8//
9
10/// Mapping a value from range `from`
11/// to another range `to`.
12///
13/// # Panics
14///
15/// Panics if `from.end == from.start`,
16/// if either range is empty with unsigned integers,
17/// if `from.start > self` with unsigned integers,
18/// or if any checked overflowing happens.
19///
20/// # Examples
21///
22/// ```
23/// # use map_range::MapRange;
24/// # use rand::random;
25/// // Mapping from trig to f32 texture range
26/// let initial_value = random::<f32>().sin();
27///
28/// let mapped = initial_value.map_range(-1.0..1.0, 0.0..1.0);
29/// let expected = initial_value / 2.0 + 0.5;
30///
31/// assert!((mapped - expected).abs() <= f32::EPSILON);
32/// ```
33///
34/// ```
35/// # use map_range::MapRange;
36/// // Mapping can happen outside of the ranges.
37/// // Might panic with unsigned integers if `self - from.start`
38/// // overflows or if either range is empty.
39/// let x = 10_i32;
40///
41/// let y = x.map_range(0..5, -5..0);
42/// let z = x.map_range(0..5, 0..-5);
43///
44/// assert_eq!(y, 5);
45/// assert_eq!(z, -10);
46/// ```
47///
48/// ```should_panic
49/// # use map_range::MapRange;
50/// // panics
51/// let _ = 10_u32.map_range(0..5, 5..2);
52/// ```
53///
54/// ```should_panic
55/// # use map_range::MapRange;
56/// // panics
57/// let _ = 10_u32.map_range(20..30, 20..40);
58/// ```
59///
60/// ```should_panic
61/// # use map_range::MapRange;
62/// // panics
63/// let _ = 200_u8.map_range(0..10, 0..20);
64/// ```
65pub trait MapRange: Sized {
66 #[must_use]
67 fn map_range(self, from: Range<Self>, to: Range<Self>) -> Self;
68}
69
70/// Mapping a value from range `from`
71/// to another range `to`.
72///
73/// This is a checked version of [`MapRange`]
74///
75/// # Examples
76///
77/// ```
78/// # use map_range::CheckedMapRange;
79/// let a = 10_u32.checked_map_range(0..5, 5..2);
80/// let b = 10_u32.checked_map_range(0..5, 2..5);
81///
82/// assert_eq!(a, None);
83/// assert_eq!(b, Some(8));
84/// ```
85pub trait CheckedMapRange: Sized {
86 #[must_use]
87 fn checked_map_range(self, from: Range<Self>, to: Range<Self>) -> Option<Self>;
88}
89
90//
91
92impl<T> MapRange for T
93where
94 T: Copy + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self> + Div<Output = Self>,
95{
96 fn map_range(self, from: Range<Self>, to: Range<Self>) -> Self {
97 // shamelessly stolen from my own code:
98 // https://github.com/Overpeek/overpeek-engine/blob/3df11072378ba870033a19cd09fb332bcc4c466d/src/engine/utility/extra.hpp
99 to.start + (self - from.start) * (to.end - to.start) / (from.end - from.start)
100 }
101}
102
103impl<T> CheckedMapRange for T
104where
105 T: CheckedAdd<Output = Self>
106 + CheckedSub<Output = Self>
107 + CheckedMul<Output = Self>
108 + CheckedDiv<Output = Self>,
109{
110 fn checked_map_range(self, from: Range<Self>, to: Range<Self>) -> Option<Self> {
111 to.start.checked_add(
112 &self
113 .checked_sub(&from.start)?
114 .checked_mul(&to.end.checked_sub(&to.start)?)?
115 .checked_div(&from.end.checked_sub(&from.start)?)?,
116 )
117 }
118}
119
120//
121
122#[cfg(test)]
123mod tests {
124 use crate::MapRange;
125
126 #[test]
127 fn test_f32_map() {
128 assert!((5.0_f32.map_range(0.0..10.0, 0.0..20.0) - 10.0).abs() <= f32::EPSILON);
129 assert!((5.0_f32.map_range(0.0..10.0, 10.0..0.0) - 5.0).abs() <= f32::EPSILON);
130 }
131
132 #[test]
133 fn test_i32_map() {
134 assert_eq!(5_i32.map_range(0..10, -10..10), 0);
135 }
136}