| | | | |
Полный текст материала
Другие публикации автора: Алексей Михайличенко
Цитата или краткий комментарий: «... В этой статье поясняется, чем отличается бухгалтерское округление от арифметического, какое из них реализовано в Excel'e, и почему некоторые Delphi—функции странным образом работают иначе.
...» |
Важно:- Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
- Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
- При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
- Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.
Добавить свое мнение.
| | Содержит полезные и(или) интересные сведения | [1] | 15 | 93.8% | | | | Ничего особенно нового и интересного | [2] | 1 | 6.2% | | | | Написано неверно (обязательно укажите почему) | [3] | 0 | 0% | | Всего проголосовали: 16 | | | Все понятно, материал читается легко | [1] | 14 | 100% | | | | Есть неясности в изложении | [2] | 0 | 0% | | | | Непонятно написано, трудно читается | [3] | 0 | 0% | | Всего проголосовали: 14 |
[Математические функции]
Отслеживать это обсуждение
Всего сообщений: 4312-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
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] = ;
long double FormatFloat2(const long double Value, const int 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сообщение от автора материала и еще
Диаграмма распределения ошибок. Опасность в том, что большие участки безошибочных значений при выборочном тестировании могут вселить ложную уверенность в правильности работы функции в целом.
|
|
04-06-2007 03:54сообщение от автора материала Ошибок стало в три раза меньше (570), и они стали происходить в разные стороны (как вверх, так и вниз, примерно поровну), так что расхождение округленых и неокругленных сумм составило всего -0.04.
Примеры ошибок (мы ведь говорим о бухгалтерском округлении, не так ли?):
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)));
ShowMessage(CurrToStr(RoundCurrTo(0.0450, -2)));
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 ошибок. Все ошибки вверх.
Прослеживается некая закономерность распределения ошибок. Если это даст пищу для размышлений - то вот диаграмма. Первый столбик - целая часть числа, для которой прорабатывались все дробные значения с четырьмя знаками. Второй - количество ошибок для этого диапазона. Третий - наглядное отображение второго.
|
|
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
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 =
(drNone,
drHalfEven,
drHalfPos,
drHalfNeg,
drHalfDown,
drHalfUp,
drRndNeg,
drRndPos,
drRndDown,
drRndUp);
|
|
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Все, врубился. Спасибо.
Мораль: используйте только иррациональные числа. В крайнем случае -- периодические десятичные дроби с периодом отличным от нуля ;-) |
|
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} -- вверх. Если предположить, что в подобных расчетах в реальной практике точных чисел нет вообще (то есть за цифрой, которую мы округляем, идут другие значащие цифры), то арифметическое округление как раз дает нам требуемый результат: при равномерном распределении половина чисел округляется вниз, половина -- вверх. И, по идее, ошибки округления в среднем должны компенсироваться.
На практике, конечно, надеятся на компенсацию погрешности не следует. Если в кассе есть деньги, то они должны быть распределены, чтобы хватило всем и ничего не осталось.
Я в свое время выкрутился следующим интересным образом. Продовали мы одни наш отчет. Комплект блоков отчета на продажу собирался под заказчика. У каждого блока был свой КТУ для тех, кто его разрабатывал, плюс определенные отчисления по самому факту продаж (например, накладные расходы). То есть распределение денег, полученных за отчет, было нетривиальным. Было принято (мной :-) решение распределять деньги с точностью до рубля (округляя вниз), а остаток приплюсовывать к фонду развития (специальная сумма, в которую шел определенный процент от продажи каждого отчета, для финансирования будущих разработок). Все были довольны.
С налоговой инспекцией, естественно, такой вариант не прокатит. Для них лучше округлить в большую сторону. Хай им будет хорошо ;-) |
|
|
|