| | | | |
Загадки округления. Тестирование функций округления различных языков. | Полный текст материала
Другие публикации автора: Алексей Михайличенко
Цитата или краткий комментарий: «... Чтобы поставить всех тестируемых в равные условия, вся работа велась через текстовые файлы. От программы требовалось считать файл с исходными данными, округлить числа, и построчно записать результат в другой файл. Таким образом, особенности внутреннего представления вещественных чисел на данном языке и соответствующих преобразований оставались на совести самой тестируемой программы.
...» |
Важно:- Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
- Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
- При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
- Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.
Добавить свое мнение.
| | Содержит полезные и(или) интересные сведения | [1] | 7 | 100% | | | | Ничего особенно нового и интересного | [2] | 0 | 0% | | | | Написано неверно (обязательно укажите почему) | [3] | 0 | 0% | | Всего проголосовали: 7 | | | Все понятно, материал читается легко | [1] | 6 | 85.7% | | | | Есть неясности в изложении | [2] | 1 | 14.3% | | | | Непонятно написано, трудно читается | [3] | 0 | 0% | | Всего проголосовали: 7 |
[Математические функции]
Отслеживать это обсуждение
Всего сообщений: 2302-11-2009 03:55Кстати, да, хоть я и использую переменную типа Extended, но я ей присваиваю результат деления чисел, взятых из полей объекта TQuery. Вполне возможно, что там тип Double и хранится он вот так. Но это уже оффтоп.
Простите, что сбаламутил. Джоново округление тоже верное в пределах "правильных цифр" числа :-)
|
|
30-10-2009 08:13Что я делаю не так?
Не тот тип используете :)
В числе 4782.52499999999983 всего 16 правильных цифр, т.е. это не Extended, а Double. |
|
30-10-2009 07:59сообщение от автора материала Пробую.
procedure TForm1.Button1Click(Sender: TObject);
var x, y, z, z2: extended;
begin
x := 9565.05;
y := 2;
z := x / y;
Edit1.Text := FloatToStr(z);
z2 := DecimalRoundExt(z, 2, drHalfUp);
Edit2.Text := FloatToStr(z2);
end;
Что я делаю не так?
|
|
30-10-2009 06:522 Fisher:
Математически - это результат деления двух чисел, и он равен точно 4782.525, а хранится в переменной как 4782.52499999999983.
Проблема ведь при округлениях только с этим. Округлить правильно ровно 4782.525 почти все могут.
|
|
28-10-2009 06:29сообщение от автора материала to VovaVV: Не совсем понял про это ext: каким оно должно быть, и когда + а когда -? Просьба оформить этот код в виде готовой функции для округления до двух знаков после запятой.
И про 4782.52499999999983 - это число по всем правилам должно округляться вниз, до 4782.52. При чем же здесь John Herbster? |
|
28-10-2009 04:17Я для себя давно написал функцию, которая пока меня не подводила:
Result := Trunc(ValExt) + Trunc(Frac(ValExt)*2);
где ValExt = Value * Scale +/- ext;
где Value - округляемое значение
Scale - множитель, зависящий от необходимой точности округления (f.e. точность: 2 знака после запятой, множитель = 100),
ext - прибавка, гарантированно меняющая разряд в "проблемных" числах (можно ставить вплоть до = 0.09),
ext прибавлять или вычитать - зависит от знака Value
Просьба автору материала потестировать её тоже, если есть желание.
|
|
28-10-2009 03:48Правда, придется избавляться от "усердности" округления (1.42499 должно быть все-таки 1.42) |
|
28-10-2009 03:42Исходники качал вчера с Borland DevNetwork (файл DecimalRounding_JH1.pas 14046 байт от 15.06.05)
Пробовал ставить Sеt8087CW, как рекомендовали ниже, результат одинаковый.
Да оно и понятно: число 4782.525 приходит в переменной типа extended, хранится оно (у меня) на самом деле как 4782.52499999999983, а перед округлением
drHalfUp: {Round to nearest or away from zero.}
i64 := round((abs(ScaledVal) + ScaledErr));
Джон прибавляет мизерное число ScaledErr, которое третью после запятой 4ку не делает 5кой и все выкрутасы тут бесполезны.
Функция округления будет работать всегда правильно, если ScaledErr будет не мизером типа 10e-19, а 0.1e-"Точность округления", чтобы гарантированно "переворачивать" следующий разряд. |
|
27-10-2009 12:43Не все так радужно и у DecimalRoundExt(n, 2, drHalfUp) by John Herbster:
Проверьте какая у вас версия функции, а то он её обновляет по мере нахождения багов. |
|
27-10-2009 11:50>>> Не все так радужно и у DecimalRoundExt
А вы автору это отправляли? |
|
27-10-2009 11:31сообщение от автора материала Хм. А у меня получается 4782,53. Проверял инспектором в точке останова. D7.
|
|
27-10-2009 08:38Не все так радужно и у DecimalRoundExt(n, 2, drHalfUp) by John Herbster:
DecimalRoundExt(4782.525, 2, drHalfUp)=4782.52, а должно быть 4782.53 (мат.округление)
|
|
25-06-2009 12:21На алгоритм Джона Хербстера влияет значение FPU Control Word , вернее свойство Precision (т.е. длина мантиссы - 24, 52, 64бита)- надо чтобы точность соответствовала той функции округления, которую вы используете - короче ставьте 64 бита. Надо перед вычислениями каждый раз выставлять контрольное слово Sеt8087CW(например $1332) т.к. сопроцессор один на всю систему и любая свистелка может его сбить. |
|
11-07-2007 05:58Небольшоепримечание для Delphi:
- ошибка округления зависит от типа данных:
StrToFloat(FloatToStrF(...
Trunc(b * 100 + 0.5) / 100
работают нормально на данных типа Currency,
для данных типа Double дают ошибочные значения
|
|
06-05-2006 04:11сообщение от автора материала В строке 2 в графе 4 стоит "Б(?)", это означает что в этой строке мы пытаемся сравнивать функцию sprintf с алгоритмом банковского округления. А это и есть округление до четного: 2.5 -> 2.0, 3.5 -> 4.0
Так как в perldoc не указано, какой именно алгоритм реализуется sprintf (и даже явно рекомендуется использовать другие функции, если это важно), то мы сравниваем sprintf и с арифметическим, и с банковским округлением - чтобы убедиться, что она не является ни тем ни другим.
perlfaq4 - Data Manipulation
Rounding in financial applications can have serious implications, and the rounding method used should be specified precisely. In these cases, it probably pays not to trust whichever system rounding is being used by Perl, but to instead implement the rounding function you need yourself.
To see why, notice how you'll still have an issue on half-way-point alternation:
for ($i = 0; $i < 1.01; $i += 0.05)
0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
0.8 0.8 0.9 0.9 1.0 1.0
Don't blame Perl. It's the same as in C. IEEE says we have to do this. Perl numbers whose absolute values are integers under 2**31 (on 32 bit machines) will work pretty much like mathematical integers. Other numbers are not guaranteed. |
|
06-05-2006 03:34Непонял, с каких пор
0.0250 почему должно быть 0.02
0.0650 --- 0.06
встроенная, sprintf("%3.2f") п. 2
|
|
26-04-2006 00:33сообщение от автора материала Sorry, недочитал. Для Floating типов оно осталось как и раньше на совести ОС-библиотек, но MySQL почему-то убежден, (наверное, в соответствии с IEEE-754, наивный), что большинство библитотек сделают бухгалтерское округление.
|
|
26-04-2006 00:25сообщение от автора материала Правда, выгрузку делал черех
SELECT * INTO OUTFILE
Может быть, из-за этого получилась такая точность?
Нет, конечно ;-) Просто они это ИСПРАВИЛИ!!!, о чем они кричат, дудят и машут разноцветными флагами на каждом углу своего MySQL 5.0 Reference Manual. Смотрим:
MySQL 5.0 Reference Manual :: 12 Functions and Operators :: 12.4 Numeric Functions :: 12.4.2 Mathematical Functions (http://dev.mysql.com/doc/refman/5.0/en/mathematical-functions.html)
Нужно также прочесть:
MySQL 5.0 Reference Manual :: 21 Precision Math, (http://dev.mysql.com/doc/refman/5.0/en/precision-math.html)
в частности 21.4 Rounding Behavior (http://dev.mysql.com/doc/refman/5.0/en/precision-math-rounding.html)
Короче говоря, с 5.0.3 они забрали реализацию математики под себя, и очень этим гордятся. Для "точных" типов (INTEGER, DECIMAL, NUMERIC) округление идет арифметическое. Для Floating - бухгалтерское.
Before MySQL 5.0.3, the behavior of ROUND() when the argument is halfway between two integers depends on the C library implementation. Different implementations round to the nearest even number, always up, always down, or always toward zero. If you need one kind of rounding, you should use a well-defined function such as TRUNCATE() or FLOOR() instead.
As of MySQL 5.0.3, ROUND() uses the precision math library for exact-value arguments when the first argument is a decimal value:
* For exact-value numbers, ROUND() uses the “round half up” or “round toward nearest” rule: A value with a fractional part of .5 or greater is rounded up to the next integer if positive or down to the next integer if negative. (In other words, it is rounded away from zero.) A value with a fractional part less than .5 is rounded down to the next integer if positive or up to the next integer if negative.
* For approximate-value numbers, the result depends on the C library. On many systems, this means that ROUND() uses the "round to nearest even" rule: A value with any fractional part is rounded to the nearest even integer.
|
|
25-04-2006 17:39В общем, так... Тестировал на MySQL 5.0.16-nt ОС Windows-2000
Получил арифметическое округление без отклонений для:
ROUND(n1,2)
TRUNCATE(n1+0.005,2)
Правда, выгрузку делал черех
SELECT * INTO OUTFILE
Может быть, из-за этого получилась такая точность? |
|
25-04-2006 14:22сообщение от автора материала Он создаст один миллион значений, но последним значением будет 104,164,096.9999
По поводу MySQL, я тестировал так:
--run as root
CREATE DATABASE testround;
GRANT ALL PRIVILEGES ON *.* TO fisher IDENTIFIED BY '123';
--run as fisher
use testround;
CREATE TABLE test1 (
n1 DECIMAL(14,4) NOT NULL PRIMARY KEY,
n2 DECIMAL(14,2) NOT NULL
);
Файл DATA.TXT переименовать в TEST1.TXT и загрузить так:
mysqlimport -c N1 --fields-terminated-by=, --local -d -v -p123 -u fisher testround test1.txt
Затем собственно округляем:
UPDATE test1 SET n2 = ROUND(n1, 2);
Затем выгружаем результат:
mysql -u fisher -p123 < select2.sql > result_mysql.txt
где select2.sql содержит
USE testround;
SELECT * FROM test1;
Убираем заголовок и итог, и сравниваем result_mysql.txt с образцовым файлом, можно просто по fc.
Образцовые файлы формируются так:
Для арифметического округления:
#!perl -w
use strict;
open(FD, "data.txt");
open(FR, ">std_a.txt");
while(<FD>) {
chomp;
my $r = okr_a($_);
print FR "$_ $r\n";
}
close(FR);
close(FD);
sub okr_a {
my $n1 = shift;
my $pos_comma = index($n1, '.');
my $res = substr($n1, 0, $pos_comma + 3);
my $rounded_digit = substr($n1, $pos_comma + 3, 1);
if($rounded_digit >= 5) {
$res = sprintf("%3.2f", ($res + 0.01));
}
return $res;
}
Для бухгалтерского:
#!perl -w
use strict;
open(FD, "data.txt");
open(FR, ">std_b.txt");
while(<FD>) {
chomp;
my $r = okr_b($_);
print FR "$_ $r\n";
}
close(FR);
close(FD);
sub okr_b {
my $n1 = shift;
my $pos_comma = index($n1, '.');
my $res = substr($n1, 0, $pos_comma + 3);
my $prerounded_digit = substr($n1, $pos_comma + 2, 1);
my $rounded_digit = substr($n1, $pos_comma + 3, 1);
my $afterrounded_digits = substr($n1, $pos_comma + 4, 1);
my $prerounded_digit_is_odd = (($prerounded_digit % 2) != 0);
my $afterrounded_digits_presents = ($afterrounded_digits > 0);
if (
($rounded_digit > 5)
or
(
($rounded_digit == 5)
and
($prerounded_digit_is_odd or $afterrounded_digits_presents)
)
)
{
$res = sprintf("%3.2f", ($res + 0.01));
}
return $res;
}
PS. По концепции ранних MySQL, он доверял политику округления системным библиотекам, поэтому я подозреваю, что результат будет зависеть от операционки. Если есть возможность, попробуй на разных ОС.
|
|
25-04-2006 14:01Может быть, я не очень хорошо разбираюсь в Perl, но, вроде бы, приведенный код будет создавать всего 1 миллион значений (100 * 10000). Где еще 2 порядка? ;-)
Я просто хочу попробовать (если время позволит) MySQL версии 5.0 (которая у меня сейчас стоит). Но для чистоты эксперимента хочу проверить на тех же самых данных (насколько это возможно). |
|
25-04-2006 13:47сообщение от автора материала Разумеется, числа идут не все подряд. Вообще, сначала я тестировал подряд миллион чисел от 0.0000 до 100.0000, и в принципе получил все что хотел. Но на некоторых функциях ошибки проявлялись лишь на больших числах, поэтому хотелось, сохранив плотное тестирование малых значений, слегка заглянуть и в большие. В окончательном варианте тестовых данных целая часть растет по экспоненте, но для каждого значения целой части отрабатываются все значения дробной от x.0000 до x.9999
Короче говоря:
#!perl -w
open(F, ">data.txt");
foreach my $i (0..99)
}
close(F);
|
|
25-04-2006 12:18Не уверен в чистоте эксперимента (как минимум, для самоделок, основанных на прибавлении бесконечно малого), так как на других задачах округления (до другой цифры) результаты исследования могут отличаться.
Впрочем, если тестирование проводилось для определения эффективности функций при бухгалтерских расчетах, тогда замечание снимается.
Кстати, не совсем понял методику построения файла с исходными данными. Вроде бы чисел 100 миллионов, а в то же время приведенные чсила отличаются друг от друга на 0.0001, в то время как максимальное число равно 104164096.9999. Если понимать тупо в лоб, то должно получиться больше 10^12 чисел. Можно этот момент поподробнее? Чтобы тестировать другие функции на тех же самых данных. |
|
|
|