Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit dbc9b1a

Browse files
authored
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.
1 parent 14ac65c commit dbc9b1a

2 files changed

Lines changed: 53 additions & 39 deletions

File tree

lib/ui/lerp.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66

77
part of dart.ui;
88

9-
/// Linearly interpolate between two numbers.
10-
// TODO(cbracken): Consider making a and b non-nullable.
11-
// https://github.com/flutter/flutter/issues/64617
9+
/// Linearly interpolate between two numbers, `a` and `b`, by an extrapolation
10+
/// factor `t`.
11+
///
12+
/// When `a` and `b` are equal or both NaN, `a` is returned. Otherwise, if
13+
/// `a`, `b`, and `t` are required to be finite or null, and the result of `a +
14+
/// (b - a) * t` is returned, where nulls are defaulted to 0.0.
1215
double? lerpDouble(num? a, num? b, double t) {
13-
if (a == null && b == null)
14-
return null;
16+
if (a == b || (a?.isNaN == true) && (b?.isNaN == true))
17+
return a?.toDouble();
1518
a ??= 0.0;
1619
b ??= 0.0;
20+
assert(a.isFinite, 'Cannot interpolate between finite and non-finite values');
21+
assert(b.isFinite, 'Cannot interpolate between finite and non-finite values');
22+
assert(t.isFinite, 't must be finite when interpolating between values');
1723
return a + (b - a) * t as double;
1824
}
1925

testing/dart/lerp_test.dart

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import 'dart:ui';
77

88
import 'package:test/test.dart';
99

