fix #189 (.max, .maxOrNull, .min, .minOrNull) for Iterable<int> and Iterable<double> (#212)

* add .min / .max on Iterable<num> to special case NaN and added tests
* fixes #189
diff --git a/AUTHORS b/AUTHORS
index e8063a8..76494b6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,3 +4,4 @@
 #   Name/Organization <email address>
 
 Google Inc.
+TimWhiting tim@whitings.org
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68333cb..85f17ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## 1.16.0-dev
 
 * Use a stable sort algorithm in the `IterableExtension.sortedBy` method.
+* Add `min`, `max`, `minOrNull` and `maxOrNull` getters to `IterableDoubleExtension`, `IterableNumberExtension` and `IterableIntegerExtension`
 
 ## 1.15.0
 
diff --git a/lib/src/iterable_extensions.dart b/lib/src/iterable_extensions.dart
index 942e7af..fae5f8e 100644
--- a/lib/src/iterable_extensions.dart
+++ b/lib/src/iterable_extensions.dart
@@ -567,7 +567,64 @@
 }
 
 /// Extensions that apply to iterables of numbers.
+///
+/// Specialized version of some extensions of [IterableComparableExtension]
+/// since doubles require special handling of [double.nan].
 extension IterableNumberExtension on Iterable<num> {
+  /// A minimal element of the iterable, or `null` it the iterable is empty.
+  num? get minOrNull {
+    var iterator = this.iterator;
+    if (iterator.moveNext()) {
+      var value = iterator.current;
+      if (value.isNaN) {
+        return value;
+      }
+      while (iterator.moveNext()) {
+        var newValue = iterator.current;
+        if (newValue.isNaN) {
+          return newValue;
+        }
+        if (newValue < value) {
+          value = newValue;
+        }
+      }
+      return value;
+    }
+    return null;
+  }
+
+  /// A minimal element of the iterable.
+  ///
+  /// The iterable must not be empty.
+  num get min => minOrNull ?? (throw StateError('No element'));
+
+  /// A maximal element of the iterable, or `null` if the iterable is empty.
+  num? get maxOrNull {
+    var iterator = this.iterator;
+    if (iterator.moveNext()) {
+      var value = iterator.current;
+      if (value.isNaN) {
+        return value;
+      }
+      while (iterator.moveNext()) {
+        var newValue = iterator.current;
+        if (newValue.isNaN) {
+          return newValue;
+        }
+        if (newValue > value) {
+          value = newValue;
+        }
+      }
+      return value;
+    }
+    return null;
+  }
+
+  /// A maximal element of the iterable.
+  ///
+  /// The iterable must not be empty.
+  num get max => maxOrNull ?? (throw StateError('No element'));
+
   /// The sum of the elements.
   ///
   /// The sum is zero if the iterable is empty.
@@ -599,8 +656,51 @@
 
 /// Extension on iterables of integers.
 ///
-/// Specialized version of some extensions of [IterableNumberExtension].
+/// Specialized version of some extensions of [IterableNumberExtension] or
+/// [IterableComparableExtension] since integers are only `Comparable<num>`.
 extension IterableIntegerExtension on Iterable<int> {
+  /// A minimal element of the iterable, or `null` it the iterable is empty.
+  int? get minOrNull {
+    var iterator = this.iterator;
+    if (iterator.moveNext()) {
+      var value = iterator.current;
+      while (iterator.moveNext()) {
+        var newValue = iterator.current;
+        if (newValue < value) {
+          value = newValue;
+        }
+      }
+      return value;
+    }
+    return null;
+  }
+
+  /// A minimal element of the iterable.
+  ///
+  /// The iterable must not be empty.
+  int get min => minOrNull ?? (throw StateError('No element'));
+
+  /// A maximal element of the iterable, or `null` if the iterable is empty.
+  int? get maxOrNull {
+    var iterator = this.iterator;
+    if (iterator.moveNext()) {
+      var value = iterator.current;
+      while (iterator.moveNext()) {
+        var newValue = iterator.current;
+        if (newValue > value) {
+          value = newValue;
+        }
+      }
+      return value;
+    }
+    return null;
+  }
+
+  /// A maximal element of the iterable.
+  ///
+  /// The iterable must not be empty.
+  int get max => maxOrNull ?? (throw StateError('No element'));
+
   /// The sum of the elements.
   ///
   /// The sum is zero if the iterable is empty.
@@ -641,8 +741,63 @@
 
 /// Extension on iterables of double.
 ///
-/// Specialized version of some extensions of [IterableNumberExtension].
+/// Specialized version of some extensions of [IterableNumberExtension] or
+/// [IterableComparableExtension] since doubles are only `Comparable<num>`.
 extension IterableDoubleExtension on Iterable<double> {
+  /// A minimal element of the iterable, or `null` it the iterable is empty.
+  double? get minOrNull {
+    var iterator = this.iterator;
+    if (iterator.moveNext()) {
+      var value = iterator.current;
+      if (value.isNaN) {
+        return value;
+      }
+      while (iterator.moveNext()) {
+        var newValue = iterator.current;
+        if (newValue.isNaN) {
+          return newValue;
+        }
+        if (newValue < value) {
+          value = newValue;
+        }
+      }
+      return value;
+    }
+    return null;
+  }
+
+  /// A minimal element of the iterable.
+  ///
+  /// The iterable must not be empty.
+  double get min => minOrNull ?? (throw StateError('No element'));
+
+  /// A maximal element of the iterable, or `null` if the iterable is empty.
+  double? get maxOrNull {
+    var iterator = this.iterator;
+    if (iterator.moveNext()) {
+      var value = iterator.current;
+      if (value.isNaN) {
+        return value;
+      }
+      while (iterator.moveNext()) {
+        var newValue = iterator.current;
+        if (newValue.isNaN) {
+          return newValue;
+        }
+        if (newValue > value) {
+          value = newValue;
+        }
+      }
+      return value;
+    }
+    return null;
+  }
+
+  /// A maximal element of the iterable.
+  ///
+  /// The iterable must not be empty.
+  double get max => maxOrNull ?? (throw StateError('No element'));
+
   /// The sum of the elements.
   ///
   /// The sum is zero if the iterable is empty.
@@ -689,26 +844,12 @@
       }
       return value;
     }
-    return null;
   }
 
   /// A minimal element of the iterable.
   ///
   /// The iterable must not be empty.
