From f310d0e5003cde10959eba46dd969f37b8089382 Mon Sep 17 00:00:00 2001 From: ltdk Date: Tue, 1 Jun 2021 22:50:42 -0400 Subject: [PATCH 1/4] Add lerp method --- library/std/src/f32.rs | 28 ++++++++++++++++++++++++++++ library/std/src/f64.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/library/std/src/f32.rs b/library/std/src/f32.rs index c16d27fa1f58c..32a4d415362a6 100644 --- a/library/std/src/f32.rs +++ b/library/std/src/f32.rs @@ -876,4 +876,32 @@ impl f32 { pub fn atanh(self) -> f32 { 0.5 * ((2.0 * self) / (1.0 - self)).ln_1p() } + + /// Linear interpolation between `start` and `end`. + /// + /// This enables the calculation of a "smooth" transition between `start` and `end`, + /// where start is represented by `self == 0.0` and `end` is represented by `self == 1.0`. + /// + /// Values below 0.0 or above 1.0 are allowed, and in general this function closely + /// resembles the value of `start + self * (end - start)`, plus additional guarantees. + /// + /// Those guarantees are, assuming that all values are [`finite`]: + /// + /// * The value at 0.0 is always `start` and the value at 1.0 is always `end` (exactness) + /// * If `start == end`, the value at any point will always be `start == end` (consistency) + /// * The values will always move in the direction from `start` to `end` (monotonicity) + /// + /// [`finite`]: #method.is_finite + #[must_use = "method returns a new number and does not mutate the original value"] + #[unstable(feature = "float_interpolation", issue = "71015")] + pub fn lerp(self, start: f32, end: f32) -> f32 { + // consistent + if start == end { + start + + // exact/monotonic + } else { + self.mul_add(end, (-self).mul_add(start, start)) + } + } } diff --git a/library/std/src/f64.rs b/library/std/src/f64.rs index 4c95df5ffe04a..39c3e587e1f6f 100644 --- a/library/std/src/f64.rs +++ b/library/std/src/f64.rs @@ -879,6 +879,34 @@ impl f64 { 0.5 * ((2.0 * self) / (1.0 - self)).ln_1p() } + /// Linear interpolation between `start` and `end`. + /// + /// This enables the calculation of a "smooth" transition between `start` and `end`, + /// where start is represented by `self == 0.0` and `end` is represented by `self == 1.0`. + /// + /// Values below 0.0 or above 1.0 are allowed, and in general this function closely + /// resembles the value of `start + self * (end - start)`, plus additional guarantees. + /// + /// Those guarantees are, assuming that all values are [`finite`]: + /// + /// * The value at 0.0 is always `start` and the value at 1.0 is always `end` (exactness) + /// * If `start == end`, the value at any point will always be `start == end` (consistency) + /// * The values will always move in the direction from `start` to `end` (monotonicity) + /// + /// [`finite`]: #method.is_finite + #[must_use = "method returns a new number and does not mutate the original value"] + #[unstable(feature = "float_interpolation", issue = "71015")] + pub fn lerp(self, start: f64, end: f64) -> f64 { + // consistent + if start == end { + start + + // exact/monotonic + } else { + self.mul_add(end, (-self).mul_add(start, start)) + } + } + // Solaris/Illumos requires a wrapper around log, log2, and log10 functions // because of their non-standard behavior (e.g., log(-n) returns -Inf instead // of expected NaN). From 0865acd22b9f0fa2d2ac0bcec61b479e4b3613d9 Mon Sep 17 00:00:00 2001 From: ltdk Date: Sun, 6 Jun 2021 22:42:53 -0400 Subject: [PATCH 2/4] A few lerp tests --- library/std/src/f32/tests.rs | 21 +++++++++++++++++++++ library/std/src/f64/tests.rs | 21 +++++++++++++++++++++ library/std/src/lib.rs | 1 + 3 files changed, 43 insertions(+) diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 0d4b865f3392a..7b54bc2335450 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -757,3 +757,24 @@ fn test_total_cmp() { assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::INFINITY)); assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); } + +#[test] +fn test_lerp_exact() { + assert_eq!(f32::lerp(0.0, 2.0, 4.0), 2.0); + assert_eq!(f32::lerp(1.0, 2.0, 4.0), 4.0); + assert_eq!(f32::lerp(0.0, f32::MIN, f32::MAX), f32::MIN); + assert_eq!(f32::lerp(1.0, f32::MIN, f32::MAX), f32::MAX); +} + +#[test] +fn test_lerp_consistent() { + assert_eq!(f32::lerp(f32::MAX, f32::MIN, f32::MIN), f32::MIN); + assert_eq!(f32::lerp(f32::MIN, f32::MAX, f32::MAX), f32::MAX); +} + +#[test] +fn test_lerp_values() { + assert_eq!(f32::lerp(0.25, 1.0, 2.0), 1.25); + assert_eq!(f32::lerp(0.50, 1.0, 2.0), 1.50); + assert_eq!(f32::lerp(0.75, 1.0, 2.0), 1.75); +} diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index 5c163cfe90e0b..3efb2e9d3236c 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -753,3 +753,24 @@ fn test_total_cmp() { assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f64::INFINITY)); assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); } + +#[test] +fn test_lerp_exact() { + assert_eq!(f64::lerp(0.0, 2.0, 4.0), 2.0); + assert_eq!(f64::lerp(1.0, 2.0, 4.0), 4.0); + assert_eq!(f64::lerp(0.0, f64::MIN, f64::MAX), f64::MIN); + assert_eq!(f64::lerp(1.0, f64::MIN, f64::MAX), f64::MAX); +} + +#[test] +fn test_lerp_consistent() { + assert_eq!(f64::lerp(f64::MAX, f64::MIN, f64::MIN), f64::MIN); + assert_eq!(f64::lerp(f64::MIN, f64::MAX, f64::MAX), f64::MAX); +} + +#[test] +fn test_lerp_values() { + assert_eq!(f64::lerp(0.25, 1.0, 2.0), 1.25); + assert_eq!(f64::lerp(0.50, 1.0, 2.0), 1.50); + assert_eq!(f64::lerp(0.75, 1.0, 2.0), 1.75); +} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 8e4c63762fd38..a0f7b41b8a0f5 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -268,6 +268,7 @@ #![feature(exhaustive_patterns)] #![feature(extend_one)] #![cfg_attr(bootstrap, feature(extended_key_value_attributes))] +#![feature(float_interpolation)] #![feature(fn_traits)] #![feature(format_args_nl)] #![feature(gen_future)] From d8e247e38c9ce6746a595d374cf260b46ac54f27 Mon Sep 17 00:00:00 2001 From: ltdk Date: Sun, 13 Jun 2021 14:00:15 -0400 Subject: [PATCH 3/4] More lerp tests, altering lerp docs --- library/std/src/f32.rs | 34 ++++++++++++++++++----------- library/std/src/f32/tests.rs | 42 ++++++++++++++++++++++++++++++++++++ library/std/src/f64.rs | 34 ++++++++++++++++++----------- library/std/src/f64/tests.rs | 34 +++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 26 deletions(-) diff --git a/library/std/src/f32.rs b/library/std/src/f32.rs index 32a4d415362a6..00cab72564f94 100644 --- a/library/std/src/f32.rs +++ b/library/std/src/f32.rs @@ -879,19 +879,27 @@ impl f32 { /// Linear interpolation between `start` and `end`. /// - /// This enables the calculation of a "smooth" transition between `start` and `end`, - /// where start is represented by `self == 0.0` and `end` is represented by `self == 1.0`. - /// - /// Values below 0.0 or above 1.0 are allowed, and in general this function closely - /// resembles the value of `start + self * (end - start)`, plus additional guarantees. - /// - /// Those guarantees are, assuming that all values are [`finite`]: - /// - /// * The value at 0.0 is always `start` and the value at 1.0 is always `end` (exactness) - /// * If `start == end`, the value at any point will always be `start == end` (consistency) - /// * The values will always move in the direction from `start` to `end` (monotonicity) - /// - /// [`finite`]: #method.is_finite + /// This enables linear interpolation between `start` and `end`, where start is represented by + /// `self == 0.0` and `end` is represented by `self == 1.0`. This is the basis of all + /// "transition", "easing", or "step" functions; if you change `self` from 0.0 to 1.0 + /// at a given rate, the result will change from `start` to `end` at a similar rate. + /// + /// Values below 0.0 or above 1.0 are allowed, allowing you to extrapolate values outside the + /// range from `start` to `end`. This also is useful for transition functions which might + /// move slightly past the end or start for a desired effect. Mathematically, the values + /// returned are equivalent to `start + self * (end - start)`, although we make a few specific + /// guarantees that are useful specifically to linear interpolation. + /// + /// These guarantees are: + /// + /// * If `start` and `end` are [finite], the value at 0.0 is always `start` and the + /// value at 1.0 is always `end`. (exactness) + /// * If `start` and `end` are [finite], the values will always move in the direction from + /// `start` to `end` (monotonicity) + /// * If `self` is [finite] and `start == end`, the value at any point will always be + /// `start == end`. (consistency) + /// + /// [finite]: #method.is_finite #[must_use = "method returns a new number and does not mutate the original value"] #[unstable(feature = "float_interpolation", issue = "71015")] pub fn lerp(self, start: f32, end: f32) -> f32 { diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index 7b54bc2335450..fe66a73afd63a 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -760,8 +760,11 @@ fn test_total_cmp() { #[test] fn test_lerp_exact() { + // simple values assert_eq!(f32::lerp(0.0, 2.0, 4.0), 2.0); assert_eq!(f32::lerp(1.0, 2.0, 4.0), 4.0); + + // boundary values assert_eq!(f32::lerp(0.0, f32::MIN, f32::MAX), f32::MIN); assert_eq!(f32::lerp(1.0, f32::MIN, f32::MAX), f32::MAX); } @@ -770,11 +773,50 @@ fn test_lerp_exact() { fn test_lerp_consistent() { assert_eq!(f32::lerp(f32::MAX, f32::MIN, f32::MIN), f32::MIN); assert_eq!(f32::lerp(f32::MIN, f32::MAX, f32::MAX), f32::MAX); + + // as long as t is finite, a/b can be infinite + assert_eq!(f32::lerp(f32::MAX, f32::NEG_INFINITY, f32::NEG_INFINITY), f32::NEG_INFINITY); + assert_eq!(f32::lerp(f32::MIN, f32::INFINITY, f32::INFINITY), f32::INFINITY); +} + +#[test] +fn test_lerp_nan_infinite() { + // non-finite t is not NaN if a/b different + assert!(!f32::lerp(f32::INFINITY, f32::MIN, f32::MAX).is_nan()); + assert!(!f32::lerp(f32::NEG_INFINITY, f32::MIN, f32::MAX).is_nan()); } #[test] fn test_lerp_values() { + // just a few basic values assert_eq!(f32::lerp(0.25, 1.0, 2.0), 1.25); assert_eq!(f32::lerp(0.50, 1.0, 2.0), 1.50); assert_eq!(f32::lerp(0.75, 1.0, 2.0), 1.75); } + +#[test] +fn test_lerp_monotonic() { + // near 0 + let below_zero = f32::lerp(-f32::EPSILON, f32::MIN, f32::MAX); + let zero = f32::lerp(0.0, f32::MIN, f32::MAX); + let above_zero = f32::lerp(f32::EPSILON, f32::MIN, f32::MAX); + assert!(below_zero <= zero); + assert!(zero <= above_zero); + assert!(below_zero <= above_zero); + + // near 0.5 + let below_half = f32::lerp(0.5 - f32::EPSILON, f32::MIN, f32::MAX); + let half = f32::lerp(0.5, f32::MIN, f32::MAX); + let above_half = f32::lerp(0.5 + f32::EPSILON, f32::MIN, f32::MAX); + assert!(below_half <= half); + assert!(half <= above_half); + assert!(below_half <= above_half); + + // near 1 + let below_one = f32::lerp(1.0 - f32::EPSILON, f32::MIN, f32::MAX); + let one = f32::lerp(1.0, f32::MIN, f32::MAX); + let above_one = f32::lerp(1.0 + f32::EPSILON, f32::MIN, f32::MAX); + assert!(below_one <= one); + assert!(one <= above_one); + assert!(below_one <= above_one); +} diff --git a/library/std/src/f64.rs b/library/std/src/f64.rs index 39c3e587e1f6f..ff41f999dd565 100644 --- a/library/std/src/f64.rs +++ b/library/std/src/f64.rs @@ -881,19 +881,27 @@ impl f64 { /// Linear interpolation between `start` and `end`. /// - /// This enables the calculation of a "smooth" transition between `start` and `end`, - /// where start is represented by `self == 0.0` and `end` is represented by `self == 1.0`. - /// - /// Values below 0.0 or above 1.0 are allowed, and in general this function closely - /// resembles the value of `start + self * (end - start)`, plus additional guarantees. - /// - /// Those guarantees are, assuming that all values are [`finite`]: - /// - /// * The value at 0.0 is always `start` and the value at 1.0 is always `end` (exactness) - /// * If `start == end`, the value at any point will always be `start == end` (consistency) - /// * The values will always move in the direction from `start` to `end` (monotonicity) - /// - /// [`finite`]: #method.is_finite + /// This enables linear interpolation between `start` and `end`, where start is represented by + /// `self == 0.0` and `end` is represented by `self == 1.0`. This is the basis of all + /// "transition", "easing", or "step" functions; if you change `self` from 0.0 to 1.0 + /// at a given rate, the result will change from `start` to `end` at a similar rate. + /// + /// Values below 0.0 or above 1.0 are allowed, allowing you to extrapolate values outside the + /// range from `start` to `end`. This also is useful for transition functions which might + /// move slightly past the end or start for a desired effect. Mathematically, the values + /// returned are equivalent to `start + self * (end - start)`, although we make a few specific + /// guarantees that are useful specifically to linear interpolation. + /// + /// These guarantees are: + /// + /// * If `start` and `end` are [finite], the value at 0.0 is always `start` and the + /// value at 1.0 is always `end`. (exactness) + /// * If `start` and `end` are [finite], the values will always move in the direction from + /// `start` to `end` (monotonicity) + /// * If `self` is [finite] and `start == end`, the value at any point will always be + /// `start == end`. (consistency) + /// + /// [finite]: #method.is_finite #[must_use = "method returns a new number and does not mutate the original value"] #[unstable(feature = "float_interpolation", issue = "71015")] pub fn lerp(self, start: f64, end: f64) -> f64 { diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index 3efb2e9d3236c..04cb0109261a4 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -756,8 +756,11 @@ fn test_total_cmp() { #[test] fn test_lerp_exact() { + // simple values assert_eq!(f64::lerp(0.0, 2.0, 4.0), 2.0); assert_eq!(f64::lerp(1.0, 2.0, 4.0), 4.0); + + // boundary values assert_eq!(f64::lerp(0.0, f64::MIN, f64::MAX), f64::MIN); assert_eq!(f64::lerp(1.0, f64::MIN, f64::MAX), f64::MAX); } @@ -766,11 +769,42 @@ fn test_lerp_exact() { fn test_lerp_consistent() { assert_eq!(f64::lerp(f64::MAX, f64::MIN, f64::MIN), f64::MIN); assert_eq!(f64::lerp(f64::MIN, f64::MAX, f64::MAX), f64::MAX); + + // as long as t is finite, a/b can be infinite + assert_eq!(f64::lerp(f64::MAX, f64::NEG_INFINITY, f64::NEG_INFINITY), f64::NEG_INFINITY); + assert_eq!(f64::lerp(f64::MIN, f64::INFINITY, f64::INFINITY), f64::INFINITY); +} + +#[test] +fn test_lerp_nan_infinite() { + // non-finite t is not NaN if a/b different + assert!(!f64::lerp(f64::INFINITY, f64::MIN, f64::MAX).is_nan()); + assert!(!f64::lerp(f64::NEG_INFINITY, f64::MIN, f64::MAX).is_nan()); } #[test] fn test_lerp_values() { + // just a few basic values assert_eq!(f64::lerp(0.25, 1.0, 2.0), 1.25); assert_eq!(f64::lerp(0.50, 1.0, 2.0), 1.50); assert_eq!(f64::lerp(0.75, 1.0, 2.0), 1.75); } + +#[test] +fn test_lerp_monotonic() { + // near 0 + let below_zero = f64::lerp(-f64::EPSILON, f64::MIN, f64::MAX); + let zero = f64::lerp(0.0, f64::MIN, f64::MAX); + let above_zero = f64::lerp(f64::EPSILON, f64::MIN, f64::MAX); + assert!(below_zero <= zero); + assert!(zero <= above_zero); + assert!(below_zero <= above_zero); + + // near 1 + let below_one = f64::lerp(1.0 - f64::EPSILON, f64::MIN, f64::MAX); + let one = f64::lerp(1.0, f64::MIN, f64::MAX); + let above_one = f64::lerp(1.0 + f64::EPSILON, f64::MIN, f64::MAX); + assert!(below_one <= one); + assert!(one <= above_one); + assert!(below_one <= above_one); +} From 525d76026fe855f6a9de4604d9fee50d974994a3 Mon Sep 17 00:00:00 2001 From: ltdk Date: Sun, 13 Jun 2021 14:04:43 -0400 Subject: [PATCH 4/4] Change tracking issue --- library/std/src/f32.rs | 2 +- library/std/src/f64.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/f32.rs b/library/std/src/f32.rs index 00cab72564f94..21bd79611a5e5 100644 --- a/library/std/src/f32.rs +++ b/library/std/src/f32.rs @@ -901,7 +901,7 @@ impl f32 { /// /// [finite]: #method.is_finite #[must_use = "method returns a new number and does not mutate the original value"] - #[unstable(feature = "float_interpolation", issue = "71015")] + #[unstable(feature = "float_interpolation", issue = "86269")] pub fn lerp(self, start: f32, end: f32) -> f32 { // consistent if start == end { diff --git a/library/std/src/f64.rs b/library/std/src/f64.rs index ff41f999dd565..8c8cf73741b51 100644 --- a/library/std/src/f64.rs +++ b/library/std/src/f64.rs @@ -903,7 +903,7 @@ impl f64 { /// /// [finite]: #method.is_finite #[must_use = "method returns a new number and does not mutate the original value"] - #[unstable(feature = "float_interpolation", issue = "71015")] + #[unstable(feature = "float_interpolation", issue = "86269")] pub fn lerp(self, start: f64, end: f64) -> f64 { // consistent if start == end {