Перейти к основному содержанию
Рецепты Linux

Main navigation

  • Основы
  • Система
  • Команды
  • Программы
  • Дистро
  • Интерфейсы
  • Устройства
  • Доки
User account menu
  • Войти

Строка навигации

  1. Главная
  2. ABS Guide
  3. Часть 3. Углубленный материал
  4. Глава 12. Внешние команды, программы и утилиты

12.8. Команды выполнения математических операций

factor

Разложение целого числа на простые множители.

bash$ factor 27417
27417: 3 13 19 37
bc

Bash не в состоянии выполнять действия над числами с плавающей запятой и не содержит многих важных математических функций. К счастью существует bc.

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

Синтаксис bc немного напоминает язык C.

Поскольку это утилита Unix, то она может достаточно широко использоваться в сценариях на языке командной оболочки, в том числе и в конвейерной обработке данных.

Ниже приводится простой шаблон работы с утилитой bc в сценарии. Здесь используется прием подстановки команд.

variable=$(echo "OPTIONS; OPERATIONS" | bc)

Пример 12-35. Ежемесячные выплаты по займу

#!/bin/bash
# monthlypmt.sh: Расчет ежемесячных выплат по займу.


# Это измененный вариант пакета "mcalc" (mortgage calculator),
#+ написанного Jeff Schmidt и Mendel Cooper (ваш покорный слуга).
# http://www.ibiblio.org/pub/Linux/apps/financial/mcalc-1.6.tar.gz [15k]

echo
echo "Введите сумму займа, процентную ставку и срок займа,"
echo "для расчета суммы ежемесячных выплат."

bottom=1.0

echo
echo -n "Сумма займа (без запятых -- с точностью до доллара) "
read principal
echo -n "Процентная ставка (процент) " # Если 12%, то нужно вводить "12", а не ".12".
read interest_r
echo -n "Срок займа (месяцев) "
read term


 interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # Здесь "scale" -- точность вычислений.


 interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)
 
 
 top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)
 
 echo; echo "Прошу подождать. Вычисления потребуют некоторого времени."
 
 let "months = $term - 1"
# ====================================================================
 for ((x=$months; x > 0; x--))
 do
 bot=$(echo "scale=9; $interest_rate^$x" | bc)
 bottom=$(echo "scale=9; $bottom+$bot" | bc)
# bottom = $(($bottom + $bot"))
 done
# --------------------------------------------------------------------
# Rick Boivie предложил более эффективную реализацию
#+ цикла вычислений, который дает выигрыш по времени на 2/3.
	 
# for ((x=1; x = $months; x++))
# do
# bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc)
# done
	 
	 
# А затем нашел еще более эффективную альтернативу,
#+ которая выполняется в 20 раз быстрее !!!
	 
# bottom=`{
# echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"
# for ((x=1; x = $months; x++))
# do
# echo 'bottom = bottom * interest_rate + 1'
# done
# echo 'bottom'
# } | bc` # Внедрить цикл 'for' в конструкцию подстановки команд.
	 
# ====================================================================
	 
 # let "payment = $top/$bottom"
 payment=$(echo "scale=2; $top/$bottom" | bc)
 # Два знака после запятой, чтобы показать доллары и центы.
		
 echo
 echo "ежемесячные выплаты = \$$payment" # Вывести знак "доллара" перед числом.
 echo
		 
		 
 exit 0
		 
 # Упражнения:
 # 1) Добавьте возможность ввода суммы с точностью до цента.
 # 2) Добавьте возможность ввода процентной ставки как в виде процентов, так и в виде десятичного числа -- доли целого.
 # 3) Если вы действительно честолюбивы,
 # добавьте в сценарий вывод полной таблицы помесячных выплат.

Пример 12-36. Перевод чисел из одной системы счисления в другую

:
##########################################################################
# Shellscript: base.sh - вывод чисел в разных системах счисления (Bourne Shell)
# Author : Heiner Steven (heiner.steven@odn.de)
# Date : 07-03-95
# Category : Desktop
# $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $
##########################################################################
# Description
#
# Changes
# 21-03-95 stv исправлена ошибка, возникающая при вводе числа 0xb (0.2)
##########################################################################

# ==> Используется в данном документе с разрешения автора.
# ==> Комментарии добавлены автором документа.