-  T get min {
-    var iterator = this.iterator;
-    if (iterator.moveNext()) {
-      var value = iterator.current;
-      while (iterator.moveNext()) {
-        var newValue = iterator.current;
-        if (value.compareTo(newValue) > 0) {
-          value = newValue;
-        }
-      }
-      return value;
-    }
-    throw StateError('No element');
-  }
+  T get min => minOrNull ?? (throw StateError('No element'));
 
   /// A maximal element of the iterable, or `null` if the iterable is empty.
   T? get maxOrNull {
@@ -729,20 +870,7 @@
   /// A maximal element of the iterable.
   ///
   /// The iterable must not be empty.
-  T get max {
-    var iterator = this.iterator;
-    if (iterator.moveNext()) {
-      var value = iterator.current;
-      while (iterator.moveNext()) {
-        var newValue = iterator.current;
-        if (value.compareTo(newValue) < 0) {
-          value = newValue;
-        }
-      }
-      return value;
-    }
-    throw StateError('No element');
-  }
+  T get max => maxOrNull ?? (throw StateError('No element'));
 
   /// Creates a sorted list of the elements of the iterable.
   ///
diff --git a/test/extensions_test.dart b/test/extensions_test.dart
index 51aac9e..741a3fd 100644
--- a/test/extensions_test.dart
+++ b/test/extensions_test.dart
@@ -887,6 +887,90 @@
           expect(iterable(<num>[1, 3, 5, 9]).average, 4.5);
         });
       });
