Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Hello, World!
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
 
 07:53 Den Sarych
 
 
Во Флориде и в Королевстве сейчас  08:13[Войти] | [Зарегистрироваться]

Обсуждение материала
Загадки округления
Полный текст материала


Другие публикации автора: Алексей Михайличенко

Цитата или краткий комментарий:

«... В этой статье поясняется, чем отличается бухгалтерское округление от арифметического, какое из них реализовано в Excel'e, и почему некоторые Delphi—функции странным образом работают иначе. ...»


Важно:
  • Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
  • Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
  • При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
  • Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.



Добавить свое мнение.

Результаты голосования
Оценка содержания

  Содержит полезные и(или) интересные сведения
[1]1593.8%
 
  Ничего особенно нового и интересного
[2]16.2%
 
  Написано неверно (обязательно укажите почему)
[3]00%
 
Всего проголосовали: 16

Оценка стиля изложения

  Все понятно, материал читается легко
[1]14100%
 
  Есть неясности в изложении
[2]00%
 
  Непонятно написано, трудно читается
[3]00%
 
Всего проголосовали: 14




Смотрите также материалы по темам:
[Математические функции]

Комментарии жителей
Отслеживать это обсуждение

Всего сообщений: 43

12-03-2016 10:52
Сейчас чего-нибудь сбрешу наверно... Со времен 7го турбо Паскаля серьезно программированием не занимался, но во времена оны, еще до интернета, был у меня диск типа "Всё в одном". По мимо самого Паскаля в нем была и прорва примеров на всё случае жизни, и в частности про округление были какие то функции работы с числами с плавающей точкой путем преобразования в строку. Вот сейчас у Гугля спрашивал, что-то он такого не помнит. А ведь точно было. То ли целое и дробную часть числа раздербанивали на два целых отдельных и считали целую и дробную часть отдельно, то ли еще что то.
Наверное, переводить числа в строку и обратно слишком ресурсоёмкая операция, но вроде как проблема с округлением была решана окончательно и с любой разрядностью которую можно впихнуть в процессор, а то и больше.

Вот никто этого не помнит?


19-08-2015 03:51
сообщение от автора материала
В тексте упоминается статья Дэвида Голдберга "Что должен знать каждый ученый-компьютерщик о об арифметике с плавающей запятой", но без ссылки.
В оригинале она называется "What Every Computer Scientist Should Know About Floating-Point Arithmetic". Одна из ссылок на нее:

http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html


02-11-2010 10:25
сообщение от автора материала
Прошу прощения за длительную паузу, только сейчас пришел в себя после переезда в новый офис.

Итак, о наших баранах - функциях округления.

Потестировал вариант от 31-08-2010 02:54 RoundToDecimal.

Пожалуй, это самая загадочная функция из встречавшихся.

Прежде всего, как я ни пытался, не смог понять из алгоритма, с каким знаком передавать количество знаков для округления: если надо округлить, скажем, 123.456 до копеек, 123.46, то как передавать Places: integer: +2 или -2 ? (поручики, молчать!) Оно работает и так и этак, и ошибки получаются в обоих случаях, но немного разные.

Например, в любом случае, при попытке арифметического округления (Bankers=false) до копеек оно из 0.0046 делает 0.01, хотя должно 0. А из 0.0146 делает 0.02.

При банковском округлении из 0.2950 должно быть 0.30, а выходит 0.29.

Таких случаев много. Вот куски из отчетов:

Арифметическое (Places=-2):

Столбцы означают: Округляемое число, Должно получиться, Фактически получилось, Разница.

0.0046 0.00 0.01 0.01
0.0047 0.00 0.01 0.01
0.0048 0.00 0.01 0.01
0.0049 0.00 0.01 0.01
0.0145 0.01 0.02 0.01
0.0146 0.01 0.02 0.01
0.0147 0.01 0.02 0.01
0.0148 0.01 0.02 0.01
0.0149 0.01 0.02 0.01
0.0245 0.02 0.03 0.01
0.0246 0.02 0.03 0.01
0.0247 0.02 0.03 0.01
0.0248 0.02 0.03 0.01
0.0249 0.02 0.03 0.01
0.0345 0.03 0.04 0.01
0.0346 0.03 0.04 0.01
0.0347 0.03 0.04 0.01
0.0348 0.03 0.04 0.01
0.0349 0.03 0.04 0.01
0.0445 0.04 0.05 0.01


Банковское (Places=-2):

