Как получить bc для обработки чисел в научной (ака экспоненциальной) нотации?

bc не любит числа, выраженные в научной нотации (он же экспоненциальная нотация).

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

но мне нужно, чтобы использовать его для обработки нескольких записей, которые выражены в этих обозначениях. Есть ли способ получить bc чтобы понять экспоненциальную нотацию? Если нет, то что я могу сделать, чтобы перевести их в формат, который bc поймет?

8 ответов


к сожалению, bc не поддерживает научную нотацию.

однако он может быть переведен в формат, который bc может обрабатывать, используя расширенное регулярное выражение согласно POSIX в sed:

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

вы можете заменить "e "(или" e+", если показатель положителен) на" *10^", который bc быстро поймет. Это работает, даже если показатель отрицательный или если число впоследствии умножается на другую степень, и позволяет отслеживать значительные десятичные знаки.

Если вам нужно придерживаться основного регулярного выражения( BRE), то это должно использоваться:

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

Из Комментариев:

  • простой Баш шаблон матч не может работать (спасибо @mklement0), поскольку нет способа сопоставить e+ и одновременно сохранить - от e -.

  • правильно работающее решение perl (спасибо @mklement0)

    $ perl -pe 's/([-\d.]+)e(?:\+|(-))?(\d+)/(*10^)/gi' <<<"$value"
    
  • спасибо @jwpat7 и @Paul Tomblin для уточнения аспектов синтаксиса sed, а также @isaac и @mklement0 для улучшения ответа.

Edit:

ответ несколько изменился с годами. Ответ выше-последняя итерация по состоянию на 17 мая 2018 года. Предыдущие попытки, о которых сообщалось здесь, были решением в pure bash (by @ormaaj) и один в sed (by @me), что удастся, по крайней мере в некоторых случаях. Я буду держать их здесь, чтобы понять комментарии, которые содержат гораздо более приятные объяснения тонкостей всего этого, чем этот ответ.

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

позвольте мне попробовать подведем итоги существующие ответы, с комментарии к каждому ниже:

  • (a) если вам действительно нужно использовать bc на произвольные-точность расчетов - как OP делает-используйте собственный умный подход OP, который текстуально переформатирует научную нотацию в эквивалентное выражение это bc поймет.

  • если потенциально теряя точность is не забота,

    • (b) рассмотрите возможность использования awk или perl as bc варианты; оба изначально понимают научную нотацию, как показано в jwpat7 это ответ для awk.
    • (c) рассмотрите возможность использования printf '%.<precision>f' просто текстуально преобразования в регулярное представление с плавающей запятой (десятичные дроби, без e/E) (решение, предложенное в удаленном посте ormaaj).

(a) переформатирование научной нотации в эквивалент bc выражение

преимущество этого решения в том, что точность сохраняется: текстовое представление преобразуется в эквивалентно текстовое представление, что bc могу понять, а bc сам способен на вычисления произвольной точности.

посмотреть собственный ответ OP, обновленная форма которого теперь способна преобразовать целое выражение, содержащее несколько чисел в экспоненциальной нотации, в эквивалент bc выражение.


(b) использование awk или perl вместо bc как калькулятор

Примечание: следующие подходы предполагают использование встроенная поддержка значений с плавающей запятой двойной точности в awk и perl. Как и присуще арифметике с плавающей запятой,
"учитывая любое фиксированное число битов, большинство вычислений с действительными числами будут производить количества, которые не могут быть точно представлены с использованием этого количества битов. Поэтому результат вычисления с плавающей запятой часто должен быть округлен, чтобы соответствовать в его конечное представление. Это ошибки округления является характерной особенностью вычисления с плавающей запятой."(http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)

