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?
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
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 quebc
comprend.-
Si potentiellement perdre précision est pas une préoccupation,
- (B) envisager d'utiliser
awk
ouperl
commebc
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, sanse
/E
) (une solution proposée dans un post supprimé depuis par ormaaj).
- (B) envisager d'utiliser
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
pourGNU MPFR
etGNU 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.
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 comme12.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, commandessed 's/[eE]+*/*10^/g' <<< '7.11e-2 + 323e+34'
sed 's/[eE]+*/*10^/g' <<< '7.11e-2 + 323e+34' | bc -l
produire une sortie7.11*10^-2 + 323*10^34
3230000000000000000000000000000000000.07110000000000000000
, 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
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
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})')"
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.
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