Comment faire en sorte que bc gère les nombres en notation scientifique (aka exponentielle)?

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

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

Mais je dois l'utiliser pour gérer quelques enregistrements qui sont exprimés dans cette notation. Existe-t-il un moyen d'obtenir bc pour comprendre la notation exponentielle? Sinon, que puis-je faire pour les traduire dans un format que bc comprendra?

29
demandé sur mklement0 2012-10-14 17:19:01

8 réponses

Malheureusement, bc ne supporte pas la notation scientifique.

Cependant, il peut être traduit dans un format que bc peut gérer, en utilisant regex étendu 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^", 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 avez besoin de s'en tenir à l'expression rationnelle de base (BRE), alors cela devrait être utilisé:

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"

Des Commentaires:

  • Un simple Bash pattern match n'a pas pu fonctionner (merci @mklement0 ) car il n'y a aucun moyen de faire correspondre un e+ et de garder le - d'un 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, comme bien que @isaac et @mklement0 pour l'amélioration de la réponse.

Modifier:

La réponse a beaucoup changé au fil des ans. La réponse ci-dessus est la dernière itération à partir du 17 Mai 2018. Les tentatives précédentes signalées ici étaient une solution en bash pur (par @ormaaj) et une solution en sed (par @ me ), qui échouent dans au moins certains cas. Je vais les garder ici juste pour donner un sens aux commentaires, qui contiennent des explications beaucoup plus agréables des subtilités de tout cela que cette réponse ne.

value=${value/[eE]+*/*10^}  ------> Can not work.
value=`echo ${value} | sed -e 's/[eE]+*/\\*10\\^/'` ------> Fail in some conditions
25
répondu Ferdinando Randisi 2018-05-18 20:49:27

Laissez-moi essayer de résumer la les réponses existantes, avec commentaires ci-dessous:

  • (a) Si vous avez en effet besoin d'utiliser bc pour arbitraire-précision des calculs - que l'OP n' - utiliser le OP propre approche astucieuse, ce qui textuellement reformate la notation scientifique d'un expression équivalente que bc comprend.

  • Si potentiellement perdre précision est pas une préoccupation,

    • (B) envisager d'utiliser awk ou perl comme bc alternatives ; les deux comprennent nativement la notation scientifique, comme démontré dans la réponse de {[91] jwpat7 pour awk.
    • (C) envisagez d'utiliser printf '%.<precision>f' pour simplement convertissez textuellement en une représentation en virgule flottante régulière (fractions décimales, sans e/E) (une solution proposée dans un post supprimé depuis par ormaaj).

A) reformatage de la notation scientifique en un équivalent bc expression

L'avantage de cette solution est que la précision est préservée: la représentation textuelle est transformée en une représentation textuelleéquivalente que bc peut comprendre, et bc elle-même est capable de calculs de précision arbitraire.

Voir la propre réponse de OP , dont la forme mise à jour est maintenant capable de transformer une expression entière contenant plusieurs nombres en notation exponentielle en une expression bc équivalente.


(B) en utilisant awk ou perl au lieu de bc comme calculatrice

Remarque: Les approches suivantes supposent l'utilisation du support intégré pour les valeurs à virgule flottante à double précision dans awk et perl. Comme cela est inhérent à l'arithmétique à virgule flottante,
"étant donné un nombre fixe de bits, la plupart des calculs avec réel les nombres produiront des quantités qui ne peuvent pas être exactement représentées en utilisant autant de bits. Par conséquent, le résultat d'un calcul à virgule flottante doit souvent être arrondi afin de s'intégrer dans sa représentation finie. Cette erreur d'arrondi est la caractéristique du calcul en virgule flottante."(http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)

Cela dit,

  • GNU awk offre la option pour être construit avec prise en charge de l'arithmétique de précision arbitraire-voir https://www.gnu.org/software/gawk/manual/html_node/Gawk-and-MPFR.html ; cependant, les distributions peuvent ou non inclure ce support support - verify en vérifiant la sortie de gawk --version pour GNU MPFR et GNU MP.
    Si la prise en charge est disponible, vous devez l'activer avec -M (--bignum) dans une invocation.

  • Perl offre facultatif arbitraire-précision décimale prise en charge via le paquet Math::BigFloat - voir https://metacpan.org/pod/Math::BigFloat

Awk

