Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: inf and nan in arithmetic expansions



On 8 Feb, Daniel Shahaf wrote:
> Oliver Kiddle wrote on Thu, 08 Feb 2018 00:25 +0100:
> > There was actually a patch posted back in workers/19597 to do this. I
> > don't know why it never got integrated other than that a certain
> > amount of integration work was perhaps required.

I have attached that patch below in a form that applies to current git.
I've removed the #ifdefs making it unconditional. This also dispenses
with the README.NONSTOP-FP and ksh93-test.sh files, though they are
a useful resource for some test cases. I've also not included the
set_fpc_csr() call that was for IRIX 6.x as I'd be fairly confident zsh
wouldn't build on IRIX anymore anyway. Please say if that's not true.

Do we even need the substitute isnan() and isinf() functions nowadays?

It definitely needs more work. In particular, the code for checking
"NaN" and "Inf" in math context should use strncmp with 3 as the length
otherwise it only accepts Inf and NaN at the end of the arithmetic
expression. In any case, I would still think predefined variables would
be better. Also note that the patch results in zsh crashing for integer
division by zero so some of the error handling may need to go back in.

Some additional work would be needed: zsh/mathfunc should provide an
isnan() function – (( NaN == NaN )) should return false so without it
you'd need to do a string comparison.
Anything we output should be accepted as input, and then there are test cases.

> Why do we generate "inf." with a period in the first place?  I know of
> no other tool that does this.  Shouldn't we generate "inf" and "nan"
> with no period?

This is actually system specific. We generate whatever printf(3)
generates. Try out Stephane's examples on Solaris and you get Inf and
NaN instead. I think I prefer those forms. We can make the printf code
detect them and hard code a consistent form so that we are consistent
across platforms.

> And then we could add 'inf' and 'nan' as readonly variables initialised to
> those respective values (as Oliver also suggests in the 19597 thread).  There
> are compatibility implications for scripts that use these variable names, but
> there is no way around them if we want to allow explicitly doing (( x = inf ))
> in user code...

I'm not sure about making them readonly simply because not doing so is
less likely to break an existing script.

Oliver

diff --git a/Src/Modules/mathfunc.c b/Src/Modules/mathfunc.c
index a7e8b29..a62154c 100644
--- a/Src/Modules/mathfunc.c
+++ b/Src/Modules/mathfunc.c
@@ -208,49 +208,6 @@ math_func(char *name, int argc, mnumber *argv, int id)
   if (errflag)
     return ret;
 
-  if (id & 0xff00) {
-      int rtst = 0;
-
-      switch ((id >> 8) & 0xff) {
-      case BF_POS:
-	  rtst = (argd <= 0.0);
-	  break;
-	  
-      case BF_NONNEG:
-	  rtst = (argd < 0.0);
-	  break;
-
-      case BF_FRAC:
-	  rtst = (fabs(argd) > 1.0);
-	  break;
-
-      case BF_GE1:
-	  rtst = (argd < 1.0);
-	  break;
-
-      case BF_FRACO:
-	  rtst = (fabs(argd) >= 1.0);
-	  break;
-
-      case BF_INTPOS:
-	  rtst = (argd <= 0 && (double)(zlong)argd == argd);
-	  break;
-
-      case BF_GTRM1:
-	  rtst = (argd <= -1);
-	  break;
-
-      case BF_POS2:
-	  rtst = (argd2 <= 0.0);
-	  break;
-      }
-
-      if (rtst) {
-	  zerr("math: argument to %s out of range", name);
-	  return ret;
-      }
-  }
-
   switch (id & 0xff) {
   case MF_ABS:
       ret.type = argv->type;
diff --git a/Src/math.c b/Src/math.c
index c383160..cdfe80b 100644
--- a/Src/math.c
+++ b/Src/math.c
@@ -578,6 +578,37 @@ int outputradix;
 /**/
 int outputunderscore;
 
+#ifndef HAVE_ISINF
+/**/
+int
+isinf(double x)
+{
+    if ((-1.0 < x) && (x < 1.0))       /* x is small, and thus finite */
+       return (0);
+    else if ((x + x) == x)             /* only true if x == Infinity */
+       return (1);
+    else                               /* must be finite (normal or subnormal), or NaN */
+       return (0);
+}
+#endif
+
+#if !defined(HAVE_ISNAN)
+/**/
+static double
+store(double *x)
+{
+    return (*x);
+}
+
+/**/
+int
+isnan(double x)
+{
+    /* (x != x) should be sufficient, but some compilers incorrectly optimize it away */
+    return (store(&x) != store(&x));
+}
+#endif
+
 /**/
 static int
 zzlex(void)
@@ -791,6 +822,21 @@ zzlex(void)
 	    break;
 	/* Fall through! */
 	default:
+	    if (strcmp(ptr-1, "NaN") == 0) {
+		yyval.type = MN_FLOAT;
+		yyval.u.d = 0.0;
+		yyval.u.d /= yyval.u.d;
+		ptr += 2;
+		return NUM;
+	    }
+	    else if (strcmp(ptr-1, "Inf") == 0) {
+		yyval.type = MN_FLOAT;
+		yyval.u.d = 0.0;
+		yyval.u.d = 1.0 / yyval.u.d;
+		ptr += 2;
+		return NUM;
+	    }
+
 	    if (idigit(*--ptr) || *ptr == '.')
 		return lexconstant();
 	    if (*ptr == '#') {
@@ -1068,10 +1114,6 @@ callmathfunc(char *o)
 static int
 notzero(mnumber a)
 {
-    if ((a.type & MN_INTEGER) ? a.u.l == 0 : a.u.d == 0.0) {
-	zerr("division by zero");
-	return 0;
-    }
     return 1;
 }
 
diff --git a/Src/params.c b/Src/params.c
index de7730a..108fb0d 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -36,6 +36,8 @@
 #else
 #include "patchlevel.h"
 
+#include <math.h>
+
 /* If removed from the ChangeLog for some reason */
 #ifndef ZSH_PATCHLEVEL
 #define ZSH_PATCHLEVEL "unknown"
@@ -5429,10 +5431,16 @@ convfloat(double dval, int digits, int flags, FILE *fout)
 	ret = NULL;
     } else {
 	VARARR(char, buf, 512 + digits);
-	sprintf(buf, fmt, digits, dval);
-	if (!strchr(buf, 'e') && !strchr(buf, '.'))
-	    strcat(buf, ".");
-	ret = dupstring(buf);
+	if (isinf(dval))
+	    ret = dupstring((dval < 0.0) ? "-Inf" : "Inf");
+	else if (isnan(dval))
+	    ret = dupstring("NaN");
+	else {
+	    sprintf(buf, fmt, digits, dval);
+	    if (!strchr(buf, 'e') && !strchr(buf, '.'))
+		strcat(buf, ".");
+	    ret = dupstring(buf);
+	}
     }
 #ifdef USE_LOCALE
     if (prev_locale) setlocale(LC_NUMERIC, prev_locale);
diff --git a/configure.ac b/configure.ac
index 1a498f8..aef0437 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1317,6 +1317,7 @@ AC_CHECK_FUNCS(strftime strptime mktime timelocal \
 	       erand48 open_memstream \
 	       posix_openpt \
 	       wctomb iconv \
+	       isinf isnan \
 	       grantpt unlockpt ptsname \
 	       htons ntohs \
 	       regcomp regexec regerror regfree \



Messages sorted by: Reverse Date, Date, Thread, Author