web-dev-qa-db-fra.com

Comment obliger bc à gérer les nombres en notation scientifique (ou exponentielle)?

bc n'aime pas les nombres exprimés en notation scientifique (notation exponentielle).

$ echo "3.1e1*2" | bc -l
(standard_in) 1: parse error

mais je dois l’utiliser pour gérer quelques enregistrements exprimés dans cette notation. Est-il possible d'obtenir que bc comprenne la notation exponentielle? Sinon, que puis-je faire pour les traduire dans un format que bc comprendra?

30
Ferdinando Randisi

Malheureusement, bc ne supporte pas la notation scientifique.

Cependant, il peut être traduit dans un format que bc peut gérer, en utilisant extended regex selon POSIX dans sed:

sed -E 's/([+-]?[0-9.]+)[eE]\+?(-?)([0-9]+)/(\1*10^\2\3)/g' <<<"$value"

vous pouvez remplacer le "e" (ou "e +", si l'exposant est positif) par "* 10 ^", ce que bc comprendra rapidement. Cela fonctionne même si l'exposant est négatif ou si le nombre est ensuite multiplié par une autre puissance et permet de garder une trace des chiffres significatifs.

Si vous devez vous en tenir à une expression rationnelle de base (BRE), utilisez-le:

sed 's/\([+-]\{0,1\}[0-9]*\.\{0,1\}[0-9]\{1,\}\)[eE]+\{0,1\}\(-\{0,1\}\)\([0-9]\{1,\}\)/(\1*10^\2\3)/g' <<<"$value"

De commentaires:

  • Une simple correspondance bash pattern ne pourrait pas fonctionner (merci @ mklement0 ) car il n’existait aucun moyen de faire correspondre un e + et de conserver le - de e- en même temps.

  • Une solution Perl qui fonctionne correctement (merci @ mklement0 )

    $ Perl -pe 's/([-\d.]+)e(?:\+|(-))?(\d+)/($1*10^$2$3)/gi' <<<"$value"
    
  • Merci à @ jwpat7 et @Paul Tomblin pour avoir clarifié certains aspects de la syntaxe de sed, ainsi que @isaac et @ mklement0 pour avoir amélioré la réponse.

Modifier:

La réponse a changé un peu au fil des ans. La réponse ci-dessus est la dernière itération du 17 mai 2018. Les tentatives précédentes signalées ici étaient une solution en pure bash (par @ormaaj ) et une autre en sed (par @me ), qui échouent à moins certains cas. Je les garderai ici juste pour donner un sens aux commentaires, qui contiennent des explications bien plus jolies sur les subtilités de tout cela que cette réponse.

value=${value/[eE]+*/*10^}  ------> Can not work.
value=`echo ${value} | sed -e 's/[eE]+*/\\*10\\^/'` ------> Fail in some conditions
26
Ferdinando Randisi

On peut utiliser awk pour cela; par exemple,

awk '{ print +$1, +$2, +$3 }' <<< '12345678e-6 0.0314159e2 54321e+13'

produit (via le format par défaut de awk% .6g) une sortie comme
12.3457 3.14159 543210000000000000
Les commandes while, comme les deux suivantes, produisent la sortie affichée après chacune, étant donné que le fichier edata contient les données comme indiqué plus tard.

$ awk '{for(i=1;i<=NF;++i)printf"%.13g ",+$i; printf"\n"}' < edata`
31 0.0312 314.15 0 
123000 3.1415965 7 0.04343 0 0.1 
1234567890000 -56.789 -30 

$ awk '{for(i=1;i<=NF;++i)printf"%9.13g ",+$i; printf"\n"}' < edata
       31    0.0312    314.15         0 
   123000 3.1415965         7   0.04343         0       0.1 
1234567890000   -56.789       -30 


$ cat edata 
3.1e1 3.12e-2 3.1415e+2 xyz
123e3 0.031415965e2 7 .4343e-1 0e+0 1e-1
.123456789e13 -56789e-3 -30

De même, en ce qui concerne les solutions utilisant sed, il est probablement préférable de supprimer les formes de signe plus comme 45e+3 en même temps que e, via regex [eE]+*, plutôt que dans une expression sed séparée. Par exemple, sur ma machine Linux avec GNU sed version 4.2.1 et bash version 4.2.24, les commandes
sed 's/[eE]+*/*10^/g' <<< '7.11e-2 + 323e+34'
sed 's/[eE]+*/*10^/g' <<< '7.11e-2 + 323e+34' | bc -l
produire une sortie
7.11*10^-2 + 323*10^34
3230000000000000000000000000000000000.07110000000000000000

11

Vous pouvez également définir une fonction bash qui appelle awk (un bon nom serait le signe égal "="):

= ()
{
    local in="$(echo "$@" | sed -e 's/\[/(/g' -e 's/\]/)/g')";
    awk 'BEGIN {print '"$in"'}' < /dev/null
}

Vous pouvez ensuite utiliser tous les types de maths en virgule flottante dans le shell. Notez que les crochets sont utilisés ici au lieu des crochets ronds, car ces derniers devraient être protégés de la bash par des guillemets.

> = 1+sin[3.14159] + log[1.5] - atan2[1,2] - 1e5 + 3e-10
0.94182

Ou dans un script pour assigner le résultat 

a=$(= 1+sin[4])
echo $a   # 0.243198
6
Jo H

Heureusement, il y a printf, qui fait le travail de formatage:

L'exemple ci-dessus:

printf "%.12f * 2\n" 3.1e1 | bc -l

Ou une comparaison de float:

n=8.1457413437133669e-02
m=8.1456839223809765e-02

n2=`printf "%.12f" $n`
m2=`printf "%.12f" $m`

if [ $(echo "$n2 > $m2" | bc -l) == 1  ]; then 
   echo "n is bigger"
else
   echo "m is bigger"
fi
2
Fridtjof Stein

Essayez ceci: (en utilisant bash)

printf "scale=20\n0.17879D-13\n" | sed -e 's/D/*10^/' | bc

ou ca:

 num="0.17879D-13"; convert="`printf \"scale=20\n$num\n\" | sed -e 's/D/*10^/' | bc`" ; echo $convert
.00000000000001787900
num="1230.17879"; convert="`printf \"scale=20\n$num\n\" | sed -e 's/D/*10^/' | bc`" ; echo $convert
1230.17879

Si vous avez des exposants positifs, vous devriez utiliser ceci:

num="0.17879D+13"; convert="`printf \"scale=20\n$num\n\" | sed -e 's/D+/*10^/' -e 's/D/*10^/' | bc`" ; echo $convert
1787900000000.00000

Ce dernier traiterait tous les chiffres. Vous pouvez adapter le 'sed' si vous avez des nombres avec 'e' ou 'E' comme exposants.

Vous devez choisir l’échelle que vous voulez.

0
cpu

Version de tuyauterie des PO acceptés réponse

$ echo 3.82955e-5 | sed 's/[eE]+*/\*10\^/'
3.82955*10^-5

En transmettant l’entrée aux OP, la commande sed acceptée a généré des barres obliques inverses telles que

$ echo 3.82955e-5 | sed 's/[eE]+*/\\*10\\^/'
3.82955\*10\^-5
0
Anton

essayez ceci (trouvé dans un exemple de données d'entrée CFD à traiter avec m4 :)

T0=4e-5
deltaT=2e-6
m4 <<< "esyscmd(Perl -e 'printf (${T0} + ${deltaT})')"
0
Ma-tri-x

J'ai réussi à le faire avec un petit bidouillage. Vous pouvez faire quelque chose comme ça - 

scientific='4.8844221e+002'
base=$(echo $scientific | cut -d 'e' -f1)
exp=$(($(echo $scientific | cut -d 'e' -f2)*1))
converted=$(bc -l <<< "$base*(10^$exp)")
echo $converted 
>> 488.4422100
0
markroxor