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