NOARGS=65
PN=`basename "$0"` # Имя программы
VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2` # ==> VER=1.2

Usage () {
 echo "$PN - вывод чисел в различных системах счисления, $VER (stv '95)
Порядок использования: $PN [number ...]

Если число не задано, то производится ввод со stdin.
Число может быть:
 двоичное должно начинаться с комбинации символов 0b (например 0b1100)
 восьмеричное должно начинаться с 0 (например 014)
 шестнадцатиричное должно начинаться с комбинации символов 0x (например 0xc)
 десятичное в любом другом случае (например 12)" >&2
 exit $NOARGS
} # ==> Функция вывода сообщения о порядке использования.
		
Msg () {
 for i # ==> [список] параметров опущен.
 do echo "$PN: $i" >&2
 done
}
			 
Fatal () { Msg "$@"; exit 66; }
			 
PrintBases () {
 # Определение системы счисления
 for i # ==> [список] параметров опущен...
 do # ==> поэтому работает с аргументами командной строки.
 case "$i" in
 0b*) ibase=2;; # двоичная
 0x*|[a-f]*|[A-F]*) ibase=16;; # шестнадцатиричная
 0*) ibase=8;; # восьмеричная
 [1-9]*) ibase=10;; # десятичная
 *)
 Msg "Ошибка в числе $i - число проигнорировано"
 continue;;
 esac
																		 
 # Удалить префикс и преобразовать шестнадцатиричные цифры в верхний регистр (этого требует bc)
 number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'`
 # ==> вместо "/", здесь используется символ ":" как разделитель для sed.
																					 
 # Преобразование в десятичную систему счисления
 dec=`echo "ibase=$ibase; $number" | bc` # ==> 'bc' используется как калькулятор.
 case "$dec" in
 [0-9]*) ;; # все в порядке
 *) continue;; # ошибка: игнорировать
 esac
																												 
 # Напечатать все преобразования в одну строку.
 # ==> 'вложенный документ' -- список команд для 'bc'.
 echo `bc  Вывод справочного сообщения.
 -*) Usage;;
 *) break;; # первое число
 esac # ==> Хорошо бы расширить анализ вводимых символов.
 shift
done
																																												
if [ $# -gt 0 ]
then
 PrintBases "$@"
else # чтение со stdin
 while read line
 do
 PrintBases $line
 done
fi

Один из вариантов вызова bc -- использование вложенного документа, внедряемого в блок с подстановкой команд. Это особенно актуально, когда сценарий должен передать bc значительный по объему список команд и аргументов.

variable=`bc  LIMIT_STRING
options
statements
operations
LIMIT_STRING

...или...

variable=$(bc  LIMIT_STRING
options
statements
operations
LIMIT_STRING
)
 

Пример 12-37. Пример взаимодействия bc со "встроенным документом"

#!/bin/bash
# Комбинирование 'bc' с
# 'вложенным документом'.


var1=`bc  EOF
18.33 * 19.78
EOF
`
echo $var1 # 362.56


# запись $( ... ) тоже работает.
v1=23.53
v2=17.881
v3=83.501
v4=171.63

var2=$(bc  EOF
scale = 4
a = ( $v1 + $v2 )
b = ( $v3 * $v4 )
a * b + 15.35
EOF
)
echo $var2 # 593487.8452


var3=$(bc -l  EOF
scale = 9
s ( 1.7 )
EOF
)
# Возвращается значение синуса от 1.7 радиана.
# Ключом "-l" вызывается математическая библиотека 'bc'.
echo $var3 # .991664810


# Попробуем функции...
hyp= # Объявление глобальной переменной.
hypotenuse () # Расчет гипотенузы прямоугольного треугольника.
{
hyp=$(bc -l  EOF
scale = 9
sqrt ( $1 * $1 + $2 * $2 )
EOF
)
# К сожалению, функции Bash не могут возвращать числа с плавающей запятой.
}

hypotenuse 3.68 7.31
echo "гипотенуза = $hyp" # 8.184039344


exit 0

Пример 12-38. Вычисление числа "пи"

#!/bin/bash
# cannon.sh: Аппроксимация числа "пи".

# Это очень простой вариант реализации метода "Monte Carlo",
#+ математическое моделирование событий реальной жизни,
#+ для эмуляции случайного события используются псевдослучайные числа.