что сказал:

  • GNU awk предлагает опции на построен с поддержка произвольной точности арифметики - см. https://www.gnu.org/software/gawk/manual/html_node/Gawk-and-MPFR.html, однако, дистрибутивы может или не может включите эту поддержку-проверьте поддержку, проверив вывод из gawk --version на GNU MPFR и GNU MP.
    Если поддержка is доступно, вы должны активировать его с помощью -M (--bignum) в данном вызове.

  • Perl предлагает дополнительно поддержка произвольно-точности десятичная через Math::BigFloat пакет-см. https://metacpan.org/pod/Math::BigFloat

awk

awk изначально понимает десятичную экспоненциальную (научную) нотацию.
(Обычно вы должны использовать только decimal представление, потому что awk реализации отличаются относительно того, поддерживают ли они числовые литералы с другими базами.)

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

если вы используете по умолчанию .

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

модификации переменная OFMT изменяет формат вывода по умолчанию (для чисел с дробными частями; (эффективные) целые числа всегда выводятся как таковые).
В качестве альтернативы, используйте printf функции с явным указанием формата:

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

Perl

perl слишком изначально понимает десятичную экспоненциальную (научную) нотацию.

Примечание: Perl, в отличие от awk, доступен не на всех платформах POSIX по умолчанию; кроме того, это не такой легкий, как на awk.
Тем не менее, он предлагает больше возможностей, чем awk, таких как изначально понимание шестнадцатеричных и восьмеричных целых чисел.

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

мне неясно, какой формат вывода Perl по умолчанию, но он кажется %.15g. Как и в awk, вы можете использовать printf выбрать нужный формат вывода:

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

(c) использование printf конвертировать научное обозначение десятичных дробей

если вы просто хотите преобразовать научную нотацию (например,1.2e-2) в десятичную дробь (например, 0.012), printf '%f' может сделать это за вас. Обратите внимание, что вы преобразовать один textual представительство в другое via арифметика с плавающей точкой, которая подлежит те же ошибки округления, что и awk и perl подходы.

printf '%.4f' '1.2e-2' # -> '0.0120'; `.4` specifies 4 decimal digits.

для этого можно использовать awk; например,

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

производит (через формат awk по умолчанию %.6g) выход как
12.3457 3.14159 543210000000000000
в то время как команды, подобные следующим двум, производят вывод, показанный после каждого, учитывая, что файл edata содержит данные, как показано ниже.

$ 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

кроме того, относительно решений с использованием sed, вероятно, лучше удалить знак плюса в формах, таких как 45e+3 в то же время как e, через regex [eE]+*, а не в отдельный sed выражение. Например, на моей машине linux с GNU sed версии 4.2.1 и bash версии 4.2.24, команды
sed 's/[eE]+*/*10^/g' <<< '7.11e-2 + 323e+34'
sed 's/[eE]+*/*10^/g' <<< '7.11e-2 + 323e+34' | bc -l
выпуск продукции
7.11*10^-2 + 323*10^34
3230000000000000000000000000000000000.07110000000000000000


вы также можете определить функцию bash, которая вызывает awk (хорошим именем будет знак равенства"="):

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

затем вы можете использовать все типы математики с плавающей запятой в оболочке. Обратите внимание, что здесь вместо круглых скобок используются квадратные скобки, поскольку последние должны быть защищены от bash кавычками.

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

или в скрипте назначить результат

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

к счастью, есть printf, который выполняет задание форматирования:

приведенный выше пример:

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

или сравнение с поплавком:

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

попробуйте это (найдено это в примере для входных данных CFD для обработки с m4:)

T0=4e-5
deltaT=2e-6
m4 <<< "esyscmd(perl -e 'printf (${T0} + ${deltaT})')"

попробуйте это: (используя bash)

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

или такой:

 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

Если у вас есть положительные показатели, вы должны использовать это:

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

этот последний будет обрабатывать все числа, брошенные на него. Вы можете адаптировать "sed", если у вас есть числа с " e " или " E " в качестве показателей.

вы можете выбрать шкалу, которую хотите.


трубопроводная версия OPs принят ответ

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

трубопровод ввода в команду OPs accepted sed дал дополнительные обратные косые черты, такие как

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