0.2950 0.30 0.29 -0.01
1.0550 1.06 1.05 -0.01
1.0650 1.06 1.07 0.01
1.1750 1.18 1.17 -0.01
1.2050 1.20 1.21 0.01
2.0050 2.00 2.01 0.01
2.0250 2.02 2.03 0.01
2.2150 2.22 2.21 -0.01
2.2350 2.24 2.23 -0.01
2.2650 2.26 2.27 0.01
2.2850 2.28 2.29 0.01
2.4750 2.48 2.47 -0.01
2.4950 2.50 2.49 -0.01
2.5050 2.50 2.51 0.01
2.5250 2.52 2.53 0.01
5.0550 5.06 5.05 -0.01
5.0750 5.08 5.07 -0.01
5.0950 5.10 5.09 -0.01
5.1150 5.12 5.11 -0.01
8.1450 8.14 8.15 0.01
8.1850 8.18 8.19 0.01
8.2250 8.22 8.23 0.01
8.2350 8.24 8.23 -0.01
8.2650 8.26 8.27 0.01
8.2750 8.28 8.27 -0.01
8.3150 8.32 8.31 -0.01
8.3550 8.36 8.35 -0.01
8.6450 8.64 8.65 0.01
8.6850 8.68 8.69 0.01




31-08-2010 02:54
Вариант без ошибки (убрана Power10)

function RoundToDecimal(Value: Extended; Places: integer;
  Bankers: boolean = False) : Extended;
var
  Val, IV, N, F : Extended;
  T: Int64;
  k: integer;
