Цитата мудреца

Голосование

Система Orphus. Если вы заметили ошибку на сайте, нажмите сюда.
Загружается, подождите...
Начало сайта Материалы сайта Программы Статьи для начинающих программистов
Версия для слабовидящих
Версия для печати

Округление до заданного числа значащих цифр

С января 2004 года журнал "Магия ПК" начал публиковать мои статьи для начинающих программистов. В них я рассказываю как прописные истины (применение различных операторов языка Паскаль), так и делюсь "секретами" (различными подходами к решению тех или иных задач).

Идея начать цикл статей посвящённых азам и секретам программирования родилась спонтанно. Один мой знакомый начал изучать программирование. Выучив несколько языков, которые требовали в институте, он так и не смог понять принцип, как же писать программы, как подойти к решению тех или иных задач. Именно для помощи в таких ситуациях, я и решил начать писать. Для этого пригодился и опыт преподавания языков программирования на компьютерных курсах (1990 г.).

Эти статьи предназначены для начинающих программистов, точнее для тех, кто решил попробовать себя в программировании. Самое первое препятствие на этом пути — страх перед трудностями, перед кажущейся сложностью процесса составления программ. Для того, чтобы помочь упростить сложные задачи — эти статьи.
  Сообщений: 8 • Страница 1 из 1

Округление до заданного числа значащих цифр

Не отходя от темы округления, продолжим восполнять пробелы стандартных функций, заложенных в библиотеки Паскаля (Delphi), а заодно поупражняемся в программировании, используя циклы по условиям.


Сначала немного о поставленной задаче. Для чего может понадобиться округление до заданного числа значащих цифр? Вспомните цифровой тестер, измеряющий напряжение, силу тока, сопротивление. На его табло помещается ограниченное число знаков (цифр): 4-5, иногда больше. Что же делать, если нужно померить напряжение в 0,00012 В? Если рисовать все необходимые нули, то отрежутся цифры в конце числа, которые тоже могут быть важны. Для решения этой проблемы используются множители, которые устанавливаются переключателем на тестере: устанавливаем переключатель в мВ, а на табло высвечивается 0,12 — вполне читабельный результат. Но мало избавится от нулей впереди числа или сзади, нужно ещё разобраться с длинным хвостом цифр справа (0,000124384743314), выросшим после применения нами деления или другой операции с плавающей точкой. Вот для этого нам и потребуется функция округления до заданного количества значащих цифр.


Посмотрим на текст программы:


Код: Выделить всё
uses Math;
function RoundSignificant(num: Extended; col: integer): Extended;
var
  counter, MaxValue, MinValue, PreSign: integer;
  operand: Extended;
begin
  if (col <= 0) or (num = 0)
    then
      begin
        result := 0;
        Exit;
      end;
  try
    MaxValue := Trunc(IntPower(10, col));
  except
    result := num;
    Exit;
  end;
  MinValue := MaxValue div 10;
  counter := 0;
  PreSign := Sign(num);
  operand := Abs(num);
  while operand <= MinValue do
    begin
      operand := operand * 10;
      counter := counter + 1;
    end;
  while operand > MaxValue do
    begin
      operand := operand / 10;
      counter := counter - 1;
    end;
  result := Round(operand) / IntPower(10, counter) * PreSign;
end;

Функция имеет два параметра: num — число, которое мы собираемся округлять (вещественное); и col — количество значащих цифр, которые должны остаться после округления (целое). Результат функции — разумеется, вещественное число.


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


Первое, что делает наша функция — открещивается от недопустимых значений параметров. Во-первых, целочисленное col вполне может привести работу функции ко всяким недопустимым ситуациям. Согласитесь, ни отрицательное, ни нулевое значение этого параметра не имеет своего логического смысла. Как, скажем, можно округлить число до 0 значащих цифр? Это число просто перестанет существовать. Вот к этому (к 0), собственно, мы и приводим результат функции, если значение второго параметра отрицательно или равно нулю. Во-вторых, бессмысленно что-либо делать, если num = 0. Это условие тоже обнуляет результат. Дальнейшее выполнение операторов в функции бессмысленно, поэтому мы завершаем работу командой Exit.


Следующая проверка второго параметра происходит на предмет его недопустимо большой величины. Приняв слишком большое значение параметра col, мы вполне можем выйти за допустимые пределы для вещественных чисел, возводя 10 в степень col. Но мы не можем заранее знать наверняка этот верхний предел для вещественных чисел, поскольку он колеблется в зависимости от платформы (операционной системы и самого компьютера), на которой будет работать наша программа. Поэтому мы даём компьютеру попробовать выполнить операцию, которая может привести к ошибке, но оговариваем, что нужно сделать, в случае, если в этом месте ошибка всё-таки возникла: мы помещаем эту операцию в конструкцию try…except. Как мы уже говорили, ошибка возникает при слишком большом заданном количестве значащих цифр. Что же должна сделать функция в этом случае? Очевидно, что это "слишком большое значение" заведомо больше, чем само округляемое число. Значит, логично будет, если функция вернёт округляемое число без изменений. Что мы и делаем, завершая после этого работу функции.


