Skip to content

Linear interpolation #85925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions library/std/src/f32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we give any guarantees about non-finite values? E.g. that any nan wil result in a nan result, and/or that if only one of the values is infinite, the result will be that infinite value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I was considering doing the full list of guarantees that C++ provides, which are:

  • If isfinite(a) && isfinite(b):
    • If t == 0, the result is equal to a.
    • If t == 1, the result is equal to b.
    • If t >= 0 && t <= 1, the result is finite.
  • If isfinite(t) && a == b, the result is equal to a.
  • If isfinite(t) || (!isnan(t) && b-a != 0), the result is not NaN.
  • Let CMP(x,y) be 1 if x > y, -1 if x < y, and 0 otherwise. For any t1 and t2, the product of CMP(lerp(a, b, t2), lerp(a, b, t1)), CMP(t2, t1), and CMP(b, a) is non-negative. (That is, lerp is monotonic.)

But I figured that it might potentially limit what kinds of implementations we allow. I'll try adding tests for these (and comment them out if they don't work) and maybe we can hash it out in the tracking issue.

///
/// * 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))
}
}
}
21 changes: 21 additions & 0 deletions library/std/src/f32/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
28 changes: 28 additions & 0 deletions library/std/src/f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
21 changes: 21 additions & 0 deletions library/std/src/f64/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down