zsh-workers
 help / color / mirror / code / Atom feed
* inf and nan in arithmetic expansions
@ 2018-02-07 22:30 Stephane Chazelas
  2018-02-07 23:25 ` Oliver Kiddle
  0 siblings, 1 reply; 15+ messages in thread
From: Stephane Chazelas @ 2018-02-07 22:30 UTC (permalink / raw)
  To: Zsh hackers list

Hi,

While looking into:
https://unix.stackexchange.com/questions/422122/why-does-0-1-expand-to-0-10000000000000001-in-zsh

I noted that zsh is very good at making sure that the result of
floating point arithmetic expansions are always suitable for
reinput inside other arithmetic expansions as floats.

For instance $((0.5 * 2)) expands to 1. and not 1 so that
when used in arithmetic expressions, it is still a float.

Or that is (I think) why numbers are expressed with 17 digits,
the full precision of IEEE 754 doubles, so that when reinput, we
get the same double.

Now, there's one case where it doesn't work is when the
expansion results into nan or inf

$ echo $((1e500))
inf.
$ echo $(($((1e500))))
zsh: bad floating point constant
$ a=$((1e200**2))
$ echo $((a))
zsh: bad floating point constant
$ echo $((1e500/1e500))
-nan.
$ echo $(($((1e500/1e500))))
zsh: bad floating point constant

See also:

$ typeset -F a; a=$((1e500))
zsh: bad floating point constant


neither "inf." nor "inf" are understood in arithmetic
expressions (and for "inf.", nor by other tools like awk, or
even the builtin printf):

$ printf '%f\n' inf nan infinity NAN
inf
nan
inf
nan
$ printf '%f\n' inf. nan.
zsh: bad floating point constant
zsh: bad floating point constant
0.000000
0.000000

Not sure what's the best way to address that. 

At the moment, $((inf)) is meant to expand to the result of
the arithmetic expression in $inf

$ inf=1+1 zsh -c 'echo $((inf))'
2

It should be safe to change zsh so that inf. (and Inf. INF. NAN.
nan., maybe also Infinity.) are recognised in arithmetic
expression, as it's currently invalid, but that leaves the
problem of "inf." not being recognised by other tools
(awk/printf).

yash and ksh93 are the two other POSIX-like shells (that I know)
that support floating point arithmetics. yash is not much
better:

$ echo $((1e400))
yash: arithmetic: `1e500' is not a valid number
$ echo $((1e200*1e200))
inf
$ inf=42; echo $(($((1e200*1e200))))
42

ksh93 recognises inf (not infinity, same for its printf) and nan
on input:

$ echo $((1e6000))
inf
$ echo $((INF))
inf
$ echo $((nan))
nan
$ echo $((infinity))
0

It is documented (though not the fact that all case variants are
supported).

Slightly related:

The printf builtin understands the C99 hex with binary
exponent on input (where strtod supports them), but not on
output (%a, %A) and not in arithmetic expressions

$ printf "%g\n" 0x1p4
16
$ printf "%a\n" 16
printf: %a: invalid directive
$ echo $((0x1p4))
zsh: bad math expression: operator expected at `p4'

yash and ksh93 do support them.

(not that I would need them, that would be more for
consistency).

-- 
Stephane


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-07 22:30 inf and nan in arithmetic expansions Stephane Chazelas
@ 2018-02-07 23:25 ` Oliver Kiddle
  2018-02-08  9:38   ` Peter Stephenson
  2018-02-08 12:46   ` Daniel Shahaf
  0 siblings, 2 replies; 15+ messages in thread
From: Oliver Kiddle @ 2018-02-07 23:25 UTC (permalink / raw)
  To: Zsh hackers list

Stephane Chazelas wrote:
>
> neither "inf." nor "inf" are understood in arithmetic
> expressions (and for "inf.", nor by other tools like awk, or
> even the builtin printf):

> It should be safe to change zsh so that inf. (and Inf. INF. NAN.
> nan., maybe also Infinity.) are recognised in arithmetic
> expression, as it's currently invalid, but that leaves the
> problem of "inf." not being recognised by other tools
> (awk/printf).

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.

It might be possible to forward port that work from 4.1.1 to the current
release. Would that be welcomed or was the original patch rejected for
good reasons? Any idea of where we might crib some decent test cases for
IEEE 754 arithmetic from?

> The printf builtin understands the C99 hex with binary
> exponent on input (where strtod supports them), but not on
> output (%a, %A) and not in arithmetic expressions
>
> $ printf "%g\n" 0x1p4
> 16
> $ printf "%a\n" 16
> printf: %a: invalid directive