Но что же мы пытались сделать такое, что могло привести к ошибке?


Задумка была следующая. Надо привести число, которое нам надо округлить, к виду, когда до запятой будет ровно столько цифр, сколько задано во втором параметре. Для этого мы должны умножать или делить округляемое число до тех пор, пока оно не окажется в заданных пределах. Так вот эти пределы мы и начали определять. Верхний предел, выше которого число не должно скакнуть, — 10col. В нашей функции за верхний предел отвечает переменная MaxValue. Мы возводим 10 в степень col и делаем из вещественного числа, получаемого после этого действия, целое. "Зачем?", — спросите вы. Дело в том, что при работе с вещественными числами иногда случается, что в последнем разряде значащих цифр иногда появляется т.н. шум. При возведении 10, к примеру, в степень 3, мы можем получить либо 1000,00000001, либо 999,99999999. Согласитесь, это не будет ровная тысяча, и при определённых действиях, производимых с этой "тысячей" мы получим совсем неожидаемый результат. Поэтому, в тех случаях, когда логически мы должны иметь целое число, лучше его сразу приводить к этому виду.


Итак, мы получили верхнюю границу диапазона (и ошибки при этом не случилось). Теперь наша задача получить нижнюю границу, которая будет храниться в переменной MinValue. Её значение будет — 10col-1. Но мы не будет прибегать к тому же способу, как получали верхнюю границу. Дело в том, что функция IntPower — достаточно медлительная, т.к. работает с "плавающей арифметикой" (вещественными числами с плавающей точкой). Поскольку мы уже имеем целочисленную верхнюю границу, получить из неё нижнюю нам гораздо проще, разделив её на 10. Причём деление мы применяем целочисленное ("div" вместо "/").


Коридор для изменения округляемого числа у нас готов. Теперь, для того, чтобы начать действия, нам надо подготовить ещё три переменные: counter — счётчик, который будет хранить количество разрядов, на которое мы сдвигаем округляемое число вправо или влево; PreSign — знак округляемого числа, который потом восстановим у результата функции; operand — это переменная, которая изначально должна принять значение модуля округляемого числа num, а затем изменяться. Дело в том, что в нашем случае параметры функции передаются как значения, т.е. мы можем ими пользоваться, (читать, сравнивать), но не можем использовать полноценно в качестве переменных (присваивать им новые значения). Теперь пара слов о модуле. Лучше сразу отказаться от знака числа, чем потом при каждой проверке вычислять абсолютное значение числа. В каждой программе, которую мы пишем, больший вес по времени выполнения, обычно занимают циклы. Поэтому мы должны как можно больше разгрузить циклы и как можно больше операций вынести, так сказать, "за скобки".


Итак, начнём приводить округляемое число в определённый для него коридор. Прежде всего, давайте определимся с границами. Они должны быть следующими: operand ? ]MaxValue; MinValue]. При col = 3 число может быть равно 100, но не может быть равно 1000. Поэтому мы применяем следующее условие: MaxValue < operand ? MinValue. Но в нашей функции вместо оператора проверки условия if использованы операторы цикла while. Это очень интересный оператор цикла, который будет выполнять вверенные ему команды до тех пор, пока выполняется заданное ему условие. Причём, если это условие не выполняется с самого начала, то и цикл не будет выполняться ни разу. У нас таких циклов стоит два, один за другим. Первый рассчитан на то, если число меньше нижней границы коридора, а второй, если оно больше верхней границы. Причём, если функция отработает один из этих циклов, то в другой она вообще не войдёт. Что же делается внутри них? Если число меньше нижней границы коридора, то мы умножаем его на 10 и увеличиваем счётчик на единицу. Если же оно больше верхней границы, то мы делим его на 10 и уменьшаем счётчик на единицу. Таким образом, после отработки циклов мы получаем operand, лежащий внутри коридора, и счётчик показывающий на сколько и в какую сторону (знак счётчика) мы двигали округляемое число.


И теперь нам осталось отработать несколько действий, объединённых в одно выражение. Сначала мы округляем operand, получая как раз заданное количество значащих цифр, находящихся, пока, до запятой. А потом делим получившееся округлённое, пока целое, число на 10 в степени, хранящейся в счётчике counter. Если counter положительный, то мы делим на число, большее 1, тем самым, уменьшая его. А если степень отрицательная, что означает, что мы делили (уменьшали) число, то, деля на число, меньшее 1, мы увеличиваем его. Вернув разрядность исходного числа, мы умножаем его на PreSign, возвращая тем самым знак.


Задача решена, а мы можем протестировать эту функцию, подавая ей различные значения в качестве параметров.

Ответить

  Сообщений: 8 • Страница 1 из 1

Вернуться в Статьи для начинающих программистов



Кто сейчас на сайте

Зарегистрированные пользователи: Bing [Bot]