begin
  IV := 0;

  // Compute 10^DecimRnd and scale the Value and MaxError: }
  N := 1;
  for k := 1 to abs(Places)
    do N := N * 10;

  if (Places > 0) then
    IV := Int(Value);
  Val := (Value - IV) * N;
  T := Trunc(Val);
  F := (Val - T);
  if Bankers then
    Val := Round(Val) / N        {Delphi's Round does Bankers}
  else
  begin
    if Abs(Round(10.0 * F)) >= 5 then
    begin
      if (F > 0) then
        Val := (T + 1.0) / N
      else
        Val := (T - 1.0) / N;
    end
    else
      Val := T / N;
  end;
  Result := Val + IV;
end;


31-08-2010 02:49
Еще один вариант функции округления из пакета TurboPower SysTools.
Ждем теста.


function RoundToDecimal(Value: Extended; Places: integer;
  Bankers: boolean = False) : Extended;
var
  Val, IV, N, F : Extended;
  T: Int64;
  k: integer;
begin
  IV := 0;

//  N := Exp10(Places);

  // Compute 10^DecimRnd and scale the Value and MaxError: }
  N := 1;
  for k := 1 to abs(Places)
    do N := N * 10;

  N := Power10(1, Places);
  if (Places > 0) then
    IV := Int(Value);
  Val := (Value - IV) * N;
  T := Trunc(Val);
  F := (Val - T);
  if Bankers then
    Val := Round(Val) / N        {Delphi's Round does Bankers}
  else
  begin
    if Abs(Round(10.0 * F)) >= 5 then
    begin
      if (F > 0) then
        Val := (T + 1.0) / N
      else
        Val := (T - 1.0) / N;
    end
    else
      Val := T / N;
  end;
  Result := Val + IV;
end;


24-02-2010 03:38
сообщение от автора материала
syber,

прежде всего, самая неожиданная ошибка: эта функция из 1.64 делает 1.65.

Ошибок округления пятерки тоже достаточно, нашлось более 2700 ситуаций. Примеры:


Аргумент Должно_быть Получается Разница

1.6400    1.64    1.65     0.01
1028.2350 1028.24 1028.23 -0.01
1028.2450 1028.25 1028.24 -0.01
1028.4850 1028.49 1028.48 -0.01
1028.4950 1028.50 1028.49 -0.01
1028.7350 1028.74 1028.73 -0.01
1028.7450 1028.75 1028.74 -0.01
1028.9850 1028.99 1028.98 -0.01
1028.9950 1029.00 1028.99 -0.01
1232.2350 1232.24 1232.23 -0.01
1232.2450 1232.25 1232.24 -0.01
1232.4850 1232.49 1232.48 -0.01
1232.4950 1232.50 1232.49 -0.01
1232.7350 1232.74 1232.73 -0.01
1232.7450 1232.75 1232.74 -0.01
1232.9850 1232.99 1232.98 -0.01
1232.9950 1233.00 1232.99 -0.01
1478.2350 1478.24 1478.23 -0.01
1478.2450 1478.25 1478.24 -0.01
1478.4850 1478.49 1478.48 -0.01
1478.4950 1478.50 1478.49 -0.01
1478.7350 1478.74 1478.73 -0.01
1478.7450 1478.75 1478.74 -0.01
1478.9850 1478.99 1478.98 -0.01
1478.9950 1479.00 1478.99 -0.01
1774.2350 1774.24 1774.23 -0.01
1774.2450 1774.25 1774.24 -0.01
1774.4850 1774.49 1774.48 -0.01
1774.4950 1774.50 1774.49 -0.01
1774.7350 1774.74 1774.73 -0.01
1774.7450 1774.75 1774.74 -0.01
1774.9850 1774.99 1774.98 -0.01
1774.9950 1775.00 1774.99 -0.01
2131.0150 2131.02 2131.01 -0.01
2131.0350 2131.04 2131.03 -0.01
2131.0550 2131.06 2131.05 -0.01
2131.0750 2131.08 2131.07 -0.01
2131.0950 2131.10 2131.09 -0.01




Несколько помогает замена всех real на extended: ошибка с 1.64 исчезает, и ошибок на том же массиве аргументов становится 764, они сдвигаются в область больших чисел:


Аргумент Должно_быть Получается Разница

2500289.2350 2500289.24 2500289.23 -0.01
2500289.2450 2500289.25 2500289.24 -0.01
2500289.4850 2500289.49 2500289.48 -0.01
2500289.4950 2500289.50 2500289.49 -0.01
2500289.7350 2500289.74 2500289.73 -0.01
2500289.7450 2500289.75 2500289.74 -0.01
2500289.9850 2500289.99 2500289.98 -0.01
2500289.9950 2500290.00 2500289.99 -0.01
3012833.2350 3012833.24 3012833.23 -0.01
3012833.2450 3012833.25 3012833.24 -0.01
3012833.4850 3012833.49 3012833.48 -0.01
3012833.4950 3012833.50 3012833.49 -0.01
3012833.7350 3012833.74 3012833.73 -0.01
3012833.7450 3012833.75 3012833.74 -0.01
3012833.9850 3012833.99 3012833.98 -0.01
3012833.9950 3012834.00 3012833.99 -0.01
3630449.2350 3630449.24 3630449.23 -0.01
3630449.2450 3630449.25 3630449.24 -0.01
3630449.4850 3630449.49 3630449.48 -0.01
3630449.4950 3630449.50 3630449.49 -0.01
3630449.7350 3630449.74 3630449.73 -0.01
3630449.7450 3630449.75 3630449.74 -0.01
3630449.9850 3630449.99 3630449.98 -0.01
3630449.9950 3630450.00 3630449.99 -0.01
4374675.0150 4374675.02 4374675.01 -0.01
4374675.0350 4374675.04 4374675.03 -0.01



16-02-2010 15:28
сообщение от автора материала
Минуточку терпения. На следующей неделе восстановлю из архива стенд для проверки, проверю и результат сообщу сюда же.


14-02-2010 10:47
Ребята, столкнулся недавно с этой же проблемой. :) Есть одно условие: входящее вещественное число после запятой может иметь максимум шесть значащих чисел после запятой - результат перемножения количества топлива на его стоимость. Нужно округлять до 2 цифр после запятой. Функция производит арифметическое округление:

function Okruglenie(x:real):real;
var
x1, x2, x3 : real;
begin
x1 := Frac(x);
x2 := x1*100;
x3 := Frac(x2);
if x3 > 0.49999999999 then x2 := x2 + 1 else x2 := x2;
if x2 >= 100 then x := Int(x)+1 else x := Int(x) + (Trunc(x2)/100);
Okruglenie := x;
end;



Кто может протестить её с позиции правильности арифметического округления?


22-05-2009 02:42
Не верно, тут такая штука: округляем мы к числам, оканчивающимся на 0 - к своебразным ямам, оканчивающиеся на (1 2 3 4) и (6 7 8 9) - разные склоны "горы" (для них понятно в какую яму скатываться при округлении), вершиной которой является 5 (а вот для нее не понятно). А вот, например при 9-чной системе счисления, имеем склоны (1 2 3 4) и (5 6 7 8) без цифры на острие.

А если округлять не до значащей цифры а до половины значащей цифры. То есть ямы будут в 0 и 5 например. Тогда последняя цифра: 1, 2 -> 0; 3,4 ->5; 6.7 -> 5; 8,9 ->10. Тогда накопления ошибки не будет. Правда тогда пол копейки нужно... Ну или с точностью до 50 копеек


07-05-2009 03:48
сообщение от автора материала
to syserg:

Хотел проверить Вашу okr.dll - что-то не получается. Такое впечатление, что возвращает то, что передали, не округляя. Можно рабочий пример, как ею пользоваться?


25-01-2009 17:27
Правильное, согласно ГОСТу, округление чисел
http://g-soft.nm.ru/download.htm


19-01-2009 09:56
Насколько я понимаю, в десятичной системе исчисления середины как раз не имеется. Для пяти цифр {0,1,2,3,4} округление производится вниз, а для других пяти {5,6,7,8,9} -- вверх. Если предположить, что в подобных расчетах в реальной практике точных чисел нет вообще (то есть за цифрой, которую мы округляем, идут другие значащие цифры), то арифметическое округление как раз дает нам требуемый результат: при равномерном распределении половина чисел округляется вниз, половина -- вверх. И, по идее, ошибки округления в среднем должны компенсироваться.


Не верно, тут такая штука: округляем мы к числам, оканчивающимся на 0 - к своебразным ямам, оканчивающиеся на (1 2 3 4) и (6 7 8 9) - разные склоны "горы" (для них понятно в какую яму скатываться при округлении), вершиной которой является 5 (а вот для нее не понятно). А вот, например при 9-чной системе счисления, имеем склоны (1 2 3 4) и (5 6 7 8) без цифры на острие.


14-03-2008 11:09
сообщение от автора материала
to Faultfinder:

Спасибо за участие в обсуждении. Если переведете текст на Delphi - погоняю на моих тестах, сообщу результат.


14-03-2008 10:05
Вот, моя ф-я, для банковсокго округления, переделанная из
http://ibase.ru/download/fmtfloat.txt


const int K10[11] = {1,10,100,1000,10000,100000,
                     1000000,10000000,100000000,
                     1000000000,10000000000}
;
long double FormatFloat2(const long double  Value, const int K)
{
__int64 iCel = 0;
__int64 iOst = 0;
long double  ValueRounded = 0;
long double ldTmpValue = 0;
  //отбрасываем все числа до значимого разряда + 1
  ValueRounded =
  RoundTo((Value * K10[K]),-1);//умножили на сто и округлили до 1
  iCel = ((__int64)ValueRounded) * 10;//Целое без последнего разряда * 10
  iOst = ValueRounded * 10;//Целое умноженное на 10
  iOst = iOst % iCel; //остаток
  //Смотрим, если последняя цифра 5 то
  if (iOst == 5)
  {
    //проверяем на четность цифру перед ним
    ldTmpValue = ((long double)iCel) / 100;
    iCel = ((__int64)ldTmpValue) * 10; //Целое без последнего разряда * 10
    iOst = ldTmpValue * 10;
    iOst = iOst % iCel;
    if ((iOst % 2) == 0) //если четное
    {
      //Если четное
      return  ((long double)(__int64(RoundTo((Value * K10[K]),-1))))/K10[K];
    }

  }
return (RoundTo((Value * K10[K]),0))/K10[K];;
}



07-02-2008 07:08
Спасибо, очень полезно.
Я, в свою очередь, по итогам некоторой работы наваял небольшую статью по поводу разных методов округлений и их реализаций в PHP, Excel, OpenOffice. Думаю, эта информация может пригодиться кому-то еще, она доступна по ссылке: http://altsoph.ru/?p=521


24-01-2008 10:30
Большое спасибо за статью
пришлось исспользовать код, похожий на приведенный в сообщении Fisher от 27-09-2007 07:01

и вообще замечательное королевство, захожу сюда регулярно и даже если не нахожу ответа на вопрос, приходит "озарение"  :-)