This was down to %a not having been portable to all systems in wide
use at the time zsh's printf was written: we rely on the underlying C
library printf. Adding an autoconf test would have been a lot more work
than the code change to support it. From a quick check of man pages,
this is perhaps not a problem anymore. Could we add it today without
bothering with autoconf magic?

Oliver


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-07 23:25 ` Oliver Kiddle
@ 2018-02-08  9:38   ` Peter Stephenson
  2018-02-08 12:46   ` Daniel Shahaf
  1 sibling, 0 replies; 15+ messages in thread
From: Peter Stephenson @ 2018-02-08  9:38 UTC (permalink / raw)
  To: Zsh hackers list

On Thu, 08 Feb 2018 00:25:46 +0100
Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
> Stephane Chazelas wrote:
> >
> > neither "inf." nor "inf" are understood in arithmetic
> > expressions (and for "inf.", nor by other tools like awk, or
> > even the builtin printf):
> 
> > It should be safe to change zsh so that inf. (and Inf. INF. NAN.
> > nan., maybe also Infinity.) are recognised in arithmetic
> > expression, as it's currently invalid, but that leaves the
> > problem of "inf." not being recognised by other tools
> > (awk/printf).
> 
> 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.
> 
> It might be possible to forward port that work from 4.1.1 to the current
> release. Would that be welcomed or was the original patch rejected for
> good reasons?

That's fine by me --- I'd completely forgotten.  The math code only
evolves fairly slowly.

I'd be a little bit worried if there were cases that produced errors
before that no longer do.  But given that typically the result is
"inf." with no error at the moment, that may well not be a real problem.

pws


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-07 23:25 ` Oliver Kiddle
  2018-02-08  9:38   ` Peter Stephenson
@ 2018-02-08 12:46   ` Daniel Shahaf
  2018-02-08 14:22     ` Stephane Chazelas
  2018-02-16 16:51     ` Oliver Kiddle
  1 sibling, 2 replies; 15+ messages in thread
From: Daniel Shahaf @ 2018-02-08 12:46 UTC (permalink / raw)
  To: zsh-workers

Oliver Kiddle wrote on Thu, 08 Feb 2018 00:25 +0100:
> > neither "inf." nor "inf" are understood in arithmetic
> > expressions (and for "inf.", nor by other tools like awk, or
> > even the builtin printf):
> 
> > It should be safe to change zsh so that inf. (and Inf. INF. NAN.
> > nan., maybe also Infinity.) are recognised in arithmetic
> > expression, as it's currently invalid, but that leaves the
> > problem of "inf." not being recognised by other tools
> > (awk/printf).
> 
> 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.

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?

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...

Cheers,

DAniel


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-08 12:46   ` Daniel Shahaf
@ 2018-02-08 14:22     ` Stephane Chazelas
  2018-02-09 15:31       ` Daniel Shahaf
  2018-02-16 16:51     ` Oliver Kiddle
  1 sibling, 1 reply; 15+ messages in thread
From: Stephane Chazelas @ 2018-02-08 14:22 UTC (permalink / raw)
  To: Daniel Shahaf; +Cc: zsh-workers

2018-02-08 12:46:35 +0000, Daniel Shahaf:
[...]
> 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?
[...]

The idea is that we add "." for floats that don't already have
one and don't have a e/E to make sure they stay floats when used
again in an arithmetic expression. That causes all sorts of
problems with yash and ksh93 which don't do that. See
https://unix.stackexchange.com/a/422123 for a few examples.

It just looks like "inf" and "nan" were overlooked. Whether we
output "inf", "Inf" or "inf.", we'd also want to make sure
they're recognised on input.

> 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...

But what value would you use for those variables? Or do you mean
that the shell arithmetic parser would understand "inf"/"nan" as
the infinity and not-a-number numbers and not as the variable
(otherwise with inf=inf, that would do infinite recursion)
but just make inf/nan readonly so users not be tempted to use
them as variables?

I'd also rather "inf" be used instead of "inf." (would also
align with ksh93), but the change could potentially break some
scripts. If we also make $inf/$nan read-only variables, that
would  also break scripts that use $inf/$nan variables but not
in arithmetic contexts (like for inf in *.inf), so that doesn't
sound like a good idea to me.