10+
import 'test_util.dart';
11+
1012
void main() {
1113
test('lerpDouble should return null if and only if both inputs are null', () {
1214
expect(lerpDouble(null, null, 1.0), isNull);
@@ -65,47 +67,53 @@ void main() {
6567
expect(lerpDouble(10, 0, 5), -40);
6668
});
6769

68-
test('lerpDouble should return NaN if any input is NaN', () {
69-
expect(lerpDouble(0.0, 10.0, double.nan), isNaN);
70-
expect(lerpDouble(0.0, double.infinity, double.nan), isNaN);
71-
expect(lerpDouble(0.0, double.nan, 5.0), isNaN);
72-
expect(lerpDouble(0.0, double.nan, double.infinity), isNaN);
73-
expect(lerpDouble(0.0, double.nan, double.nan), isNaN);
74-
expect(lerpDouble(double.infinity, 10.0, double.nan), isNaN);
75-
expect(lerpDouble(double.infinity, double.infinity, double.nan), isNaN);
76-
expect(lerpDouble(double.infinity, double.nan, 5.0), isNaN);
77-
expect(lerpDouble(double.infinity, double.nan, double.infinity), isNaN);
78-
expect(lerpDouble(double.infinity, double.nan, double.nan), isNaN);
79-
expect(lerpDouble(double.nan, 10.0, 5.0), isNaN);
80-
expect(lerpDouble(double.nan, 10.0, double.infinity), isNaN);
81-
expect(lerpDouble(double.nan, 10.0, double.nan), isNaN);
82-
expect(lerpDouble(double.nan, double.infinity, 5.0), isNaN);
83-
expect(lerpDouble(double.nan, double.infinity, double.infinity), isNaN);
84-
expect(lerpDouble(double.nan, double.infinity, double.nan), isNaN);
70+
test('lerpDouble should return input value in all cases if begin/end are equal', () {
71+
expect(lerpDouble(10.0, 10.0, 5.0), 10.0);
72+
expect(lerpDouble(10.0, 10.0, double.nan), 10.0);
73+
expect(lerpDouble(10.0, 10.0, double.infinity), 10.0);
74+
expect(lerpDouble(10.0, 10.0, -double.infinity), 10.0);
75+
76+
expect(lerpDouble(10, 10, 5.0), 10.0);
77+
expect(lerpDouble(10, 10, double.nan), 10.0);
78+
expect(lerpDouble(10, 10, double.infinity), 10.0);
79+
expect(lerpDouble(10, 10, -double.infinity), 10.0);
80+
8581
expect(lerpDouble(double.nan, double.nan, 5.0), isNaN);
86-
expect(lerpDouble(double.nan, double.nan, double.infinity), isNaN);
8782
expect(lerpDouble(double.nan, double.nan, double.nan), isNaN);
83+
expect(lerpDouble(double.nan, double.nan, double.infinity), isNaN);
84+
expect(lerpDouble(double.nan, double.nan, -double.infinity), isNaN);
85+
86+
expect(lerpDouble(double.infinity, double.infinity, 5.0), double.infinity);
87+
expect(lerpDouble(double.infinity, double.infinity, double.nan), double.infinity);
88+
expect(lerpDouble(double.infinity, double.infinity, double.infinity), double.infinity);
89+
expect(lerpDouble(double.infinity, double.infinity, -double.infinity), double.infinity);
90+
91+
expect(lerpDouble(-double.infinity, -double.infinity, 5.0), -double.infinity);
92+
expect(lerpDouble(-double.infinity, -double.infinity, double.nan), -double.infinity);
93+
expect(lerpDouble(-double.infinity, -double.infinity, double.infinity), -double.infinity);
94+
expect(lerpDouble(-double.infinity, -double.infinity, -double.infinity), -double.infinity);
8895
});
8996

90-
test('lerpDouble returns NaN if interpolation results in Infinity - Infinity', () {
91-
expect(lerpDouble(double.infinity, 10.0, 5.0), isNaN);
92-
expect(lerpDouble(double.infinity, 10.0, double.infinity), isNaN);
93-
expect(lerpDouble(-double.infinity, 10.0, 5.0), isNaN);
94-
expect(lerpDouble(-double.infinity, 10.0, double.infinity), isNaN);
97+
test('lerpDouble should throw AssertionError if interpolation value is NaN and a != b', () {
98+
expectAssertion(() => lerpDouble(0.0, 10.0, double.nan));
9599
});
96100

97-
test('lerpDouble returns +/- infinity if interpolating towards an infinity', () {
98-
expect(lerpDouble(double.infinity, 10.0, -5.0)?.isInfinite, isTrue);
99-
expect(lerpDouble(double.infinity, 10.0, -double.infinity)?.isInfinite, isTrue);
100-
expect(lerpDouble(-double.infinity, 10.0, -5.0)?.isInfinite, isTrue);
101-
expect(lerpDouble(-double.infinity, 10.0, -double.infinity)?.isInfinite, isTrue);
102-
expect(lerpDouble(0.0, double.infinity, 5.0)?.isInfinite, isTrue);
103-
expect(lerpDouble(0.0, double.infinity, -5.0)?.isInfinite, isTrue);
104-
expect(lerpDouble(0.0, 10.0, double.infinity)?.isInfinite, isTrue);
105-
expect(lerpDouble(0.0, double.infinity, double.infinity)?.isInfinite, isTrue);
101+
test('lerpDouble should throw AssertionError if interpolation value is +/- infinity and a != b', () {
102+
expectAssertion(() => lerpDouble(0.0, 10.0, double.infinity));
103+
expectAssertion(() => lerpDouble(0.0, 10.0, -double.infinity));
106104
});
107105

108-
test('lerpDouble returns NaN if start/end and interpolation value are infinity', () {
109-
expect(lerpDouble(double.infinity, double.infinity, double.infinity), isNaN);
106+
test('lerpDouble should throw AssertionError if either start or end are NaN', () {
107+
expectAssertion(() => lerpDouble(double.nan, 10.0, 5.0));
108+
expectAssertion(() => lerpDouble(0.0, double.nan, 5.0));
109+
});
110+
111+
test('lerpDouble should throw AssertionError if either start or end are +/- infinity', () {
112+
expectAssertion(() => lerpDouble(double.infinity, 10.0, 5.0));
113+
expectAssertion(() => lerpDouble(-double.infinity, 10.0, 5.0));
114+
expectAssertion(() => lerpDouble(0.0, double.infinity, 5.0));
115+
expectAssertion(() => lerpDouble(0.0, -double.infinity, 5.0));
110116
});
111117
}
118+
119+
final Matcher throwsAssertionError = throwsA(const TypeMatcher<AssertionError>());

0 commit comments

Comments
 (0)