Математические функции

В библиотеке Java определено большое количество математических функций, предназначенных для выполнения более сложных операций. Для примера их использования рассмотрим задачу решения квадратного уравнения ax2 + bx + c = 0.

Напомним, что корни квадратного уравнения ищутся по формуле x1,2 = (-b ± √d) / (2a), где d — дискриминант квадратного уравнения — вычисляется как d = b2-4ac. Мы решим эту задачу в упрощённом виде — найти какой-нибудь из двух возможных корней, скажем, тот, в котором в числителе используется знак плюс.

Для начала напишем функцию для расчёта дискриминанта (она ещё пригодится нам в будущем). Для расчёта b2применим уже написанную выше функцию sqr(x: Double).

fun discriminant(a: Double, b: Double, c: Double) = sqr(b) - 4 * a * c

В приведённой записи b является аргументом функции sqr. Запись вида sqr(b) называется вызовом функции sqr. Подчеркнём отличие параметра и аргумента — параметр определяется внутри функции и имеет определённое имя, в данном случае x, а аргумент передаётся в функцию снаружи и может являться как именем переменной, так и более сложным выражением.

Теперь напишем функцию для поиска корня квадратного уравнения. Для вычисления квадратного корня применим готовую математическую функцию sqrt(x: Double) из математической библиотеки Котлина.

fun quadraticEquationRoot(a: Double, b: Double, c: Double) =
        (-b + kotlin.math.sqrt(discriminant(a, b, c))) / (2 * a)

Здесь мы, в свою очередь, используем уже написанную функцию discriminant для поиска дискриминанта, и выражение discriminant(a, b, c), то есть дискриминант уравнения, является аргументом функции sqrt. Это как раз тот случай, когда аргумент является сложным выражением.

Обратите внимание на нотацию kotlin.math. перед именем функции sqrt. Поскольку готовых функций существует очень много, они разбиты на так называемые пакеты и классы внутри пакетов. kotlin.math.sqrt является полнымименем функции вычисления квадратного корня, а sqrt — её коротким именем. Из-за неудобства работы с полными именами, чаще используется следующая запись:

// Разрешение использовать короткие имена для ВСЕХ функций из пакета kotlin.math
import kotlin.math.*

fun quadraticEquationRoot(a: Double, b: Double, c: Double) =
        (-b + sqrt(discriminant(a, b, c))) / (2 * a)

Здесь import — так называемая директива импорта имён, смысл её пояснён в комментарии.

Примеры других функций из kotlin.math:

  • abs(x: Int) или abs(x: Double) — модуль;
  • sqrt(x: Double) — квадратный корень;
  • Double.pow(x: Double) — возведение в степень xy, используется как x.pow(y);
  • sin(x: Double) — синус, cos(x: Double) — косинус, tan(x: Double) — тангенс, все три функции считают, что x задан в радианах;
  • exp(x: Double) — экспонента ex;
  • log(x: Double), log10(x: Double) — соответственно натуральный и десятичный логарифм;
  • min(x: Int, y: Int) или min(x: Double, y: Double) — минимум из двух чисел;
  • max(x: Int, y: Int) или max(x: Double, y: Double) — максимум из двух чисел.

В том же пакете kotlin.math определены константы PI = 3.14…​ и E = 2.718…​

Переменные в функциях

Выше мы рассмотрели примеры с функциями sqrdiscriminant и sqRoot, вычисление результата в которых занимало одну строчку кода. Однако, в программировании это скорее редкий случай; гораздо чаще расчёт результата функции предполагает реализацию некоторой последовательности вычислений — алгоритма. Для сохранения результатов промежуточных вычислений программисты придумали переменные.

Рассмотрим, например, задачу вычисления произведения двух корней квадратного уравнения. Напомним, что корни квадратного уравнения вычисляются как (-b+√d)/(2a) и (-b-√d)/(2a) соответственно, где d — дискриминант квадратного уравнения. При вычислении произведения удобно вначале сохранить вычисленный корень из дискриминанта в переменной sd, так как он используется при вычислении обоих корней. Затем нужно вычислить оба корня x1 и x2и уже потом рассчитать их произведение. На Котлине это записывается следующим образом:

fun quadraticRootProduct(a: Double, b: Double, c: Double): Double /* тип обязателен */ {
    // Тело функции в виде блока
    val sd = sqrt(discriminant(a, b, c))
    val x1 = (-b + sd) / (2 * a)
    val x2 = (-b - sd) / (2 * a)
    return x1 * x2 // Результат
}

В этом примере тело функции записано в виде блока в фигурных скобках, в противоположность телу в виде выражения — как в функциях sqr и discriminant выше. Знак равенства при этом убирается и обязательно указывается тип результата функции. В примере присутствуют три промежуточные переменные — dx1x2. Определение промежуточной переменной в Котлине начинается с ключевого слова val (сокращение от value — значение), за которым следует имя переменной и, после знака равенства — её значение. При желании можно также указать тип переменной, например:

    // ...
    val sd: Double = sqrt(discriminant(a, b, c))

Если тип переменной не указан, он определяется автоматически, например, в данном случае он совпадёт с типом результата функции sqrt.

Блок состоит из так называемых операторов (в примере их четыре), выполняющихся по порядку сверху вниз. Преждечем использовать какую-либо переменную, её следует определить. Например, такая запись привела бы к ошибке:

fun quadraticRootProduct(a: Double, b: Double, c: Double): Double {
    val x1 = (-b + sd) / (2 * a) // Unresolved reference: sd
    val x2 = (-b - sd) / (2 * a) // Unresolved reference: sd
    val sd = sqrt(discriminant(a, b, c))
    return x1 * x2 // Результат
}

Последний оператор функции, начинающийся с ключевого слова return, определяет значение её результата; returnпереводится с английского как вернуть (результат). Функция quadraticRootProduct в первую очередь вычислит значение переменной sd, используя другие функции discriminant и sqrt. Затем произойдёт вычисление переменных x1 и x2 и лишь в конце — вычисление результата в операторе return.

Для сравнения, приведём запись той же функции, не использующей переменные:

fun quadraticRootProduct(a: Double, b: Double, c: Double) =
        ((-b + sqrt(discriminant(a, b, c))) / (2 * a)) * ((-b - sqrt(discriminant(a, b, c))) / (2 * a))

Хотя и записанная в одну строчку, такая функция является гораздо менее понятной, при её написании легко запутаться при расстановке скобок. Кроме того, в ней происходит двухкратное вычисление корня из дискриминанта, чего следует избегать.

Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.