-- 
Stephane


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-08 14:22     ` Stephane Chazelas
@ 2018-02-09 15:31       ` Daniel Shahaf
  2018-02-09 21:09         ` Stephane Chazelas
  0 siblings, 1 reply; 15+ messages in thread
From: Daniel Shahaf @ 2018-02-09 15:31 UTC (permalink / raw)
  To: Stephane Chazelas; +Cc: zsh-workers

Stephane Chazelas wrote on Thu, 08 Feb 2018 14:22 +0000:
> 2018-02-08 12:46:35 +0000, Daniel Shahaf:
> > 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...
> 
> But what value would you use for those variables?

'inf' would be positive infinity. 'nan' would be a NaN (whichever flavour
thereof would be least surprising).

> Or do you mean that the shell arithmetic parser would understand
> "inf"/"nan" as the infinity and not-a-number numbers and not as the
> variable (otherwise with inf=inf, that would do infinite recursion)

Why would it be recursive?

> but just make inf/nan readonly so users not be tempted to use them as
> variables?

That would be the simplest implementation, yes.  It might suffice, or we might
prefer something more elaborate.

> I'd also rather "inf" be used instead of "inf." (would also
> align with ksh93), but the change could potentially break some
> scripts. If we also make $inf/$nan read-only variables, that
> would  also break scripts that use $inf/$nan variables but not
> in arithmetic contexts (like for inf in *.inf), so that doesn't
> sound like a good idea to me.

Yes, that would be backwards incompatible.  If we choose this path,
those people can use 'for inf in...' use 'local inf' or 'local -h inf'
to hide the float inf/nan.

One option that is compatible is to make 'inf' and 'nan' only declared
on an opt-in basis --- using some autoload/zmodload, or perhaps with
some "This script requires zsh 5.5 or greater" directive.

Cheers,

Daniel


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-09 15:31       ` Daniel Shahaf
@ 2018-02-09 21:09         ` Stephane Chazelas
  2018-02-10  0:10           ` Bart Schaefer
  0 siblings, 1 reply; 15+ messages in thread
From: Stephane Chazelas @ 2018-02-09 21:09 UTC (permalink / raw)
  To: Daniel Shahaf; +Cc: zsh-workers

2018-02-09 15:31:12 +0000, Daniel Shahaf:
> Stephane Chazelas wrote on Thu, 08 Feb 2018 14:22 +0000:
> > 2018-02-08 12:46:35 +0000, Daniel Shahaf:
> > > 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...
> > 
> > But what value would you use for those variables?
> 
> 'inf' would be positive infinity. 'nan' would be a NaN (whichever flavour
> thereof would be least surprising).
> 
> > Or do you mean that the shell arithmetic parser would understand
> > "inf"/"nan" as the infinity and not-a-number numbers and not as the
> > variable (otherwise with inf=inf, that would do infinite recursion)
> 
> Why would it be recursive?

I mean as in:

$ inf=inf zsh -c 'echo $((inf))'
zsh:1: math recursion limit exceeded: inf

a variable name in arithmetic expression is expanded and its
value subject to arithmetic evaluation and so on.

There's always the option of defining them as:

$ nan='(-(1e9999/1e9999))' inf='(1e9999)' zsh -c 'echo $((nan)) $((inf))'
nan. inf.

(I don't know why I need the "-" there, it's the same in ksh93)

> 
> > but just make inf/nan readonly so users not be tempted to use them as
> > variables?
> 
> That would be the simplest implementation, yes.  It might suffice, or we might
> prefer something more elaborate.

