未验证 提交 dbc9b1a8 编写于 作者: C Chris Bracken 提交者: GitHub

lerpDouble: stricter handling of NaN and infinity (#20871)

Previously, the behaviour of lerpDouble with respect to NaN and infinity
was relatively complex and difficult to reason about. This patch
simplifies the behaviour with respect to those conditions and adds
documentation and tests.

In general, if `a == b` or both values are null, infinite, or NaN, `a`
is returned. Otherwise we require `a` and `b` and `t` to be finite or
null and the result of the linear interpolation is returned.
上级 14ac65c9
......@@ -6,14 +6,20 @@
part of dart.ui;
/// Linearly interpolate between two numbers.
// TODO(cbracken): Consider making a and b non-nullable.
// https://github.com/flutter/flutter/issues/64617
/// Linearly interpolate between two numbers, `a` and `b`, by an extrapolation
/// factor `t`.
///
/// When `a` and `b` are equal or both NaN, `a` is returned. Otherwise, if
/// `a`, `b`, and `t` are required to be finite or null, and the result of `a +
/// (b - a) * t` is returned, where nulls are defaulted to 0.0.
double? lerpDouble(num? a, num? b, double t) {
if (a == null && b == null)
return null;
if (a == b || (a?.isNaN == true) && (b?.isNaN == true))
return a?.toDouble();
a ??= 0.0;
b ??= 0.0;
assert(a.isFinite, 'Cannot interpolate between finite and non-finite values');
assert(b.isFinite, 'Cannot interpolate between finite and non-finite values');
assert(t.isFinite, 't must be finite when interpolating between values');
return a + (b - a) * t as double;
}
......
......@@ -7,6 +7,8 @@ import 'dart:ui';
import 'package:test/test.dart';
import 'test_util.dart';
void main() {
test('lerpDouble should return null if and only if both inputs are null', () {
expect(lerpDouble(null, null, 1.0), isNull);
......@@ -65,47 +67,53 @@ void main() {
expect(lerpDouble(10, 0, 5), -40);
});
test('lerpDouble should return NaN if any input is NaN', () {
expect(lerpDouble(0.0, 10.0, double.nan), isNaN);
expect(lerpDouble(0.0, double.infinity, double.nan), isNaN);
expect(lerpDouble(0.0, double.nan, 5.0), isNaN);
expect(lerpDouble(0.0, double.nan, double.infinity), isNaN);
expect(lerpDouble(0.0, double.nan, double.nan), isNaN);
expect(lerpDouble(double.infinity, 10.0, double.nan), isNaN);
expect(lerpDouble(double.infinity, double.infinity, double.nan), isNaN);
expect(lerpDouble(double.infinity, double.nan, 5.0), isNaN);
expect(lerpDouble(double.infinity, double.nan, double.infinity), isNaN);
expect(lerpDouble(double.infinity, double.nan, double.nan), isNaN);
expect(lerpDouble(double.nan, 10.0, 5.0), isNaN);
expect(lerpDouble(double.nan, 10.0, double.infinity), isNaN);
expect(lerpDouble(double.nan, 10.0, double.nan), isNaN);
expect(lerpDouble(double.nan, double.infinity, 5.0), isNaN);
expect(lerpDouble(double.nan, double.infinity, double.infinity), isNaN);
expect(lerpDouble(double.nan, double.infinity, double.nan), isNaN);
test('lerpDouble should return input value in all cases if begin/end are equal', () {
expect(lerpDouble(10.0, 10.0, 5.0), 10.0);
expect(lerpDouble(10.0, 10.0, double.nan), 10.0);
expect(lerpDouble(10.0, 10.0, double.infinity), 10.0);
expect(lerpDouble(10.0, 10.0, -double.infinity), 10.0);
expect(lerpDouble(10, 10, 5.0), 10.0);
expect(lerpDouble(10, 10, double.nan), 10.0);
expect(lerpDouble(10, 10, double.infinity), 10.0);
expect(lerpDouble(10, 10, -double.infinity), 10.0);
expect(lerpDouble(double.nan, double.nan, 5.0), isNaN);
expect(lerpDouble(double.nan, double.nan, double.infinity), isNaN);
expect(lerpDouble(double.nan, double.nan, double.nan), isNaN);
expect(lerpDouble(double.nan, double.nan, double.infinity), isNaN);
expect(lerpDouble(double.nan, double.nan, -double.infinity), isNaN);
expect(lerpDouble(double.infinity, double.infinity, 5.0), double.infinity);
expect(lerpDouble(double.infinity, double.infinity, double.nan), double.infinity);
expect(lerpDouble(double.infinity, double.infinity, double.infinity), double.infinity);
expect(lerpDouble(double.infinity, double.infinity, -double.infinity), double.infinity);
expect(lerpDouble(-double.infinity, -double.infinity, 5.0), -double.infinity);
expect(lerpDouble(-double.infinity, -double.infinity, double.nan), -double.infinity);
expect(lerpDouble(-double.infinity, -double.infinity, double.infinity), -double.infinity);
expect(lerpDouble(-double.infinity, -double.infinity, -double.infinity), -double.infinity);
});
test('lerpDouble returns NaN if interpolation results in Infinity - Infinity', () {
expect(lerpDouble(double.infinity, 10.0, 5.0), isNaN);
expect(lerpDouble(double.infinity, 10.0, double.infinity), isNaN);
expect(lerpDouble(-double.infinity, 10.0, 5.0), isNaN);
expect(lerpDouble(-double.infinity, 10.0, double.infinity), isNaN);
test('lerpDouble should throw AssertionError if interpolation value is NaN and a != b', () {
expectAssertion(() => lerpDouble(0.0, 10.0, double.nan));
});
test('lerpDouble returns +/- infinity if interpolating towards an infinity', () {
expect(lerpDouble(double.infinity, 10.0, -5.0)?.isInfinite, isTrue);
expect(lerpDouble(double.infinity, 10.0, -double.infinity)?.isInfinite, isTrue);
expect(lerpDouble(-double.infinity, 10.0, -5.0)?.isInfinite, isTrue);
expect(lerpDouble(-double.infinity, 10.0, -double.infinity)?.isInfinite, isTrue);
expect(lerpDouble(0.0, double.infinity, 5.0)?.isInfinite, isTrue);
expect(lerpDouble(0.0, double.infinity, -5.0)?.isInfinite, isTrue);
expect(lerpDouble(0.0, 10.0, double.infinity)?.isInfinite, isTrue);
expect(lerpDouble(0.0, double.infinity, double.infinity)?.isInfinite, isTrue);
test('lerpDouble should throw AssertionError if interpolation value is +/- infinity and a != b', () {
expectAssertion(() => lerpDouble(0.0, 10.0, double.infinity));
expectAssertion(() => lerpDouble(0.0, 10.0, -double.infinity));
});
test('lerpDouble returns NaN if start/end and interpolation value are infinity', () {
expect(lerpDouble(double.infinity, double.infinity, double.infinity), isNaN);
test('lerpDouble should throw AssertionError if either start or end are NaN', () {
expectAssertion(() => lerpDouble(double.nan, 10.0, 5.0));
expectAssertion(() => lerpDouble(0.0, double.nan, 5.0));
});
test('lerpDouble should throw AssertionError if either start or end are +/- infinity', () {
expectAssertion(() => lerpDouble(double.infinity, 10.0, 5.0));
expectAssertion(() => lerpDouble(-double.infinity, 10.0, 5.0));
expectAssertion(() => lerpDouble(0.0, double.infinity, 5.0));
expectAssertion(() => lerpDouble(0.0, -double.infinity, 5.0));
});
}
final Matcher throwsAssertionError = throwsA(const TypeMatcher<AssertionError>());
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册