awk comprend nativement la notation exponentielle décimale (scientifique).
(Vous ne devez généralement utiliser que la représentation decimal , car les implémentations awk diffèrent quant à savoir si elles prennent en charge les littéraux numériques avec d'autres bases.)

awk 'BEGIN { print 3.1e1 * 2 }'  # -> 62

Si vous utilisez la fonction par défaut print, la variable OFMT contrôle le format de sortie au moyen d'une chaîne de format printf; la valeur par défaut (POSIX-mandated) est %.6g, ce qui signifie 6 chiffres significatifs , qui notamment inclut les chiffres dans la partie entière.

Notez que si le nombre en notation scientifique est fourni comme input (par opposition à une partie littérale du programme awk) , vous devez ajouter +0 pour le forcer au format de sortie par défaut, s'il est utilisé par lui-même avec print:

Selon vos paramètres régionaux et l'implémentation awk que vous utilisez, vous devrez peut-être remplacer le point décimal (.) avec le caractère radix approprié aux paramètres régionaux, tel que , dans un paramètre régional allemand; s'applique à BSD awk, mawk, et à GNU awk avec l'option --posix.

awk '{ print $1+0 }' <<<'3.1e1' # -> 31; without `+0`, output would be the same as input

Modification de la variable OFMT modifie le format de sortie par défaut (pour les nombres avec des parties fractionnaires; les entiers (effectifs) sont toujours produits en tant que tels).
Vous pouvez également utiliser le printf fonction avec un format de sortie explicite:

awk 'BEGIN { printf "%.4f", 3.1e1 * 2.1234 }' # -> 65.8254

Perl

perl comprend trop nativement la notation exponentielle décimale (scientifique).

Remarque: Perl, contrairement à awk, n'est pas disponible sur toutes les plates-formes de type POSIX par défaut; de plus, il n'est pas aussi léger que awk.
Cependant, il offre plus de fonctionnalités que awk, telles que la compréhension native hexadécimale et octale les entiers.

perl -le 'print 3.1e1 * 2'  # -> 62

Je ne sais pas quel est le format de sortie par défaut de Perl, mais il semble l'être %.15g. Comme avec awk, vous pouvez utiliser printf choisir le format de sortie souhaité:

perl -e 'printf "%.4f\n", 3.1e1 * 2.1234' # -> 65.8254

(c) en utilisant printf pour convertir la notation scientifique en fractions décimales

Si vous voulez simplement convertir la notation scientifique (par exemple, 1.2e-2) en une fraction décimale (par exemple, 0.012), printf '%f' je peux le faire pour toi. Notez que vous allez convertir un textuel représentation dans un autre via arithmétique à virgule flottante, qui est soumis à l' même les erreurs d'arrondi comme le awk et perl approches.

printf '%.4f' '1.2e-2' # -> '0.0120'; `.4` specifies 4 decimal digits.
13
répondu mklement0 2018-05-19 02:52:16

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 d'awk %.6G) sortie comme
12.3457 3.14159 543210000000000000
alors que les commandes comme les deux suivantes produisent la sortie affichée après chacune, étant donné que le fichier edata contient des 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

En outre, en ce qui concerne les solutions utilisant sed, il est probablement préférable de supprimer le signe plus dans des formes 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, 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
répondu James Waldby - jwpat7 2012-10-14 16:15:08

, 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
}

Ensuite, vous pouvez utiliser tous les types de mathématiques à 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 du bash par des guillemets.

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

Ou dans un script pour attribuer le résultat

a=$(= 1+sin[4])
echo $a   # 0.243198
6
répondu Jo H 2013-10-08 15:25:09

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 flottante:

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
répondu Fridtjof Stein 2016-03-31 13:30:22

Essayez ceci (trouvé ceci dans un exemple pour une donnée D'entrée CFD pour le traitement avec m4:)

T0=4e-5
deltaT=2e-6
m4 <<< "esyscmd(perl -e 'printf (${T0} + ${deltaT})')"
0
répondu Ma-tri-x 2013-11-15 12:44:39

Essayez ceci: (en utilisant bash)

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

Ou ceci:

 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 l'utiliser:

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 gérerait tous les numéros lancés. Vous pouvez adapter le ' sed 'si vous avez des nombres avec' e ' ou ' E ' comme exposants.

Vous de choisir l'échelle que vous voulez.

0
répondu cpu 2014-10-23 18:19:18

Version Piping de OPs réponse acceptée

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

La Tuyauterie de l'entrée de la commande sed acceptée par OPs a donné des antislashs supplémentaires comme

$ echo 3.82955e-5 | sed 's/[eE]+*/\\*10\\^/'
3.82955\*10\^-5
0
répondu Anton 2018-04-13 09:08:34