I'm not sure I see the benefit of preventing users using from
using variables called "inf" or "nan". It seems to me that
ksh93's approach at just treating "inf" and "nan" as literral
constants representing their value like in C would be enough
(which would in effect prevent users from using variables by the
same name inside but not outside arithmetic expression (though
they'd still be able to do $(($inf)))).

-- 
Stephane


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-09 21:09         ` Stephane Chazelas
@ 2018-02-10  0:10           ` Bart Schaefer
  0 siblings, 0 replies; 15+ messages in thread
From: Bart Schaefer @ 2018-02-10  0:10 UTC (permalink / raw)
  To: zsh-workers

On Fri, Feb 9, 2018 at 1:09 PM, Stephane Chazelas
<stephane.chazelas@gmail.com> wrote:
> It seems to me that
> ksh93's approach at just treating "inf" and "nan" as literral
> constants representing their value like in C would be enough

Agree.  We could also recognize "inf." and "nan." as constants so that
any scripts that explicitly compare for those wouldn't break, but I
have no argument with not generating the trailing dot on output.


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-08 12:46   ` Daniel Shahaf
  2018-02-08 14:22     ` Stephane Chazelas
@ 2018-02-16 16:51     ` Oliver Kiddle
  2018-02-17  0:38       ` Daniel Shahaf
                         ` (2 more replies)
  1 sibling, 3 replies; 15+ messages in thread
From: Oliver Kiddle @ 2018-02-16 16:51 UTC (permalink / raw)
  To: zsh-workers

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 \


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-16 16:51     ` Oliver Kiddle
@ 2018-02-17  0:38       ` Daniel Shahaf
  2018-02-19 14:19         ` Stephane Chazelas
  2018-02-27 13:02       ` Vincent Lefevre
  2018-03-21 23:46       ` Oliver Kiddle
  2 siblings, 1 reply; 15+ messages in thread
From: Daniel Shahaf @ 2018-02-17  0:38 UTC (permalink / raw)
  To: zsh-workers

Oliver Kiddle wrote on Fri, 16 Feb 2018 17:51 +0100:
> > 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.

After 42356 I am not sure whether I would prefer predefined variables
(readonly or not) or recognising 'inf' and 'nan' (putting aside the
question of case for a moment) as special constants in math contexts
as 42356 suggests.

> @@ -791,6 +822,21 @@ zzlex(void)
>  	    break;
>  	/* Fall through! */

Unrelated to the patch: that comment is incorrect.

>  	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 == '#') {


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-17  0:38       ` Daniel Shahaf
@ 2018-02-19 14:19         ` Stephane Chazelas
  0 siblings, 0 replies; 15+ messages in thread
From: Stephane Chazelas @ 2018-02-19 14:19 UTC (permalink / raw)
  To: Daniel Shahaf; +Cc: zsh-workers

2018-02-17 00:38:52 +0000, Daniel Shahaf:
> Oliver Kiddle wrote on Fri, 16 Feb 2018 17:51 +0100:
> > > 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.
> 
> After 42356 I am not sure whether I would prefer predefined variables
> (readonly or not) or recognising 'inf' and 'nan' (putting aside the
> question of case for a moment) as special constants in math contexts
> as 42356 suggests.
[...]

A note on top of what I said earlier on that. In ksh93, from a
syntax point of view, it does look a bit like ksh93 treats
"inf"/"nan" (with any variation of case) like if it was a
variable whose value resolved to infinity as seen when we use
inf[0] (but not ${inf[0]}) in arithmetic contexts:

$ ksh93 -c 'InF=(2 3); echo "$((InF[0])) $((${InF[0]}))"'
inf 2

Or with:

$ ksh93 -c 'inf=2; echo "$((inf = 1, inf))"'
ksh93: Inf: is read only

But:

$ ksh93 -c 'echo "$((${Inf=2}))"'
2
$ ksh93 -c 'echo "$((${Inf=2}, Inf))"'
inf

That does look bad to me.

I still think it doesn't make sense to treat inf/nan as
variables.

-- 
Stephane


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-16 16:51     ` Oliver Kiddle
  2018-02-17  0:38       ` Daniel Shahaf
@ 2018-02-27 13:02       ` Vincent Lefevre
  2018-02-27 15:25         ` Oliver Kiddle
  2018-03-21 23:46       ` Oliver Kiddle
  2 siblings, 1 reply; 15+ messages in thread
From: Vincent Lefevre @ 2018-02-27 13:02 UTC (permalink / raw)
  To: zsh-workers

On 2018-02-16 17:51:15 +0100, Oliver Kiddle wrote:
> On 8 Feb, Daniel Shahaf wrote:
> > 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.

What do you mean by "We generate whatever printf(3) generates."?

On Debian/unstable:

cventin% echo $((1e9999))
inf.
cventin% printf "%g\n" 1e9999
inf
cventin% /usr/bin/printf "%g\n" 1e9999
/usr/bin/printf: ‘1e9999’: Numerical result out of range
inf

-- 
Vincent Lefèvre <vincent@vinc17.net> - Web: <https://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <https://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / AriC project (LIP, ENS-Lyon)


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-27 13:02       ` Vincent Lefevre
@ 2018-02-27 15:25         ` Oliver Kiddle
  2018-02-27 16:56           ` Vincent Lefevre
  0 siblings, 1 reply; 15+ messages in thread
From: Oliver Kiddle @ 2018-02-27 15:25 UTC (permalink / raw)
  To: zsh-workers

Vincent Lefevre wrote:
> > 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.
>
> What do you mean by "We generate whatever printf(3) generates."?

Zsh calls printf(3) to do the work underneath. The following code:
  #include <stdio.h>
  int main() { printf("%g\n", 1e9999); }
Will output "Inf" on Solaris and "inf" on Linux (for glibc at least).
And the zsh printf builtin does likewise on each platform.

> On Debian/unstable:
>
> cventin% echo $((1e9999))
> inf.

That trailing . is unrelated. It is added to ensure floating point types
remain floating point which is not applicable to Inf and NaN.

> cventin% printf "%g\n" 1e9999
> cventin% /usr/bin/printf "%g\n" 1e9999

How 1e9999 is interpreted a different matter. Zsh first tries strtod()
then full zsh math evaluation. So printf "%g\n" 3+4 works in zsh but not
coreutils' printf.

I do intend to get the patch cleaned up and a follow-up submitted but it
has been a busy time.

Oliver


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-27 15:25         ` Oliver Kiddle
@ 2018-02-27 16:56           ` Vincent Lefevre
  0 siblings, 0 replies; 15+ messages in thread
From: Vincent Lefevre @ 2018-02-27 16:56 UTC (permalink / raw)
  To: zsh-workers

On 2018-02-27 16:25:47 +0100, Oliver Kiddle wrote:
> Vincent Lefevre wrote:
> > > 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.
> >
> > What do you mean by "We generate whatever printf(3) generates."?
> 
> Zsh calls printf(3) to do the work underneath. The following code:
>   #include <stdio.h>
>   int main() { printf("%g\n", 1e9999); }
> Will output "Inf" on Solaris and "inf" on Linux (for glibc at least).
> And the zsh printf builtin does likewise on each platform.

Solaris is broken. The ISO C99 and C11 standards say:

  A double argument representing an infinity is converted in one of the
  styles [-]inf or [-]infinity — which style is implementation-defined.
  A double argument representing a NaN is converted in one of the styles
  [-]nan or [-]nan(n-char-sequence) — which style, and the meaning of
  any n-char-sequence, is implementation-defined. The F conversion
  specifier produces INF, INFINITY, or NAN instead of inf, infinity, or
  nan, respectively.

Thus for the above example, the correct outputs are "inf" and
"infinity".

> > On Debian/unstable:
> >
> > cventin% echo $((1e9999))
> > inf.
> 
> That trailing . is unrelated. It is added to ensure floating point types
> remain floating point which is not applicable to Inf and NaN.

OK, so whatever printf(3) generates and a trailing ".".

-- 
Vincent Lefèvre <vincent@vinc17.net> - Web: <https://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <https://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / AriC project (LIP, ENS-Lyon)


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: inf and nan in arithmetic expansions
  2018-02-16 16:51     ` Oliver Kiddle
  2018-02-17  0:38       ` Daniel Shahaf
  2018-02-27 13:02       ` Vincent Lefevre
@ 2018-03-21 23:46       ` Oliver Kiddle
  2 siblings, 0 replies; 15+ messages in thread
From: Oliver Kiddle @ 2018-03-21 23:46 UTC (permalink / raw)
  To: zsh-workers

On 16 Feb, I wrote:
> 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.

The following patch goes on top of 42369 to cleanup some rough edges and
to add test cases.

It still treats Inf and NaN as special keywords in math context but they
are matched case-insensitively. "infinity" is not matched unlike
strtod() which printf still uses. You can't assign to them in math
context.

Following the ISO C99 standard quoted by Vincent, printf will output
lowercase form - "inf", "nan" etc even on e.g. Solaris. I've left it as
"Inf"/"NaN" for $((..)) but I don't care a great deal either way.

The actual bounds checking for math functions was removed in 42369 which
leaves the flags being rather superfluous. This removes all trace of
them. I checked all the functions and you do just get values like NaN
and -Inf in the cases that were blocked before.

I had to revert the division by zero checking for integer division by
zero.

Like ksh93, you'll get "Inf" as output from echo "$((${inf=2}, inf))"
I don't really see a problem with that. There seemed to be consensus
against making Inf a variable and for case-insensitivity so that comes
as a consequence.

I'm open to consider any aspect of the behaviour.
Should I hold off with committing this for now or is it ok to go?

Oliver

diff --git a/Src/Modules/mathfunc.c b/Src/Modules/mathfunc.c
index a62154c50..01a2913ef 100644
--- a/Src/Modules/mathfunc.c
+++ b/Src/Modules/mathfunc.c
@@ -93,22 +93,6 @@ MS_RAND48
  * conversion), atan2.
  */
 
-/* Flags for bounds.  Note these must start at 1, not 0. */
-
-enum {
-  BF_POS    = 1,		/* must be positive */
-  BF_NONNEG = 2,		/* must be non-negative */
-  BF_FRAC   = 3,		/* must be -1 <= x <= 1 */
-  BF_GE1    = 4,		/* must be >= 1 */
-  BF_FRACO  = 5,		/* must be in open range -1 < x < 1 */
-  BF_INTPOS = 6,		/* must be non-integer or positive */
-  BF_GTRM1  = 7,		/* must be > -1 */
-  BF_NONZ   = 8,		/* must be nonzero */
-  BF_POS2   = 9			/* second argument must be positive */
-};
-
-#define BFLAG(x) ((x) << 8)
-
 /*
  * Flags for type of function: unlike the above, these must
  * be individually bit-testable.
@@ -121,18 +105,18 @@ enum {
     TF_NOASS  = 8		/* don't assign result as double */
 };
 