27-09-2007 08:37
сообщение от автора материала
Да, ошибок в ней не найдено. А в статье ее нет, потому что функция была предложена уже после написания статьи. Чтобы быть в курсе новостей округления, следите за обсуждением ;-)


27-09-2007 07:01
Так что, согласно
http://delphikingdom.com/asp/answer.asp?IDAnswer=52159
нижеследующая переделанная функция работает правильно для арифметического округления? Просто в тестовом приложении переделанный вариант я не нашел...


function SimpleRoundTo(const AValue: Extended; const ADigit: TRoundToRange = -2): Extended;
var
  LFactor: Extended;
begin
  LFactor := IntPower(10, ADigit);
  if AValue < 0 then
    Result := Trunc((AValue / LFactor) - 0.5) * LFactor
  else
    Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;



20-09-2007 05:40
А как поступать если нужно до целого округлить значение периодической дроби, причём вот такой весёленькой: X.555555555555...6
Где гадкая 6-чка получается всегда и только из личных соображений компании-производителя процессора... Резонный вопрос - где здесь замена реальной погрешности округления на статистическую :)


04-06-2007 03:57
сообщение от автора материала
и еще

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


//         0      1 #
//         1      4 ####
//         2     10 ##########
//         3      0
//         5      4 ####
//         6      0
//         8     16 ################
//         9     16 ################
//        11      0
//        13      0
//        15      0
//        17     16 ################
//        20      8 ########
//        23      0
//        26      0
//        30      0
//        34     16 ################
//        39     16 ################
//        45      0
//        52      0
//        60      0

// дальше аналогично



04-06-2007 03:54
сообщение от автора материала
Ошибок стало в три раза меньше (570), и они стали происходить в разные стороны (как вверх, так и вниз, примерно поровну), так что расхождение округленых и неокругленных сумм составило всего -0.04.

Примеры ошибок (мы ведь говорим о бухгалтерском округлении, не так ли?):


//1.Аргумент | 2.Верное значение | 3.Результат теста| 4.Расхождение

0.2950 0.30 0.29 -0.01
1.0550 1.06 1.05 -0.01
1.0650 1.06 1.07 0.01
1.1750 1.18 1.17 -0.01
1.2050 1.20 1.21 0.01
2.0050 2.00 2.01 0.01
2.0250 2.02 2.03 0.01
2.2150 2.22 2.21 -0.01
2.2350 2.24 2.23 -0.01
2.2650 2.26 2.27 0.01
2.2850 2.28 2.29 0.01
2.4750 2.48 2.47 -0.01
2.4950 2.50 2.49 -0.01
2.5050 2.50 2.51 0.01
2.5250 2.52 2.53 0.01
5.0550 5.06 5.05 -0.01
5.0750 5.08 5.07 -0.01
5.0950 5.10 5.09 -0.01
5.1150 5.12 5.11 -0.01
8.1450 8.14 8.15 0.01