# Допустим, что мы располагаем картой квадратного участка поверхности со стороной квадрата 10000 единиц.
# На этом участке, в центре, находится совершенно круглое озеро,
#+ с диаметром в 10000 единиц.
# Т.е. озеро покрывает почти всю карту, кроме ее углов.
# (Фактически -- это квадрат со вписанным кругом.)
#
# Пусть по этому участку ведется стрельба железными ядрами из древней пушки
# Все ядра падают где-то в пределах данного участка,
#+ т.е. либо в озеро, либо на сушу, по углам участка.
# Поскольку озеро покрывает большую часть участка,
#+ то большинство ядер будет падать в воду.
# Незначительная часть ядер будет падать на твердую почву.
#
# Если произвести достаточно большое число неприцельных выстрелов по данному участку,
#+ то отношение попаданий в воду к общему числу выстрелов будет примерно равно
#+ значению PI/4.
#
# По той простой причине, что стрельба фактически ведется только
#+ по правому верхнему квадранту карты.
# (Предыдущее описание было несколько упрощено.)
#
# Теоретически, чем больше будет произведено выстрелов, тем точнее будет результат.
# Однако, сценарий на языке командной оболочки, в отличие от других языков программирования,
#+ в которых доступны операции с плавающей запятой, имеет некоторые ограничения.
# К сожалению, это делает вычисления менее точными.


DIMENSION=10000 # Длина стороны квадратного участка поверхности.
 # Он же -- верхний предел для генератора случайных чисел.
		 
		 MAXSHOTS=1000 # Количество выстрелов.
		 # 10000 выстрелов (или больше) даст лучший результат,
				 # но потребует значительного количества времени.
PMULTIPLIER=4.0 # Масштабирующий коэффициент.
												 
get_random ()
{
SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
RANDOM=$SEED # Из примера "seeding-random.sh"
												 
let "rnum = $RANDOM % $DIMENSION" # Число не более чем 10000.
echo $rnum
}
												 
distance= # Объявление глобальной переменной.
hypotenuse () # Расчет гипотенузы прямоугольного треугольника.
{ # Из примера "alt-bc.sh".
distance=$(bc -l  EOF
scale = 0
sqrt ( $1 * $1 + $2 * $2 )
EOF
)
# Установка "scale" в ноль приводит к округлению результата "вниз",
#+ это и есть то самое ограничение, накладываемое командной оболочкой.
# Что, к сожалению, снижает точность аппроксимации.
}
												 

# main() {
											 
# Инициализация переменных.
shots=0
splashes=0
thuds=0
Pi=0
												 
while [ "$shots" -lt "$MAXSHOTS" ] # Главный цикл.
do
												 
 xCoord=$(get_random) # Получить случайные координаты X и Y.
 yCoord=$(get_random)
 hypotenuse $xCoord $yCoord # Гипотенуза = расстоянию.
 ((shots++))
													 
 printf "#%4d " $shots
 printf "Xc = %4d " $xCoord
 printf "Yc = %4d " $yCoord
 printf "Distance = %5d " $distance # Растояние от
 #+ центра озера,
 #+ с координатами (0,0).
																									 
 if [ "$distance" -le "$DIMENSION" ]
 then
 echo -n "ШЛЕП! " # попадание в озеро
 ((splashes++))
 else
 echo -n "БУХ! " # попадание на твердую почву
 ((thuds++))
 fi
																												 
 Pi=$(echo "scale=9; $PMULTIPLIER*$splashes/$shots" | bc)
 # Умножение на коэффициент 4.0.
 echo -n "PI ~ $Pi"
 echo

done

echo
echo "После $shots выстрела, примерное значение числа \"пи\" равно $Pi."
# Имеет тенденцию к завышению...
# Вероятно из-за ошибок округления и несовершенства генератора случайных чисел.
echo
																													 
# }
																													 
exit 0
																													 
# Самое время задуматься над тем, является ли сценарий удобным средством
#+ для выполнения большого количества столь сложных вычислений.
#
# Тем не менее, этот пример может расцениваться как
# 1) Доказательство возможностей языка командной оболочки.
# 2) Прототип для "обкатки" алгоритма перед тем как перенести
#+ его на высокоуровневые языки программирования компилирующего типа.
dc

Утилита dc (desk calculator) -- это калькулятор, использующий "Обратную Польскую Нотацию", и ориентированный на работу со стеком.

Многие стараются избегать испоьзования dc, из-за непривычной формы записи операндов и операций. Однако, dc имеет и своих сторонников.

Пример 12-39. Преобразование чисел из десятичной в шестнадцатиричную систему счисления

#!/bin/bash
# hexconvert.sh: Преобразование чисел из десятичной в шестнадцатиричную систему счисления.

BASE=16 # Шестнадцатиричная.

if [ -z "$1" ]
then
 echo "Порядок использования: $0 number"
 exit $E_NOARGS
 # Необходим аргумент командной строки.
fi
# Упражнение: добавьте проверку корректности аргумента.