-#define TFLAG(x) ((x) << 16)
+#define TFLAG(x) ((x) << 8)
 
 
 static struct mathfunc mftab[] = {
-  NUMMATHFUNC("abs", math_func, 1, 1, MF_ABS | BFLAG(BF_FRAC) |
+  NUMMATHFUNC("abs", math_func, 1, 1, MF_ABS |
 	      TFLAG(TF_NOCONV|TF_NOASS)),
-  NUMMATHFUNC("acos", math_func, 1, 1, MF_ACOS | BFLAG(BF_FRAC)),
-  NUMMATHFUNC("acosh", math_func, 1, 1, MF_ACOSH | BFLAG(BF_GE1)),
-  NUMMATHFUNC("asin", math_func, 1, 1, MF_ASIN | BFLAG(BF_FRAC)),
+  NUMMATHFUNC("acos", math_func, 1, 1, MF_ACOS),
+  NUMMATHFUNC("acosh", math_func, 1, 1, MF_ACOSH),
+  NUMMATHFUNC("asin", math_func, 1, 1, MF_ASIN),
   NUMMATHFUNC("asinh", math_func, 1, 1, MF_ASINH),
   NUMMATHFUNC("atan", math_func, 1, 2, MF_ATAN),
-  NUMMATHFUNC("atanh", math_func, 1, 1, MF_ATANH | BFLAG(BF_FRACO)),
+  NUMMATHFUNC("atanh", math_func, 1, 1, MF_ATANH),
   NUMMATHFUNC("cbrt", math_func, 1, 1, MF_CBRT),
   NUMMATHFUNC("ceil", math_func, 1, 1, MF_CEIL),
   NUMMATHFUNC("copysign", math_func, 2, 2, MF_COPYSIGN),
@@ -146,20 +130,19 @@ static struct mathfunc mftab[] = {
   NUMMATHFUNC("float", math_func, 1, 1, MF_FLOAT),
   NUMMATHFUNC("floor", math_func, 1, 1, MF_FLOOR),
   NUMMATHFUNC("fmod", math_func, 2, 2, MF_FMOD),
-  NUMMATHFUNC("gamma", math_func, 1, 1, MF_GAMMA | BFLAG(BF_INTPOS)),
+  NUMMATHFUNC("gamma", math_func, 1, 1, MF_GAMMA),
   NUMMATHFUNC("hypot", math_func, 2, 2, MF_HYPOT),
-  NUMMATHFUNC("ilogb", math_func, 1, 1, MF_ILOGB | BFLAG(BF_NONZ) |
-	      TFLAG(TF_NOASS)),
+  NUMMATHFUNC("ilogb", math_func, 1, 1, MF_ILOGB | TFLAG(TF_NOASS)),
   NUMMATHFUNC("int", math_func, 1, 1, MF_INT | TFLAG(TF_NOASS)),
   NUMMATHFUNC("j0", math_func, 1, 1, MF_J0),
   NUMMATHFUNC("j1", math_func, 1, 1, MF_J1),
   NUMMATHFUNC("jn", math_func, 2, 2, MF_JN | TFLAG(TF_INT1)),
   NUMMATHFUNC("ldexp", math_func, 2, 2, MF_LDEXP | TFLAG(TF_INT2)),
-  NUMMATHFUNC("lgamma", math_func, 1, 1, MF_LGAMMA | BFLAG(BF_INTPOS)),
-  NUMMATHFUNC("log", math_func, 1, 1, MF_LOG | BFLAG(BF_POS)),
-  NUMMATHFUNC("log10", math_func, 1, 1, MF_LOG10 | BFLAG(BF_POS)),
-  NUMMATHFUNC("log1p", math_func, 1, 1, MF_LOG1P | BFLAG(BF_GTRM1)),
-  NUMMATHFUNC("logb", math_func, 1, 1, MF_LOGB | BFLAG(BF_NONZ)),
+  NUMMATHFUNC("lgamma", math_func, 1, 1, MF_LGAMMA),
+  NUMMATHFUNC("log", math_func, 1, 1, MF_LOG),
+  NUMMATHFUNC("log10", math_func, 1, 1, MF_LOG10),
+  NUMMATHFUNC("log1p", math_func, 1, 1, MF_LOG1P),
+  NUMMATHFUNC("logb", math_func, 1, 1, MF_LOGB),
   NUMMATHFUNC("nextafter", math_func, 2, 2, MF_NEXTAFTER),
 #ifdef HAVE_ERAND48
   STRMATHFUNC("rand48", math_string, MS_RAND48),
@@ -171,12 +154,12 @@ static struct mathfunc mftab[] = {
 #endif
   NUMMATHFUNC("sin", math_func, 1, 1, MF_SIN),
   NUMMATHFUNC("sinh", math_func, 1, 1, MF_SINH),
-  NUMMATHFUNC("sqrt", math_func, 1, 1, MF_SQRT | BFLAG(BF_NONNEG)),
+  NUMMATHFUNC("sqrt", math_func, 1, 1, MF_SQRT),
   NUMMATHFUNC("tan", math_func, 1, 1, MF_TAN),
   NUMMATHFUNC("tanh", math_func, 1, 1, MF_TANH),
-  NUMMATHFUNC("y0", math_func, 1, 1, MF_Y0 | BFLAG(BF_POS)),
-  NUMMATHFUNC("y1", math_func, 1, 1, MF_Y1 | BFLAG(BF_POS)),
-  NUMMATHFUNC("yn", math_func, 2, 2, MF_YN | BFLAG(BF_POS2) | TFLAG(TF_INT1))
+  NUMMATHFUNC("y0", math_func, 1, 1, MF_Y0),
+  NUMMATHFUNC("y1", math_func, 1, 1, MF_Y1),
+  NUMMATHFUNC("yn", math_func, 2, 2, MF_YN | TFLAG(TF_INT1))
 };
 
 /**/
diff --git a/Src/builtin.c b/Src/builtin.c
index fb59738f3..3852e7c52 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -5236,8 +5236,14 @@ bin_print(char *name, char **args, Options ops, int func)
 			    errflag &= ~ERRFLAG_ERROR;
 			    ret = 1;
 			}
-			print_val(doubleval)
-			    break;
+			/* force consistent form for Inf/NaN output */
+			if (isnan(doubleval))
+			    count += fputs("nan", fout);
+			else if (isinf(doubleval))
+			    count += fputs((doubleval < 0.0) ? "-inf" : "inf", fout);
+		        else
+			    print_val(doubleval)
+			break;
 		    case 3:
 #ifdef ZSH_64_BIT_UTYPE
  		    	*d++ = 'l';
diff --git a/Src/math.c b/Src/math.c
index cdfe80bb4..32bccc6e9 100644
--- a/Src/math.c
+++ b/Src/math.c
@@ -593,7 +593,6 @@ isinf(double x)
 #endif
 
 #if !defined(HAVE_ISNAN)
-/**/
 static double
 store(double *x)
 {
@@ -816,27 +815,11 @@ zzlex(void)
 		ptr++;
 		break;
 	    }
-	case ' ':
+	case ' ': /* Fall through! */
 	case '\t':
 	case '\n':
 	    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 == '#') {
@@ -860,6 +843,20 @@ zzlex(void)
 
 		p = ptr;
 		ptr = ie;
+		if (ie - p == 3) {
+		    if (strncasecmp(p, "NaN", 3) == 0) {
+			yyval.type = MN_FLOAT;
+			yyval.u.d = 0.0;
+			yyval.u.d /= yyval.u.d;
+			return NUM;
+		    }
+		    else if (strncasecmp(p, "Inf", 3) == 0) {
+			yyval.type = MN_FLOAT;
+			yyval.u.d = 0.0;
+			yyval.u.d = 1.0 / yyval.u.d;
+			return NUM;
+		    }
+		}
 		if (*ptr == '[' || (!cct && *ptr == '(')) {
 		    char op = *ptr, cp = ((*ptr == '[') ? ']' : ')');
 		    int l;
@@ -1114,6 +1111,10 @@ callmathfunc(char *o)
 static int
 notzero(mnumber a)
 {
+    if ((a.type & MN_INTEGER) && a.u.l == 0) {
+        zerr("division by zero");
+        return 0;
+    }
     return 1;
 }
 
diff --git a/Test/B03print.ztst b/Test/B03print.ztst
index c65568ad9..0ef3743ce 100644
--- a/Test/B03print.ztst
+++ b/Test/B03print.ztst
@@ -86,6 +86,17 @@
 >123.45 678
 >90.1 0
 
+ nan=0 inf=1 Infinity=2
+ printf '%.1f\n' -inf Infinity Inf nan NaN -Inf -0.0
+0:infinity constants
+>-inf
+>inf
+>inf
+>nan
+>nan
+>-inf
+>-0.0
+
  print -f 'arg: %b\n' -C2 '\x41' '\x42' '\x43'
 0:override -C when -f was given
 >arg: A
diff --git a/Test/C01arith.ztst b/Test/C01arith.ztst
index 30409adf3..77a46ebd5 100644
--- a/Test/C01arith.ztst
+++ b/Test/C01arith.ztst
@@ -302,6 +302,40 @@
 ?(eval):1: bad math expression: operator expected at `2 '
 # ` for emacs shell mode
 
+  in=1 info=2 Infinity=3 Inf=4
+  print $(( in )) $(( info )) $(( Infinity )) $(( $Inf )) $(( inf )) $(( INF )) $(( Inf )) $(( iNF ))
+0:Infinity parsing
+>1 2 3 4 Inf Inf Inf Inf
+
+  integer Inf
+  print $(( Inf[0] ))
+1:Refer to Inf with an array subscript
+?(eval):2: bad base syntax
+
+  (( NaN = 1 ))
+2:Assign to NaN
+?(eval):1: bad math expression: lvalue required
+
+  a='Inf'
+  (( b = 1e500 ))
+  print $((1e500)) $(($((1e500)))) $(( a )) $b $(( b )) $(( 3.0 / 0 ))
+0:Overflow to infinity
+>Inf Inf Inf Inf Inf Inf
+
+  print $((1e500))
+  print $(( $((1e500)) ))
+0:Reinput infinity value into math context
+>Inf
+>Inf
+
+  print $((1e500/1e500)) $((-1e500/1e500)) $(( 24. % 0 ))
+0:NaN results
+>NaN NaN NaN
+
+  (( 3 / 0 ))
+2:Integer division by zero
+?(eval):1: division by zero
+
   integer varassi
   print $(( varassi = 5.5 / 2.0 ))
   print $varassi
diff --git a/Test/V03mathfunc.ztst b/Test/V03mathfunc.ztst
index 1edb7a279..d6b4e0987 100644
--- a/Test/V03mathfunc.ztst
+++ b/Test/V03mathfunc.ztst
@@ -100,8 +100,8 @@ F:This test fails if your math library doesn't have erand48().
 >1.50000
 
    print $(( sqrt(-1) ))
-1:Non-negative argument checking for square roots.
-?(eval):1: math: argument to sqrt out of range
+0:Non-negative argument checking for square roots.
+>NaN
 
 # Simple test that the pseudorandom number generators are producing
 # something that could conceivably be pseudorandom numbers in a


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2018-03-21 23:53 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-02-07 22:30 inf and nan in arithmetic expansions Stephane Chazelas
2018-02-07 23:25 ` Oliver Kiddle
2018-02-08  9:38   ` Peter Stephenson
2018-02-08 12:46   ` Daniel Shahaf
2018-02-08 14:22     ` Stephane Chazelas
2018-02-09 15:31       ` Daniel Shahaf
2018-02-09 21:09         ` Stephane Chazelas
2018-02-10  0:10           ` Bart Schaefer
2018-02-16 16:51     ` Oliver Kiddle
2018-02-17  0:38       ` Daniel Shahaf
2018-02-19 14:19         ` Stephane Chazelas
2018-02-27 13:02       ` Vincent Lefevre
2018-02-27 15:25         ` Oliver Kiddle
2018-02-27 16:56           ` Vincent Lefevre
2018-03-21 23:46       ` Oliver Kiddle

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).