28-05-2007 05:23
Вот такая функция должна снизить количество ошибок. Как выясняется проблема в делении если убрать деление то ошибки должны исчезнуть.

function RoundTo(const AValue: Extended; const ADigit: TRoundToRange): Extended;
var
  LFactor: Extended;
  Tmp: Extended;
  Minus: Boolean;
begin
  Minus:=ADigit<0;
  if Minus then
  begin
    LFactor := IntPower(10, -ADigit);
    Tmp:=AValue * LFactor
  end
  else
  begin
    LFactor := IntPower(10, ADigit);
    Tmp:=AValue / LFactor
  end;
  Tmp:=Round(Tmp);
  if Minus then
    Result := Tmp / LFactor
  else
    Result := Tmp * LFactor
end;



28-05-2007 01:28
Написал вот такие 2 функции

function RoundTo(const AValue: Extended; const ADigit: TRoundToRange): Extended;
var
  LFactor: Extended;
  Tmp: Extended;
begin
  LFactor := IntPower(10, ADigit);
  Tmp:=AValue / LFactor;
  Tmp:=Round(Tmp);
  Result := Tmp * LFactor;
end;

function RoundCurrTo(const AValue: Currency; const ADigit: TRoundToRange): Currency;
var
  LFactor: Currency;
  Tmp: Currency;
begin
  LFactor := IntPower(10, ADigit);
  Tmp:=AValue / LFactor;
  Tmp:=Round(Tmp);
  Result := Tmp * LFactor;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(FloatToStr(RoundTo(0.0450, -2))); // Дает не верный результат 0.05
  ShowMessage(CurrToStr(RoundCurrTo(0.0450, -2))); // Дает верный результат 0.04
end;


Разный результат возврощает вот эта строка

  Tmp:=Round(Tmp);


В одном случае 5 в другом 4 хотя выполняется один и тот же код, я не селён в асемблере и работе с FPU, но разници так и не нашел, на входе одни и теже данные на выходе разные. %O


23-05-2007 00:38
сообщение от автора материала
К сожалению, не получается. Нормально округляет 0.0050, 0.0150, 0.0250, 0.0350; но уже на 0.0450 возникает ошибка - должно быть 0.04 (к ближайшему четному), а получается 0.05. Следующая ошибка на 0.1650. Всего на миллионе чисел 1226 ошибок. Все ошибки вверх.

Прослеживается некая закономерность распределения ошибок. Если это даст пищу для размышлений - то вот диаграмма. Первый столбик - целая часть числа, для которой прорабатывались все дробные значения с четырьмя знаками. Второй - количество ошибок для этого диапазона. Третий - наглядное отображение второго.


//_________0_____11_###########
//_________1_____12_############
//_________2_____14_##############
//_________3______9_#########
//_________5_____10_##########
//_________6______9_#########
//_________8_____18_##################
//_________9_____20_####################
//________11______6_######
//________13______9_#########
//________15_____11_###########
//________17_____20_####################
//________20_____12_############
//________23______7_#######
//________26______8_########
//________30_____10_##########
//________34_____20_####################
//________39_____20_####################
//________45______6_######
//________52______8_########
//________60_____10_##########
//________70_____20_####################
//________81_____20_####################
//________94______6_######
//_______110_____10_##########
//_______129_____18_##################
//_______152_____20_####################
//_______179______6_######
//_______212______8_########
//_______251_____12_############
//_______297_____20_####################
//_______354______6_######
//_______421______8_########
//_______502_____12_############
//_______599_____20_####################
//_______717______6_######
//_______858______8_########
//______1028_____18_##################
//______1232_____20_####################
//______1478______6_######
//______1774_____10_##########
//______2131_____18_##################
//______2561_____20_####################
//______3078______8_########
//______3702_____10_##########
//______4453_____20_####################
//______5358______6_######
//______6449______8_########
//______7762_____10_##########
//______9345_____20_####################
//_____11252______6_######
//_____13550______8_########
//_____16318_____12_############
//_____19654_____20_####################
//_____23674______6_######
//_____28517_____10_##########
//_____34353_____18_##################
//_____41385_____20_####################
//_____49859______8_########
//_____60069_____10_##########
//_____72373_____20_####################
//_____87198______6_######
//____105062______8_########
//____126589_____12_############
//____152528_____20_####################
//____183784______6_######
//____221448______8_########
//____266833_____18_##################
//____321521_____20_####################
//____387420______6_######
//____466828_____10_##########
//____562515_____20_####################
//____677817______6_######
//____816756______8_########
//____984178_____10_##########
//___1185920_____20_####################
//___1429020______6_######
//___1721954______8_########
//___2074941_____12_############
//___2500289_____20_####################
//___3012833______6_######
//___3630449_____10_##########
//___4374675_____18_##################
//___5271468_____20_####################
//___6352104______8_########
//___7654269_____10_##########
//___9223378_____20_####################
//__11114154______6_######
//__13392539______8_########
//__16137993_____12_############
//__19446265_____20_####################
//__23432732______6_######
//__28236425______8_########
//__34024874_____18_##################
//__40999956_____20_####################
//__49404929______6_######
//__59532921_____10_##########
//__71737152_____20_####################
//__86443249______6_######
//_104164096______8_########