hexcvt ()
{
if [ -z "$1" ]
then
 echo 0
 return # "Return" 0, если функции не был передан аргумент.
fi

echo ""$1" "$BASE" o p" | dc
# "o" устанавливает основание системы счисления для вывода.
# "p" выводит число, находящееся на вершине стека.
# См. 'man dc'.
return
}

hexcvt "$1"

exit 0

Изучение страниц infodc позволит детальнее разобраться с утилитой. Однако, отряд "гуру", которые могут похвастать своим знанием этой мощной, но весьма запутанной утилиты, весьма немногочислен.

Пример 12-40. Разложение числа на простые множители

#!/bin/bash
# factr.sh: Разложение числа на простые множители

MIN=2 # Не работает с числами меньше 2.
E_NOARGS=65
E_TOOSMALL=66

if [ -z $1 ]
then
 echo "Порядок использования: $0 number"
 exit $E_NOARGS
fi

if [ "$1" -lt "$MIN" ]
then
 echo "Исходное число должно быть больше или равно $MIN."
 exit $E_TOOSMALL
fi

# Упражнение: Добавьте проверку типа числа (не целые числа должны отвергаться).

echo "Простые множители для числа $1:"
# ---------------------------------------------------------------------------------
echo "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=1lrli2+dsi!>.]ds.xd12" | dc
# ---------------------------------------------------------------------------------
# Автор вышеприведенной строки: Michel Charpentier .
# Используется с его разрешения (спасибо).

 exit 0
awk

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

Пример 12-41. Расчет гипотенузы прямоугольного треугольника

#!/bin/bash
# hypotenuse.sh: Возвращает "гипотенузу" прямоугольного треугольника.
# ( корень квадратный от суммы квадратов катетов)

ARGS=2 # В сценарий необходимо передать два катета.
E_BADARGS=65 # Ошибка в аргументах.

if [ $# -ne "$ARGS" ] # Проверка количества аргументов.
then
 echo "Порядок использования: `basename $0` катет_1 катет_2"
 exit $E_BADARGS
fi


AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } '
# команды и параметры, передаваемые в awk


echo -n "Гипотенуза прямоугольного треугольника, с катетами $1 и $2, = "
echo $1 $2 | awk "$AWKSCRIPT"

exit 0

Перекрёстные ссылки книги для 12.8. Команды выполнения математических операций

  • 12.7. Команды управления терминалом
  • Вверх
  • 12.9. Прочие команды

Book navigation

  • Содержание
  • Часть 1. Введение
  • Часть 2. Основы
  • Часть 3. Углубленный материал
    • Глава 9. К вопросу о переменных
    • Глава 10. Циклы и ветвления
    • Глава 11. Внутренние команды
    • Глава 12. Внешние команды, программы и утилиты
      • 12.1. Базовые команды
      • 12.2. Более сложные команды
      • 12.3. Команды для работы с датой и временем
      • 12.4. Команды обработки текста
      • 12.5. Команды для работы с файлами и архивами
      • 12.6. Команды для работы с сетью
      • 12.7. Команды управления терминалом
      • 12.8. Команды выполнения математических операций
      • 12.9. Прочие команды
    • Глава 13. Команды системного администрирования
    • Глава 14. Подстановка команд
    • Глава 15. Арифметические подстановки
    • Глава 16. Перенаправление ввода/вывода
    • Глава 17. Встроенные документы
  • Часть 4. Материал повышенной сложности
  • Глава 35. Замечания и дополнения
  • Библиография
  • Приложение A. Дополнительные примеры сценариев
  • Приложение B. Справочная информация
  • Приложение C. Маленький учебник по Sed и Awk
  • Приложение D. Коды завершения, имеющие предопределенный смысл
  • Приложение E. Подробное введение в операции ввода-вывода и перенаправление ввода-вывода
  • Приложение F. Системные каталоги
  • Приложение G. Локализация
  • Приложение H. История команд
  • Приложение I. Пример файла .bashrc
  • Приложение J. Преобразование пакетных (*.bat) файлов DOS в сценарии командной оболочки
  • Приложение K. Упражнения
  • Приложение L. Хронология
  • Приложение M. Авторские права

Последние материалы

  • Приложение scanimage
    4 hours 8 minutes ago
  • Утилита sensors
    4 days ago
  • Сканер Rkhunter
    1 week 4 days ago
  • Программа resize2fs
    2 weeks 3 days ago
  • Аудиопроигрыватель QMMP
    3 weeks 1 day ago
RSS feed

Secondary menu

  • О проекте

© 2008–2025 Олег Меньшенин mensh@yandex.ru