Redo/add seq precision logic.

Josh Gao hit a case where "seq 1000000 1000001" output 1e+06, and while he
was there changed several things to work like existing seq implementations.
I changed a couple back (commenting out the test cases) until somebody
came come up with a reason (or existing use case) to do it that way.
diff --git a/tests/seq.test b/tests/seq.test
index 7107978..15a208b 100755
--- a/tests/seq.test
+++ b/tests/seq.test
@@ -19,9 +19,14 @@
 testing "count down by 2" "seq 8 -2 4" "8\n6\n4\n" "" ""
 testing "count wrong way #1" "seq 4 -2 8" "" "" ""
 testing "count wrong way #2" "seq 8 2 4" "" "" ""
-testing "count by .3" "seq 3 .3 4" "3\n3.3\n3.6\n3.9\n" "" ""
-testing "count by -.9" "seq .7 -.9 -2.2" "0.7\n-0.2\n-1.1\n-2\n" "" ""
-testing "count by zero" "seq 4 0 8 | head -n 10" "" "" ""
+testing "count by .3" "seq 3 .3 4" "3.0\n3.3\n3.6\n3.9\n" "" ""
+testing "count by -.9" "seq .7 -.9 -2.2" "0.7\n-0.2\n-1.1\n-2.0\n" "" ""
+
+# Ubuntu does this, for no obvious reason. (The "yes" command exists.)
+#testing "count up by zero" "seq 4 0 8 | head -n 4" "4\n4\n4\n4\n" "" ""
+#testing "count nowhere by zero" "seq 4 0 4 | head -n 4" "4\n4\n4\n4\n" "" ""
+
+testing "count down by zero" "seq 8 0 4 | head -n 4" "" "" ""
 testing "separator -" "seq -s - 1 3" "1-2-3\n" "" ""
 testing "format string" 'seq -f %+01g -10 5 10' "-10\n-5\n+0\n+5\n+10\n" \
   "" ""
@@ -46,3 +51,21 @@
   testing "filter reject -f '$i'" \
     "seq -f '$i' 1 3 2>/dev/null || echo no" "no\n" "" ""
 done
+
+testing "precision inc" "seq -s, 1.0 2.00 4" "1.00,3.00\n" "" ""
+testing "precision first" "seq -s, 1.000 2.0 4" "1.000,3.000\n" "" ""
+
+# In ubuntu inc and first set precision, but last doesn't. (Why?)
+#testing "precision last" "seq -s, 1.0 2.0 4.00" "1.0,3.0\n" "" ""
+
+testing "precision int" "seq -s, 9007199254740991 1 9007199254740991" \
+  "9007199254740991\n" "" ""
+testing "precision e" "seq -s, 1.0e0 2" "1.0,2.0\n" "" ""
+testing "precision E" "seq -s, 1.0E0 2" "1.0,2.0\n" "" ""
+
+testing "invalid last" "seq 1 1 1f 2>/dev/null || echo y" "y\n" "" ""
+testing "invalid first" "seq 1f 1 1 2>/dev/null || echo y" "y\n" "" ""
+testing "invalid increment" "seq 1 1f 1 2>/dev/null || echo y" "y\n" "" ""
+
+# TODO: busybox fails this too, but GNU seems to not use double for large ints.
+#testing "too large for double" "seq -s, 9007199254740991 1 9007199254740992" "9007199254740992\n" "" ""
diff --git a/toys/lsb/seq.c b/toys/lsb/seq.c
index d5a6c0d..a8c1a4e 100644
--- a/toys/lsb/seq.c
+++ b/toys/lsb/seq.c
@@ -28,6 +28,8 @@
 GLOBALS(
   char *sep;
   char *fmt;
+
+  int precision;
 )
 
 // Ensure there's one %f escape with correct attributes
@@ -42,51 +44,56 @@
   }
 }
 
+// Parse a numeric argument setting *prec to the precision of this argument.
+// This reproduces the "1.234e5" precision bug from upstream.
+static double parsef(char *s)
+{
+  char *dp = strchr(s, '.');
+
+  if (dp++) TT.precision = maxof(TT.precision, strcspn(dp, "eE"));
+
+  return xstrtod(s);
+}
+
 void seq_main(void)
 {
-  double first, increment, last, dd;
-  char *sep_str = "\n", *fmt_str = "%g";
+  double first = 1, increment = 1, last, dd;
   int i;
 
-  // Parse command line arguments, with appropriate defaults.
-  // Note that any non-numeric arguments are treated as zero.
-  first = increment = 1;
+  if (!TT.sep) TT.sep = "\n";
   switch (toys.optc) {
-    case 3: increment = atof(toys.optargs[1]);
-    case 2: first = atof(*toys.optargs);
-    default: last = atof(toys.optargs[toys.optc-1]);
+    case 3: increment = parsef(toys.optargs[1]);
+    case 2: first = parsef(*toys.optargs);
+    default: last = parsef(toys.optargs[toys.optc-1]);
   }
 
+  // Prepare format string with appropriate precision. Can't use %g because 1e6
+  if (toys.optflags & FLAG_f) insanitize(TT.fmt);
+  else sprintf(TT.fmt = toybuf, "%%.%df", TT.precision);
+
   // Pad to largest width
   if (toys.optflags & FLAG_w) {
-    char *s;
-    int len, dot, left = 0, right = 0;
+    int len = 0;
 
     for (i=0; i<3; i++) {
       dd = (double []){first, increment, last}[i];
-
-      len = sprintf(toybuf, "%g", dd);
-      if ((s = strchr(toybuf, '.'))) {
-        dot = s-toybuf;
-        if (left<dot) left = dot;
-        dot = len-dot-1;
-        if (right<dot) right = dot;
-      } else if (len>left) left = len;
+      len = maxof(len, snprintf(0, 0, TT.fmt, dd));
     }
-
-    sprintf(fmt_str = toybuf, "%%0%d.%df", left+right+!!right, right);
+    sprintf(TT.fmt = toybuf, "%%0%d.%df", len, TT.precision);
   }
-  if (toys.optflags & FLAG_f) insanitize(fmt_str = TT.fmt);
-  if (toys.optflags & FLAG_s) sep_str = TT.sep;
+
+  // Other implementations output nothing if increment is 0 and first > last,
+  // but loop forever if first < last or even first == last. We output
+  // nothing for all three, if you want endless output use "yes".
+  if (!increment) return;
 
   i = 0;
-  dd = first;
-  if (increment) for (;;) {
-    // avoid accumulating rounding errors from increment
+  for (;;) {
+    // Multiply to avoid accumulating rounding errors from increment.
     dd = first+i*increment;
     if ((increment<0 && dd<last) || (increment>0 && dd>last)) break;
-    if (i++) printf("%s", sep_str);
-    printf(fmt_str, dd);
+    if (i++) printf("%s", TT.sep);
+    printf(TT.fmt, dd);
   }
 
   if (i) printf("\n");