22-05-2007 07:21
Идею можно развить и на функцию RoundTo

function RoundTo(const AValue: Extended; const ADigit: TRoundToRange): Extended;
var
  LFactor: Extended;
begin
  LFactor := IntPower(10, ADigit);
  SetRoundMode(rmNearest); // На всякий случай
  Result := Round(AValue / LFactor) * LFactor;
end;



22-05-2007 00:54
сообщение от автора материала
Пример правильной функции арифметического округления (Марат Сафин):

http://delphikingdom.com/asp/answer.asp?IDAnswer=52159

function SimpleRoundTo(const AValue: Extended; const ADigit: TRoundToRange = -2): Extended;
var
  LFactor: Extended;
begin
  LFactor := IntPower(10, ADigit);
  if AValue < 0 then
    Result := Trunc((AValue / LFactor) - 0.5) * LFactor
  else
    Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;



19-02-2007 07:14
Fisher, thanx
перенаправляется на http://cc.codegear.com/Item.aspx?id=21909
download после регистрации на codegear


09-02-2007 05:37
сообщение от автора материала
to Алексей:

Проверил функцию Around.

Сразу нужно отметить, что при округлении до двух знаков после запятой ("до копеек") она работает только с числами до 2 миллионов, видимо, из-за переполнения переменных LongInt. Поведение функции с бОльшими значениями очень неприятно: она молча выдает грубо неверные результаты (2200000.00 -> -2094967.29). Учитывая, что 2 млн рублей на предприятии - не такая уж запредельная сумма, становится страшновато.

Таким образом, проверки осуществлялись только до двух миллионов.

Ошибок нашлось достаточно много (3792 штуки), распределение их достаточно равномерное, и все "вниз". Примеры:

1.Аргумент | 2.Верное значение (арифм.округл.) | 3.Результат ARound | 4.Расхождение

(если функция отрабатывала правильно, графы 3 и 4 оставлены пустыми, а показаны только ошибки, для наглядности. То есть 0.0050 верно округляется до 0.01, а из 0.0150 должно быть 0.02, а у Around выходит 0.01, расхождение -0.01).

0.0050 0.01
0.0150 0.02 0.01 -0.01
0.0250 0.03
0.0350 0.04
0.0450 0.05 0.04 -0.01
0.0550 0.06
0.0650 0.07
0.0750 0.08 0.07 -0.01
0.0850 0.09
0.0950 0.10
0.1050 0.11 0.10 -0.01
0.1150 0.12
0.1250 0.13
0.1350 0.14
0.1450 0.15 0.14 -0.01
0.1550 0.16 0.15 -0.01
0.1650 0.17
0.1750 0.18 0.17 -0.01
0.1850 0.19 0.18 -0.01
0.1950 0.20
0.2050 0.21 0.20 -0.01
0.2150 0.22 0.21 -0.01
0.2250 0.23
0.2350 0.24 0.23 -0.01
0.2450 0.25 0.24 -0.01
0.2550 0.26
0.2650 0.27

Несколько лучшие результаты получаются, если тип Double в функции заменить на Extended. Ошибок становится меньше (76), распределение становится эпизодическим. Почему-то "не любит" числа на 8, 129, 1028, 266833. Примеры:

8.0250 8.03 8.02 -0.01
8.0650 8.07 8.06 -0.01
8.1050 8.11 8.10 -0.01
8.1550 8.16 8.15 -0.01
129.0250 129.03 129.02 -0.01
129.0550 129.06 129.05 -0.01
129.1050 129.11 129.10 -0.01
129.1350 129.14 129.13 -0.01
129.1650 129.17 129.16 -0.01
129.2450 129.25 129.24 -0.01
129.2750 129.28 129.27 -0.01
129.3050 129.31 129.30 -0.01
129.3550 129.36 129.35 -0.01
129.3850 129.39 129.38 -0.01
129.4150 129.42 129.41 -0.01
129.4950 129.50 129.49 -0.01
129.5250 129.53 129.52 -0.01
129.5550 129.56 129.55 -0.01
129.6050 129.61 129.60 -0.01
129.6350 129.64 129.63 -0.01
129.6650 129.67 129.66 -0.01
129.7450 129.75 129.74 -0.01

Перепроверьте, пожалуйста, все ли я сделал верно. Для проверки пришлось вернуться к моим почти забытым файлам, так что заранее прошу прощения, что в поиске ошибок тоже возможны ошибки ;-)


04-02-2007 02:06
С год назад, прочитав статью первый раз, сделал вывод "Fisher переработался" :o)
Провалив сдачу проекта на прошлой неделе из-за этих "загадок округления" теперь с удовольствием перечитываю Вашу статью.
Спасибо!


