diff --git a/lib/ui/lerp.dart b/lib/ui/lerp.dart index c4841e2ae37b6135422ee11b638b34eaafa93063..2357433ba33d854f1232cf240898e2c82739aa03 100644 --- a/lib/ui/lerp.dart +++ b/lib/ui/lerp.dart @@ -20,21 +20,21 @@ double? lerpDouble(num? a, num? b, double t) { 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; + return a * (1.0 - t) + b * t as double; } /// Linearly interpolate between two doubles. /// /// Same as [lerpDouble] but specialized for non-null `double` type. double _lerpDouble(double a, double b, double t) { - return a + (b - a) * t; + return a * (1.0 - t) + b * t; } /// Linearly interpolate between two integers. /// /// Same as [lerpDouble] but specialized for non-null `int` type. double _lerpInt(int a, int b, double t) { - return a + (b - a) * t; + return a * (1.0 - t) + b * t; } /// Same as [num.clamp] but specialized for non-null [int]. diff --git a/testing/dart/lerp_test.dart b/testing/dart/lerp_test.dart index 4395e974747f7217527c4aaa34f006877c9cdaa9..d9e43092c95e7705113ac55b2a15e09133d36c99 100644 --- a/testing/dart/lerp_test.dart +++ b/testing/dart/lerp_test.dart @@ -20,19 +20,19 @@ void main() { }); test('lerpDouble should treat a null input as 0 if the other input is non-null', () { - expect(lerpDouble(null, 10.0, 0.25), 2.5); - expect(lerpDouble(10.0, null, 0.25), 7.5); + expect(lerpDouble(null, 10.0, 0.25), closeTo(2.5, precisionErrorTolerance)); + expect(lerpDouble(10.0, null, 0.25), closeTo(7.5, precisionErrorTolerance)); - expect(lerpDouble(null, 10, 0.25), 2.5); - expect(lerpDouble(10, null, 0.25), 7.5); + expect(lerpDouble(null, 10, 0.25), closeTo(2.5, precisionErrorTolerance)); + expect(lerpDouble(10, null, 0.25), closeTo(7.5, precisionErrorTolerance)); }); test('lerpDouble should handle interpolation values < 0.0', () { - expect(lerpDouble(0.0, 10.0, -5.0), -50.0); - expect(lerpDouble(10.0, 0.0, -5.0), 60.0); + expect(lerpDouble(0.0, 10.0, -5.0), closeTo(-50.0, precisionErrorTolerance)); + expect(lerpDouble(10.0, 0.0, -5.0), closeTo(60.0, precisionErrorTolerance)); - expect(lerpDouble(0, 10, -5), -50); - expect(lerpDouble(10, 0, -5), 60); + expect(lerpDouble(0, 10, -5), closeTo(-50, precisionErrorTolerance)); + expect(lerpDouble(10, 0, -5), closeTo(60, precisionErrorTolerance)); }); test('lerpDouble should return the start value at 0.0', () { @@ -44,11 +44,17 @@ void main() { }); test('lerpDouble should interpolate between two values', () { - expect(lerpDouble(0.0, 10.0, 0.25), 2.5); - expect(lerpDouble(10.0, 0.0, 0.25), 7.5); + expect(lerpDouble(0.0, 10.0, 0.25), closeTo(2.5, precisionErrorTolerance)); + expect(lerpDouble(10.0, 0.0, 0.25), closeTo(7.5, precisionErrorTolerance)); - expect(lerpDouble(0, 10, 0.25), 2.5); - expect(lerpDouble(10, 0, 0.25), 7.5); + expect(lerpDouble(0, 10, 0.25), closeTo(2.5, precisionErrorTolerance)); + expect(lerpDouble(10, 0, 0.25), closeTo(7.5, precisionErrorTolerance)); + + // Exact answer: 20.0 - 1.0e-29 + expect(lerpDouble(10.0, 1.0e30, 1.0e-29), closeTo(20.0, precisionErrorTolerance)); + + // Exact answer: 5.0 + 5.0e29 + expect(lerpDouble(10.0, 1.0e30, 0.5), closeTo(5.0e29, precisionErrorTolerance)); }); test('lerpDouble should return the end value at 1.0', () { @@ -57,14 +63,17 @@ void main() { expect(lerpDouble(0, 10, 5), 50); expect(lerpDouble(10, 0, 5), -40); + + expect(lerpDouble(1.0e30, 10.0, 1.0), 10.0); + expect(lerpDouble(10.0, 1.0e30, 0.0), 10.0); }); test('lerpDouble should handle interpolation values > 1.0', () { - expect(lerpDouble(0.0, 10.0, 5.0), 50.0); - expect(lerpDouble(10.0, 0.0, 5.0), -40.0); + expect(lerpDouble(0.0, 10.0, 5.0), closeTo(50.0, precisionErrorTolerance)); + expect(lerpDouble(10.0, 0.0, 5.0), closeTo(-40.0, precisionErrorTolerance)); - expect(lerpDouble(0, 10, 5), 50); - expect(lerpDouble(10, 0, 5), -40); + expect(lerpDouble(0, 10, 5), closeTo(50, precisionErrorTolerance)); + expect(lerpDouble(10, 0, 5), closeTo(-40, precisionErrorTolerance)); }); test('lerpDouble should return input value in all cases if begin/end are equal', () { diff --git a/testing/dart/test_util.dart b/testing/dart/test_util.dart index e990b138e7f3ebbde58ffe9813b0632c9323045a..9ef1f2d25f50dd77f86581e520a36a8188fca369 100644 --- a/testing/dart/test_util.dart +++ b/testing/dart/test_util.dart @@ -6,6 +6,13 @@ import 'package:test/test.dart'; +/// The epsilon of tolerable double precision error. +/// +/// This is used in various places in the framework to allow for floating point +/// precision loss in calculations. Differences below this threshold are safe +/// to disregard. +const double precisionErrorTolerance = 1e-10; + /// Asserts that `callback` throws an [AssertionError]. /// /// When running in a VM in which assertions are enabled, asserts that the