+      group('.min', () {
+        test('empty', () {
+          expect(() => iterable(<int>[]).min, throwsStateError);
+          expect(() => iterable(<double>[]).min, throwsStateError);
+          expect(() => iterable(<num>[]).min, throwsStateError);
+        });
+        test('single', () {
+          expect(iterable(<int>[1]).min, 1);
+          expect(iterable(<double>[1.0]).min, 1.0);
+          expect(iterable(<num>[1.0]).min, 1.0);
+        });
+        test('multiple', () {
+          expect(iterable(<int>[3, 1, 2]).min, 1);
+          expect(iterable(<double>[3.0, 1.0, 2.5]).min, 1.0);
+          expect(iterable(<num>[3, 1, 2.5]).min, 1.0);
+        });
+        test('nan', () {
+          expect(iterable(<double>[3.0, 1.0, double.nan]).min, isNaN);
+          expect(iterable(<num>[3.0, 1, double.nan]).min, isNaN);
+        });
+      });
+      group('.minOrNull', () {
+        test('empty', () {
+          expect(iterable(<int>[]).minOrNull, null);
+          expect(iterable(<double>[]).minOrNull, null);
+          expect(iterable(<num>[]).minOrNull, null);
+        });
+        test('single', () {
+          expect(iterable(<int>[1]).minOrNull, 1);
+          expect(iterable(<double>[1.0]).minOrNull, 1.0);
+          expect(iterable(<num>[1.0]).minOrNull, 1.0);
+        });
+        test('multiple', () {
+          expect(iterable(<int>[3, 1, 2]).minOrNull, 1);
+          expect(iterable(<double>[3.0, 1.0, 2.5]).minOrNull, 1.0);
+          expect(iterable(<num>[3, 1, 2.5]).minOrNull, 1.0);
+        });
+        test('nan', () {
+          expect(iterable(<double>[3.0, 1.0, double.nan]).minOrNull, isNaN);
+          expect(iterable(<num>[3.0, 1, double.nan]).minOrNull, isNaN);
+        });
+      });
+      group('.max', () {
+        test('empty', () {
+          expect(() => iterable(<int>[]).max, throwsStateError);
+          expect(() => iterable(<double>[]).max, throwsStateError);
+          expect(() => iterable(<num>[]).max, throwsStateError);
+        });
+        test('single', () {
+          expect(iterable(<int>[1]).max, 1);
+          expect(iterable(<double>[1.0]).max, 1.0);
+          expect(iterable(<num>[1.0]).max, 1.0);
+        });
+        test('multiple', () {
+          expect(iterable(<int>[3, 1, 2]).max, 3);
+          expect(iterable(<double>[3.0, 1.0, 2.5]).max, 3.0);
+          expect(iterable(<num>[3, 1, 2.5]).max, 3);
+        });
+        test('nan', () {
+          expect(iterable(<double>[3.0, 1.0, double.nan]).max, isNaN);
+          expect(iterable(<num>[3.0, 1, double.nan]).max, isNaN);
+        });
+      });
+      group('.maxOrNull', () {
+        test('empty', () {
+          expect(iterable(<int>[]).maxOrNull, null);
+          expect(iterable(<double>[]).maxOrNull, null);
+          expect(iterable(<num>[]).maxOrNull, null);
+        });
+        test('single', () {
+          expect(iterable(<int>[1]).maxOrNull, 1);
+          expect(iterable(<double>[1.0]).maxOrNull, 1.0);
+          expect(iterable(<num>[1.0]).maxOrNull, 1.0);
+        });
+        test('multiple', () {
+          expect(iterable(<int>[3, 1, 2]).maxOrNull, 3);
+          expect(iterable(<double>[3.0, 1.0, 2.5]).maxOrNull, 3.0);
+          expect(iterable(<num>[3, 1, 2.5]).maxOrNull, 3);
+        });
+        test('nan', () {
+          expect(iterable(<double>[3.0, 1.0, double.nan]).maxOrNull, isNaN);
+          expect(iterable(<num>[3.0, 1, double.nan]).maxOrNull, isNaN);
+        });
+      });
     });
     group('of iterable', () {
       group('.flattened', () {