02-02-2007 15:04
сообщение от автора материала
to krutoff:

drRndUp - это вообще не арифметическое округление. Это округление всегда в бОльшую сторону для положительных чисел (10.1341 -> 10.14), и всегда в меньшую сторону для отрицательных; другими словами - округление от нуля. См. мой ответ от 17-01-2007 06:27, последняя строка.


to Алексей: постараюсь погонять Вашу функцию на своих тестах на следующей неделе, о результатах сообщу здесь же.


to Андрей Токинов:

John Herbster at 4/19/2006 4:40:28 AM wrote:

Borland and all Delphi users are welcome to use my decimal rounding routines in module DecimalRounding (JH1) the source of which is available at

http://codecentral.borland.com/Item.aspx?id=21909


02-02-2007 04:45
Полезная статья.

А где бы посмотреть на
unit DecimalRounding_JH1;


24-01-2007 08:55
Когда мы узнали, что в Delphi 5 функция округления работает как-то не так (не арифметическая), я написал свою.

function ARound(Number: Double; Prec: Byte=0): Double;
var IntNumber, Stepen: LongInt;
begin
  Stepen:=Trunc(Power(10,Prec+1));
  IntNumber:=Trunc(Number*Stepen);
  if ((IntNumber mod 10)>=5) then
    Number:=(IntNumber div 10)+1
  else
    Number:=IntNumber div 10;
  Result:=Number*10/Stepen;
end;

Много лет пользуемся, вроде не ошибается.
Успехов.


19-01-2007 03:29
Но и в случае drRndUp
Функция арифм. округления не работает:

DecimalRoundExt(10.1341,2,) = 10.14


17-01-2007 06:27
сообщение от автора материала
>>> DecimalRoundExt(10.1347,2,drHalfUp) = 10.13

Все верно, так и должно быть. Арифметическое округление, старшая отбрасываемая цифра 4, округление вниз.

Если хотелось получить 10.14, то есть округление "к потолку" (Ceil), то надо использовать drRndUp.

Unit DecimalRounding_JH1;

type tDecimalRoundingCtrl =    {The defined rounding methods}
   (drNone,    {No rounding.}
    drHalfEven,{Round to nearest or to even whole number. (a.k.a Bankers) }
    drHalfPos, {Round to nearest or toward positive.}
    drHalfNeg, {Round to nearest or toward negative.}
    drHalfDown,{Round to nearest or toward zero.}
    drHalfUp,  {Round to nearest or away from zero.}
    drRndNeg,  {Round toward negative.                    (a.k.a. Floor) }
    drRndPos,  {Round toward positive.                    (a.k.a. Ceil ) }
    drRndDown, {Round toward zero.                        (a.k.a. Trunc) }
    drRndUp);  {Round away from zero.}




17-01-2007 01:54
Функция арифм. округления не работает:

DecimalRoundExt(10.1347,2,drHalfUp) = 10.13


10-05-2006 15:36
В конце статьи было упоминание про тип currency.
Но это не единственный тип с фиксированным количество знаков после запятой.

Хотел бы заметить, что в дельфи не реализован, но доступен через библиотеку oleaut32.dll (см. в msdn функцию VarDecAdd и другие) тип TDecimal (var type = 14). Он почему-то не реализован в дельфи (в модуле system об этом есть упоминание).

Очень неплохой тип: 18 знаков до запятой, 10 после. В процессе вычислений банковское округление.

Я его использую уже лет 5. Раньше, когда не было перегрузки операторов приходилось писать фукнции или собтвенные реализации типа variant.

Теперь в BDS 2006 есть перегрузка операторов для записей. Вообще класс.


28-04-2006 10:04
сообщение от автора материала
10.135 как по арифметическому, так и по банковскому правило должно округляться до 10.14

Проверил на D7 Build 4.453 на Win2KServer SP4: RoundTo и SimpleRoundTo врут - 10.13. DecimalRoundExt работает верно (и drHalfEven и drHalfUp).


28-04-2006 07:43
Полностью согласен что в данном случае они ошибки не внесли но ведь могут. И возможно какой то другой вариант перевода в строку может дать глюк.

Кстати обнаружились проблемы при округлении исспользуя функции из модуля DecimalRounding_JH1.pas :( на машине с D7 без UpDate + Win2000. В чём баг выясняем. Но машина мне не доступна лично. Так что если проблема будет найдена сообщу. Ошибка в округлении до 2 знака числа 10.135


28-04-2006 06:20
сообщение от автора материала
Результат в текст выводился с помощью FloatToStrF(nRes, ffFixed, 15, 2). Я считаю, что она не могла исказить результат, потому что даже если аргумент представлен "слегка неточным" значением (с учетом особенностей Floating-point - формата), то FloatToStrF сама по себе "доокруглит" результат до верного значения. Если вы заметили, сами функции FloatToStrF и StrToFloat тоже участвовали в тесте, и показали правильный результат для арифметического округления.


28-04-2006 06:07
Есть мнение что нужно указывать пару функций на которой проводилось тестирование. Одна функция которая округляет число и вторая которая выводит результат. Ведь они обе могут работать не так как нам бы того хотелось?


25-04-2006 14:14
Все, врубился. Спасибо.

Мораль: используйте только иррациональные числа. В крайнем случае -- периодические десятичные дроби с периодом отличным от нуля ;-)
 Geo


