last day (19 days later) » 

2:52 PM
1
A: bash + how to calculate percentage from number

isaacBash itself is unable to deal with floating point math. The best bet is to use bc like this: $ bc <<<"248*80/100" 198 The shell (bash,sh) is able to calculate only integers: $ bash -c 'echo $((248*80/100))' 198 The ksh93 is able to deal with floating point math: $ ksh -c 'echo $((248*0.8))...

 
Note that the difference between ksh93 and zsh is that ksh93 uses long doubles and zsh doubles. The difference between zsh and yash (another POSIX shell with floating point arithmetic support, which also uses doubles) is the precision on output. 198.4 as a double is actually something like 198.400000000000005684.
 
Full representation of a double precision float (IEEE-754) can be seen here or with echo 248 | awk -v CONVFMT=%.29g '{gsub($1, $1*0.8)}; {print}' which yields 198.40000000000000568434188608 (no more than ~16 digits are correct). However, the 32 bit representation of 198.4 is 198.399993896484375. That is, no more than ~7 decimal digits are correct, the rest is plain noise. The zsh shell should improve on its floating point representation.
 
feel free to suggest improvements to the zsh maintainers, from what I can tell, and I'm no expert on the subject, this kind of thing is not trivial. See ksh -c 'echo $((198.4 - 198))' or yash -c 'echo $((198.4 - 198))' which also show similar errors
 
Oh, yes, it is quite complex. However, this bit of knowledge is very simple: The smallest bit (which is bit 0) represents bit 0 = 0.00000011920928955078125 (From wikipedia). Please note that the zeros after the decimal point are 6, that hints the fact that no more than ~7 digits are usually correct. The shell zsh is printing 14 digits, that is over the recommended precision. That's it.
 
@issac, zsh uses doubles which are 64bit, long doubles are 128 bits. zsh rounds 198.400000000000005684341886080801486968994140625 to 198.40000000000001 which here causes that artefact.
 
2:52 PM
So, maybe, zsh should trim at 16 significant figures.
 
The thing is you'd want to make sure you don't lose precision when converting from the double to the decimal string representation (so that you get the same double back when converting back). According to wikipedia, it seems 17 digits are needed to get that guarantee.
 
At any length you make the cut, some numbers will not convert back correctly. There is no guarantee in floating point arithmetic.
 
Yes, here anyway the OP doesn't care. One could use printf '%.4g\n' '248*0.8' for instance (awk uses %.6g (default OFMT) which translates 198.0001 to 198 for instance).
Actually, compare x=$((1./3)); echo "$((x == 1./3))" in ksh, yash and zsh, and you'll see only zsh gets it right (even ksh93 -c 'typeset -F x=1./3; echo $((x == 1./3))' returns 0 even though you could think ksh would store the content of x as a long double there).
No, $((1 == 1)) expands to 1 because 1 and 1 are equal. Maybe you're confusing with ((1 == 1)) that exits with a 0 exit status (which means true as an exit status) because the 1 == 1 arithmetic expression evaluates to a non-0 number (which means true for an arithmetic expression). You'll find that echo "$((1 == 1))" or echo "$((1./3 == 1./3))" output 1 in all shells. But not echo "$(($((1./3)) == 1./3))" in yash and ksh93 because of the conversion back and forth to string using a not-completely-faithful representation.
Note that because ksh uses long doubles, it can't reasonably do it right because those long double having 80->128 bits of precision, that would mean outputting too large numbers for most people's tastes (36 digits with the quadruple-precision ones like 0.333333333333333333333333333333333333)
(continued). For the long doubles with 80bit precisions like the x86 ones however, one needs only 21 decimal digits. ksh93 is not too far off as it uses 18 digits, so it could make it right on those systems (possibly the most common ones).
 
A 80 bit precision Floating Point representation use 64 bits of mantisa. The least bit represents awk 'BEGIN{print(1/2^63)}' which yields 1.0842e-19 or roughly 19 decimal digits (not 21). Using 18 seems about right if using 80 bit floating point math.
 
@isaac Quoting wikipedia: if an 80-bit IEEE 754 binary floating point value is correctly converted and (nearest) rounded to a decimal string with at least 21 significant decimal digits then converted back to binary format it will exactly match the original.
There's a reference there to cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF with more details (see page 4)
 
 
2 hours later…
5:21 PM
Wikipedia is funny. In the same paragraph, just near the start: this format is usually described as giving approximately eighteen significant digits of precision. That is 18, not 21. Understand that in floating point representation more digits means less exactness. The digits beyond what is reasonable are just noise.
The paper states that Significant Digits are >=18-21. Again 18 sounds about right to trim the result to its more precise value. @StéphaneChazelas
 
5:39 PM
The workaround (for all shells) of the problem you report is to use ksh -c 'a=$((1./3));echo $(( a == $((1./3)) ))' which yields 1 in yash, ksh and zsh. @StéphaneChazelas
 
 
2 hours later…
7:24 PM
@isaac take the example of 1/3, there are dozens of different extended precision (80 bit) floating point number that have 0.333333333333333333 as their 18 digit decimal approximation. See for instance ksh -c 'printf "%a %.18g\n" $((1./3)) $((1./3)) 1./3 1./3 0x1.5555555555555518p-2 0x1.5555555555555518p-2 0x1.5555555555555562p-2 0x1.5555555555555562p-2'
1/3 itself is the one of those that is closest to 1.3. What W. Kahan (the father of floating point) tells us there is that if you take 21 digits, you're guaranteed to have only one corresponding extended precision number.
 
7:54 PM
A shorter string ksh -c 'printf "%a %.26g\n" $((1./3)){,} 1./3{,} 0x1.5555555555555518p-2{,} 0x1.5555555555555562p-2{,}'
However, ksh trims internally the representation: ksh -c 'printf "%a %.26g\n" 0x1.555555555555555555555555{,}'.
There is no simple way to debug this.
However there must be only one representation that results from binary 0x1.555555555555555555555555 if the implementation correctly match IEEE754.
 

  last day (19 days later) »