25-04-2006 14:07
сообщение от автора материала
при равномерном распределении половина чисел округляется вниз, половина -- вверх. И, по идее, ошибки округления в среднем должны компенсироваться.

Как раз наоборот. Вы забываете, что среди десяти цифр есть одна волшебная - ноль. При округлении (отбрасывании) нуля, погрешности (разницы между округленным и неокругленным значением) вообще не возникает. Если же рассмотреть цифры, дающие при отбрасывании погрешность, и какую именно погрешность, и кто кого компенсирует, то имеем:

Отбрасывемая | Погрешность | (Кем компенсируется)
цифра

x.1  - 0.1 (девяткой)
x.2  - 0.2 (восьмеркой)
x.3  - 0.3 (семеркой)
x.4  - 0.4 (шестеркой)

x.5  + 0.5  (---) ... отсюда начинается округление вверх

x.6  + 0.4 (четверкой)
x.7  + 0.3 (тройкой)
x.8  + 0.2 (двойкой)
x.9  + 0.1 (единицей)

Вот и получается наличие самой настоящей середины, никем не скомпенсированной.


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

Разумеется, если за пятеркой идут другие ненулевые цифры, то вопроса не возникает. Беда в том, что этой цифрой (цифрами) могут оказаться нули (почему бы и нет). И тогда это число будет стоять точно между верхней и нижней "целями" округления, и возникнет вопрос: вверх? вниз? попеременно?


25-04-2006 11:20
Ну ничего себе исследование! Одобрям-с!

Немного непонятен один момент. <...> чтобы какой-нибудь саблезубый тигр откусил руку, или хотя бы нечетное количество пальцев нашему волосатому пращуру, придумавшему десятичную систему счисления, чтобы в ней не осталось "середины". Насколько я понимаю, в десятичной системе исчисления середины как раз не имеется. Для пяти цифр {0,1,2,3,4} округление производится вниз, а для других пяти {5,6,7,8,9} -- вверх. Если предположить, что в подобных расчетах в реальной практике точных чисел нет вообще (то есть за цифрой, которую мы округляем, идут другие значащие цифры), то арифметическое округление как раз дает нам требуемый результат: при равномерном распределении половина чисел округляется вниз, половина -- вверх. И, по идее, ошибки округления в среднем должны компенсироваться.

На практике, конечно, надеятся на компенсацию погрешности не следует. Если в кассе есть деньги, то они должны быть распределены, чтобы хватило всем и ничего не осталось.

Я в свое время выкрутился следующим интересным образом. Продовали мы одни наш отчет. Комплект блоков отчета на продажу собирался под заказчика. У каждого блока был свой КТУ для тех, кто его разрабатывал, плюс определенные отчисления по самому факту продаж (например, накладные расходы). То есть распределение денег, полученных за отчет, было нетривиальным. Было принято (мной :-) решение распределять деньги с точностью до рубля (округляя вниз), а остаток приплюсовывать к фонду развития (специальная сумма, в которую шел определенный процент от продажи каждого отчета, для финансирования будущих разработок). Все были довольны.

С налоговой инспекцией, естественно, такой вариант не прокатит. Для них лучше округлить в большую сторону. Хай им будет хорошо ;-)
 Geo


Добавьте свое cообщение

Вашe имя:  [Войти]
Ваш адрес (e-mail):На Королевстве все адреса защищаются от спам-роботов
контрольный вопрос:
Кто съел Красную шапочку?
в качестве ответа на вопрос или загадку следует давать только одно слово в именительном падеже и именно в такой форме, как оно используется в оригинале.
Надоело отвечать на странные вопросы? Зарегистрируйтесь на сайте.

Оценка содержания
 
Содержит полезные и(или) интересные сведения
 
Ничего особенно нового и интересного
 
Написано неверно (обязательно укажите почему)


Оценка стиля изложения
 
Все понятно, материал читается легко
 
Есть неясности в изложении
 
Непонятно написано, трудно читается

Текст:
Жирный шрифт  Наклонный шрифт  Подчеркнутый шрифт  Выравнивание по центру  Список  Заголовок  Разделительная линия  Код  Маленький шрифт  Крупный шрифт  Цитирование блока текста  Строчное цитирование
  • вопрос Круглого стола № XXX

  • вопрос № YYY в тесте № XXX Рыцарской Квинтаны

  • сообщение № YYY в теме № XXX Базарной площади
  • обсуждение темы № YYY Базарной площади
  •  
     Правила оформления сообщений на Королевстве
      
    Время на сайте: GMT минус 5 часов

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

    Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
    Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

     
    © При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
    Все используемые на сайте торговые марки являются собственностью их производителей.

    Яндекс цитирования