| | | | |
Неочевидные особенности вещественных чисел | Полный текст материала
Другие публикации автора: Антон Григорьев
Цитата или краткий комментарий: «... Статья ориентирована на начинающих, но содержит
некоторые нетривиальные сведения; в частности, почему в системах Windows 9x
вычисления с типом Extended производятся с точностью типа Double
'Когда-то описание внутреннего представления таких чисел было неотъемлемой частью любой сколь-нибудь серьёзной книги по программированию, но сейчас у авторов появились более интересные предметы для обсуждения: COM/DCOM, ActiveX, OLE и многое другое. На вещественные числа просто не хватает места. И люди, начавшие программирование с Delphi и не имеющие опыта работы в более старых средах, часто оказываются совершенно беспомощными перед непонятным поведением программы, содержащей дробные вычисления ...' ...» |
Важно:- Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
- Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
- При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
- Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.
Добавить свое мнение.
| | Содержит полезные и(или) интересные сведения | [1] | 58 | 98.3% | | | | Ничего особенно нового и интересного | [2] | 0 | 0% | | | | Написано неверно (обязательно укажите почему) | [3] | 1 | 1.7% | | Всего проголосовали: 59 | | | Все понятно, материал читается легко | [1] | 42 | 91.3% | | | | Есть неясности в изложении | [2] | 4 | 8.7% | | | | Непонятно написано, трудно читается | [3] | 0 | 0% | | Всего проголосовали: 46 |
[Вещественные числа]
Отслеживать это обсуждение
Всего сообщений: 7701-07-2019 03:46Во-первых, спасибо автору за познавательную и толково написанную статью. Во-вторых, наткнулся на одну ошибку в тексте. Считаю полезным привести подробности по этой теме для других читателей. В статье сказано, что если поле PC (биты 8 и 9) регистра CWR указывают на то, что вычисления надо производить в четырёх- или восьмибайтовом форматах (форматах Singlе или Double), то по сравнению с Extended возрастает скорость в ущерб точности. Это не так. Преимущества в скорости не будет. Дело в том, что для вычислений различных математических функций используются жёстко предопределённые внутренние алгоритмы, поэтому количество требуемых вычислительных затрат от изменения разрядности представления чисел не зависит. Такая возможность сделана в большей степени для нужд отладки - чтобы программист мог получить в точности тот же результат, какой он получил бы, если бы для хранения промежуточных результатов вычислений вместо регистров сопроцессора использовал переменные в памяти типов Single или Double. Преимущество этих типов перед Extended достигается только за счёт экономии на пересылке данных в оперативной памяти. Более ощутимое преимущество в скорости от использования типов Single или Double можно получить, если вместо команд FPU использовать команды из наборов SSE. Правда, там есть только простейшие арифметические операции. |
|
31-05-2010 20:55Есть ещё подводные камни с этими "противными" вещественными числами
Например, "от перемены мест слагаемых сумма изменяется"
а виновато выравнивание порядков
Да, таких мест может быть много. На всякий случай поясню ваш пример. Вещественное число хранит фиксированное число разрядов, а экспонента, по сути дела, указывает, в каком диапазоне лежат эти разряды. Т.е., например, 23 бита мантиссы могут хранить 23 двоичных разряда в диапазоне от 2^10 до 2^-42, а могут - от 2^-80 до 2^-132 - это зависит от значения экспоненты. Другими словами, в большом диапазоне возможных значений есть относительно небольшое "окно", которое может перемещаться по этому диапазону. Верхняя граница этого окна выравнивается на старший разряд. Соответственно, младшие разряды, отстоящие слишком далеко от старшего, не попадают в окно и отбрасываются.
В первом варианте мы изначально работаем с маленькими числами, поэтому окно выровнено так, чтобы вмещать в себя их с достаточной точностью. За счёт многократных сложений число увеличивается на 6 десятичных порядков (около 20 двоичных), но так как каждый раз прибавляется маленькое число, окно перемещается постепенно, и существенной потери точности не происходит. И когда мы прибавляем сумму к большому числу, они оба умещаются в одном окне.
Во втором случае мы начинаем вычисление сразу с большого числа, в результате чего окно оказывается сдвинуто далеко в область старших разрядов. Те маленькие числа, которые мы добавляем к большому на каждой итерации, просто не помещаются в это окно (т.е. имеем тот же эффект, что и в случае машинного эпсилон, только маленькое число прибавляется не к единице, а к миллиону). Соответственно, это маленькое число просто игнорируется, что и приводит к большой потере точности.
Есть и более простое объяснение:
в типе Single под мантиссу отведено 23 бита, то есть там может быть 7-значное число,
а при операции 1000000+0.000001=1000000.000001 получается 13-значное, младшие разряды отсекаются
Вообще (ИМХО) выход из всей этой белиберды - это BCD числа, с их помощью можно получить просто умопомрачительную точность, какая Extended'у и не снилась в самой смелой фантазии. Другое дело конешно скорость таких вычислений. |
|
21-06-2009 10:16Очень интересная статья, спасибо.
На прочтение (повторное) сподвигнула проблема с RoundTo. Когда имеем значение Double как 1,4676311039e+300, и точность +4.-4, то RoundTo выкидывает 'Invalid floating point operation'.
Сталкивался ли кто еще с такой проблемой?
Саша
|
|
11-06-2009 01:15Интересная статья. Но как-то не натыкался на ошибки, связанные с особенностями представления вещественных чисел. Но вот набрел на такую неприятную штуку :) :
procedure TForm1.Button1Click(Sender: TObject);
var
x: Integer;
y: Integer;
z: Extended;
XdivYmultZ: Extended;
res: integer;
begin
x := 15;
y := 55;
z := 55;
XdivYmultZ := y * ( x / z );
res := Trunc( XdivYmultZ );
end;
На строке вычисления XdivYmultZ поставить breakpoint и по Alt+F5 изменить значение "z" опять же на 55 (значение этой переменной для этого примера, по сути, в присваивании можно не задавать) (у меня в программе при вычислениях получалось логически целое значение z=55, а в компьютере где-то терялись младшие разряды).
"XdivYmultZ" будет равно 14.9999999...82. "res", соотвественно, 14, а это уже не логично, тогда как 14.99999... вроде бы и устраивало, и для дальнейших вычислений сошло бы за 15
решил положить этот пример сюда. |
|
24-12-2008 10:38М-дя... Антон, спасибо за ссылку. Дочитать до конца, правда, не хватило сил, но... Честно говоря, впервые стало стыдно за свой родной институт :( Как-то не приходилось мне пока встречать однокашников, которые вместо знаний демонстрировали бы корочки Физтеха.
Утешает только то, что поскольку автор напирает на то, что он физик, то, скорее всего, он не с Факультета Управления и Прикладной Математики. А значит, наверное, не имел в программе столько курсов по вычислительной математике. Иначе бы знал, что и ракеты летают как надо, и физичесике процессы моделируются как надо. А уж деньги посчитать -- вообще не проблема. И все это нормально рассчитывалось даже на БЭСМ-6. Только нужно считать уметь.
Хотя... Подумалось вот... Нам с певрого курса вбивали, что в физике ничего точно измерить нельзя. Всегда есть какая-то погрешность. Вопрос к физику Юровицкому: А как же мы можем построить, например, мост, если не можем точно посчитать расстояние между двумя берегами? Если Вы ответите на это вопрос, то, наверное, сможете понять, как можно пользоваться компьютерными мчислами с плавающей точкой ддля получения нужного результата. И бабушке не придется отрезать от килограмма колбасы 333.3333333333333333333333333 грамма :D
Впрочем, бабушки безо всякой высшей математики делят батон колбасы на любое наперед заданное количество частей. И довольно точно ;-) |
|
24-12-2008 07:37сообщение от автора материала Тем, кто заинтересовался сообщением В. Юровицкого, напомню, что я уже приводил ссылку на его "труды" в своём сообщении от 02-05-2007 05:53. Повторяю ссылку: http://elementy.ru/blogs/users/vladyur/10157/ Там интересен не столько текст, сколько комментарии к нему. Особенно ответы Юровицкого. Например, его оппонент вполне корректно разбирает несколько явных ляпов в тексте, а Юровицкий в ответ, вместо контраргументов, говорит следующее:
Юрий, хамство, конечно, запретить трудно. Ваши ухмылочки я как программист со стажем более двадцати лет, выпускник МФТИ, ученик лвуреатов Нобелевской премии Ландау и Капицы я не могу принять. А объяснять вам то, что поймет любой студент, у меня как-то нет желания. И в вашей помощи я, думается, никак не нуждаюсь, так как мне нужны программисты, но несколько более квалифицировапнные.
Личные выпады и размахивания регалиями вместо ответа по существу. И так везде. Не ведитесь на слова Юровицкого, это не тот человек, к чьему мнению следует прислушиваться.
|
|
24-12-2008 02:26Уважаемый Антон Григорьев.
Ваша статья наглядно показывает, что современное компьютерное исчисление вещественных чисел смертельно опасно для нынешней цивилизации, в которой именно компьютер управляет всем - атомными станциями и туалетами.
В чем смысл вашей статьи? Модели, используемые для вычислений на компьютере как правило на множестве действительных чисел. А компьютер их вычисляет на множестве компьютерных. И расхождение может быть сколь угодно велико. Вам стоило бы показать, как из этих "малых" ошибок могут возникать "очень большие". Действительно, стоит разделить одну ошибку на другую и вы получае6те результат абсолютно непредсказуемый. И как вы можете предотвратить такие вещи? Конечно, ВЫ может и смогли бы. Но сейчас програмированием занимаются миллионы людей. И требовать от всех столь высокой грамотности просто нереально.
Таким образом, современный компьютер может давать сколь угодно большие ошибки. И эти вычислительные ошибки практически непредсказуемы. И сколько аварий, катастроф и происшествий связано с вычислительными ошибками мы просто не знаем. Их обычно списывают на "чедовеческий фактор".
Компьютерная технология пришла в опасный тупик. Нужна новая компьютерная вычислительная технология. Об этом см.http://yur.ru/Conference/Cipros/index.htm · Текст выступления Юровицкого В.М. Доклад «Общая теория чисел и числовых эпох» |
|
08-12-2008 09:38Хорошая статья. Если б только на пару лет раньше на глаза попалась... |
|
09-11-2008 10:52При работе с Vista столкнулся с необходимостью использовать Set8087CW(Get8087CW or $0100)!
Дело в том, что при вычислении расстояния между двумя близкими точками по их GPS-координатам получал 0 на Delphi 7 в то время как на JavaScript правильное значение - 340 метров. Пока не прочитал статью, не мог добиться правильного результата на Delphi...
Спасибо, уважаемый Антон Григорьев! |
|
30-08-2008 06:35to Антон Григорьев
Легко видеть, что если бы всё было точно, то A-B дало бы 0.01 и C стало бы равным 0, чего не происходит.
..
Округление обычно нужно только при выводе на экран - ну так и используйте функцию FloatToStrF или Format, а округлять само число в двоичном представлении я смысла не вижу.
Приводя свою функцию я сразу оговорился что для инженерных целей она не годится. Она написана исключительно для финансовых задач - т.е. расчеты разных начислений, остатков, комиссий, процентов и проч. Тут ошибка даже на копейку приводит к "разрыву баланса", что недопустимо.
В приведенном вами примере, для получения корректного результата необходимо опять округлить значение передаваемое в переменную C и все будет именно так как ожидалось.
var
A, B, C: Extended;
begin
A := Okrugl(1.111111111, 2);
B := Okrugl(1.111111111, 1);
C := Okrugl(100* (A - B) - 1, 4);
Label1.Caption:=FloatToStr(C);
end;
|
|
24-07-2008 03:17сообщение от автора материала Andy1618:
А чем вас не устраивает такой абзац, который есть в статье?
Процессор Pentium во всех своих вариантах имел встроенный сопроцессор. Таким образом, с приходом этого процессора тип Real стал как бы обузой, а на передний план вышли Single, Double и Extended. Чтобы свести к минимуму необходимые переделки программ, Borland ввела новую директиву компилятора: {$REALCOMPATIBILITY ON/OFF}. По умолчанию стоит OFF, что означает отсутствие полной совместимости. В этом случае тип Real в Delphi совпадает с типом Double. Если же совместимость включена, тип Real совпадает со своим прообразом из Паскаля. Существует ещё тип Real48, который всегда, вне зависимости от настроек, совпадает со старым Real. Далее в этой статье под словом “Real” я всегда буду подразумевать старый тип. Отмечу, что всё это появилось только в Delphi 4, в более ранних версиях тип Real48 отсутствовал, а тип Real был всегда старым, шестибайтным. |
|
21-07-2008 03:18Встретился с этим еще работая с бейсиком на ПК "Поиск" пытаясь вывести на экран число 1.0 и сильно удивился, но потом почитав документацию там такая ситуация более менее была объяснена.
Второй раз столкнулся с этим уже на курсах, там были 8086-е и выдавали слегка другой результат, не помню точно но порядок погрешности оставался таким же, но цифры были другие.
Плохо что в современной документации если и есть какие-то намеки на подобные проблемы то найти их среди тонны информации совершенно бесперспективное занятие, и недолог тот час когда об этой проблеме будут знать лишь единицы, а остальные люди полагать что компьютер вычисляет точнее чем это можно сделать на бумаге и соответственно винить в ошибках только человека. |
|
20-07-2008 04:05Прелестная статья!
Возможно, стоит где-нибудь сделать сноску, для какой версии Delphi она была написана, поскольку "всё течёт, всё меняется".
К примеру, вот кусочек хелпа про тип Real:
==
The generic type Real, in its current implementation, is equivalent to Double.
...
Note: The six-byte Real48 type was called Real in earlier versions of Object Pascal. If you are recompiling code that uses the older, six-byte Real type, you may want to change it to Real48.
== |
|
06-05-2008 06:31сообщение от автора материала Что ж, авторитет Fisher'а по вопросам округления вещественных чисел получил ещё одно подтверждение, не могу не согласиться :) |
|
06-05-2008 06:22Позволю себе вступиться за смысл существования функций округления.
Светлый идеал использования функций округления в том, что при грамотном их применении можно достичь математически точных результатов (в рамках заданной точности) даже при всех чудесах внутреннего представления чисел.
Другими словами, если предположить, что в приведенном примере А,В и С - числа с двумя знаками после запятой (например, рубли), то можно добиться, что С будет чистым нулем, как и ожидается из школьного курса арифметики, и именно таковым поучаствует в дальнейших вычислениях. Для этого надо не забыть применить функцию Okrugl() не только при вычислении А и В, но и при вычислении С
C := Okrugl( 100*(A - B) - 1 ,2);
Смысл происходящего в том, что хотя вещественное число не может быть равно точно 1.11, но погрешность его представления как 1.11 значительно меньше самого числа 1.11 (для extended - думаю, порядков на 10), и потому погрешность эта в последующих вычислениях при правильном округлении устраняется.
Разумеется, полезность эта может быть получена, только если функции округления не глючны сами по себе, как вредоносная RoundTo. Ну и понятно, что если построить вычисления так, что на каком-то этапе погрешность представления сравняется по порядку с самим числом, то никакое округление не поможет.
|
|
06-05-2008 02:00сообщение от автора материала Stalker Stalker:
Я не совсем об этом. Вот смотрите, такой код с вашей функцией:
procedure TForm1.Button1Click(Sender: TObject);
var
A, B, C: Extended;
begin
A := Okrugl(1.111111111, 2);
B := Okrugl(1.111111111, 1);
C := 100* (A - B) - 1;
Label1.Caption:=FloatToStr(C)
end;
Легко видеть, что если бы всё было точно, то A-B дало бы 0.01 и C стало бы равным 0, чего не происходит. Зная формат хранения вещественного числа, легко понять, что A не может иметь значения 1.11, а B - 1.1, там всё равно будет некоторое округление. Вот я и не понимаю, зачем тратить такие усилия на достижение заведомо неточного результата? Округление обычно нужно только при выводе на экран - ну так и используйте функцию FloatToStrF или Format, а округлять само число в двоичном представлении я смысла не вижу.
P.S. Смысл существования функции RoundTo я, если честно, тоже не понимаю :) |
|
06-05-2008 01:08К неочевидным особенностям вещестенных чисел хочу приписать свои наблюдения.
Функции Sin(), Cos(), Sqrt() не растут (или убывают) равномерно.
При небольших изменениях аргумента они будут прыгать взад-вперед.
И понятно почему - они вычисляются итерационно при помощи рядов.
Не могу привести список всех таких функций, просто с этими сталкивался сам.
Поскольку в статье это не было упомянуто, я решил, что неплохо об этом тоже сообщить.
|
|
04-05-2008 08:02>>> В результате пришел к выводу, что надежнее всего - "ручной" анализ строчного представления исходного числа.
Правильно! Только не совсем "ручной". В Delphi уже давно есть модуль для работы с числами в таком представлении - FmtBCD.pas. Он рекомендуется для финансовых приложений и т.п. |
|
04-05-2008 06:561. Она была написана еще под D3 тогда, никаких RoundTo не было
2. В своей работе пробовал RoundTo, его аналоги и самые разные вариации округления, предлагаемые на разных программерских форумах и всегда были случаи когда результат не совпадал с ожидаемым.
В результате пришел к выводу, что надежнее всего - "ручной" анализ строчного представления исходного числа. |
|
30-04-2008 07:23сообщение от автора материала Привожу свою функцию корректного округления.
И чем она лучше RoundTo? |
|
30-04-2008 06:58Привожу свою функцию корректного округления. Функция опробирована с параметром Dig = 1..4. Больше необходимости не было. Используется в финансовых задачах. Для научных, ижинерных целей ее тчность недостаточна, надо дописывать.
function Okrugl(aFloat: Extended; Dig: Integer): Extended;
var sFloat: string;
i, p, x: Integer;
MinusSign: boolean;
begin
if Abs(aFloat) < 0.000001 then begin
Result := 0;
Exit;
end;
MinusSign := aFloat < 0;
aFloat := Abs(aFloat);
sFloat := FloatToStrF(aFloat, ffFixed, 18, 18);
Dig := Abs(Dig);
for i := 1 to Length(sFloat) do
if not (sFloat[i] in ['0'..'9']) then sFloat[i] := DecimalSeparator;
p := Pos(DecimalSeparator, sFloat);
if p = 0 then begin
Result := aFloat;
if MinusSign then
Result := -Result;
Exit;
end;
x := p + Dig;
if x >= Length(sFloat) then begin
Result := aFloat;
if MinusSign then
Result := -Result;
Exit;
end;
sFloat := Copy(sFloat, 1, p-1) + Copy(sFloat, p+1, Length(sFloat) - p);
x := x-1;
if sFloat[x+1] > '4' then
sFloat[x] := Succ(sFloat[x]);
while (sFloat[x] > '9')and(x > 0) do begin
sFloat[x] := '0';
x := x - 1;
if x > 0 then
sFloat[x] := Succ(sFloat[x]);
end;
if (x = 1)and(sFloat[1] > '9') then begin
sFloat[1] := '0';
sFloat := '1' + sFloat;
Inc(p);
end;
sFloat := Copy(sFloat, 1, p-1) + DecimalSeparator + Copy(sFloat, p, Length(sFloat) - p+1);
if x = 0 then
sFloat := '1' + Copy(sFloat, 1, p+Dig)
else
sFloat := Copy(sFloat, 1, p+Dig);
Result := StrToFloat(sFloat);
if MinusSign then
Result := -Result;
end;
|
|
08-05-2007 05:49
08-05-2007 00:58>>> мне кажется, что в той статье проблема излишне раздута
Я считаю, что излишне раздута эмоциональная часть. Впрочем, похоже, ради нее статья и написана. А крупицу здравого смысла можно выразить словами: действительно, в современной вычислительной технике многие вещи реализованы компромиссными путями, причем далеко не лучшими, а зачастую продиктованными историческими причинами: нехваткой определенных ресурсов в древности, рыночными капризами фортуны, и многим другим...
http://www.anekdot.ru/an/an0602/s060220.html#33 (По бокам космического корабля "Кеннеди" размещаются два двигателя по 5 футов шириной....)
|
|
07-05-2007 22:26>>>аффтар жжот, каменты рулят
согласен
мне кажется, что в той статье проблема излишне раздута
типа как было с проблемой 2000 года |
|
07-05-2007 15:20Что самое интересное, Антон Григорьев лично меня "заставил" читать данную статью! ;))
Респект автору. Действительно грамотная и занятная информация. |
|
02-05-2007 05:53
16-04-2007 03:33Да, про "сдвиг" я был не прав.
А свои примеры я привел для подтверждения фразы
"от перемены мест слагаемых сумма изменяется"
То что Вы написали я знаю как "выравнивание порядков", т.е. сначала числа сводятся к одному порядку, а потом складываются мантиссы.
>>>Да, таких мест может быть много
Я хотел, чтобы моё сообщение сподвигло Вас написать продолжение. ;-) |
|
14-04-2007 05:58Антону спасибо, я уже многое забыл из этого, а раньше ведь знал на изусть добрую часть этой статьи.
Написано хорошо и добротно, ставлю 5. |
|
14-04-2007 00:16сообщение от автора материала при вычислении маш.епс. делить надо на 2. Но Вы не акценитировали почему.
Вот цитата из статьи: Прибавим к числу 1.00 число 1.00E-4. Если бы всё было честно, мы получили бы 1.0001. Но у нас ограничена точность, поэтому мы вынуждены округлять до трёх значащих цифр. В результате получается 1.00. Другими словами, мы прибавляем к единице некоторое число, большее нуля, а в результате из-за ограниченной точности получаем снова единицу.
Понятно, что если бы мы прибавляли не 1.00E-4, а 9.99E-4, ничего бы не изменилось - число 1.000999 всё равно округлилось бы до 1.00. Т.е. роль играет только старшая значащая цифра, причём даже не сама цифра, а то, какой разряд является старшим. В двоичной системе, где старший разряд обязательно равен единице, уж точно играет роль только значение экспоненты, т.е. если число с заданной экспонентой добавить к единице, то получится либо единица (если число меньше машинного эпсилон), либо не единица, и конкретное значение мантиссы здесь ничего не меняет.
Из этого легко видеть, что машинное эпсилон должно иметь вид 2^N, где N - какое-то целое отрицательное число. На всякий случай напомню определение: НАИМЕНЬШЕЕ положительное число, которое при добавлении его к единице даёт результат, не равный единице, называется машинным эпсилон. При заданной экспоненте наименьшим числом будет как раз 2^N. Поэтому мы начинаем с 1 (т.е. 2^0) и делим это число на два, чтобы найти именно степень двойки.
Насколько я помню деление на 2 выполняется сдвигом даже для вещественных чисел.
Ну, вам уже сказали, что это не так. Для нормализованной записи сдвигать нужно только экспоненту, не трогая мантиссу, для денормализованной - только матиссу, не трогая экспоненту. А так как мантисса и экспонента не выровнены на границу байта, то сдвигать их обычными иструкциями процессора - занятие неблагодарное. Так что пусть уж лучше этим сопроцессор занимается.
Есть ещё подводные камни с этими "противными" вещественными числами
Например, "от перемены мест слагаемых сумма изменяется"
а виновато выравнивание порядков
Да, таких мест может быть много. На всякий случай поясню ваш пример. Вещественное число хранит фиксированное число разрядов, а экспонента, по сути дела, указывает, в каком диапазоне лежат эти разряды. Т.е., например, 23 бита мантиссы могут хранить 23 двоичных разряда в диапазоне от 2^10 до 2^-42, а могут - от 2^-80 до 2^-132 - это зависит от значения экспоненты. Другими словами, в большом диапазоне возможных значений есть относительно небольшое "окно", которое может перемещаться по этому диапазону. Верхняя граница этого окна выравнивается на старший разряд. Соответственно, младшие разряды, отстоящие слишком далеко от старшего, не попадают в окно и отбрасываются.
В первом варианте мы изначально работаем с маленькими числами, поэтому окно выровнено так, чтобы вмещать в себя их с достаточной точностью. За счёт многократных сложений число увеличивается на 6 десятичных порядков (около 20 двоичных), но так как каждый раз прибавляется маленькое число, окно перемещается постепенно, и существенной потери точности не происходит. И когда мы прибавляем сумму к большому числу, они оба умещаются в одном окне.
Во втором случае мы начинаем вычисление сразу с большого числа, в результате чего окно оказывается сдвинуто далеко в область старших разрядов. Те маленькие числа, которые мы добавляем к большому на каждой итерации, просто не помещаются в это окно (т.е. имеем тот же эффект, что и в случае машинного эпсилон, только маленькое число прибавляется не к единице, а к миллиону). Соответственно, это маленькое число просто игнорируется, что и приводит к большой потере точности. |
|
13-04-2007 00:582 Yashin
да, наверно так и есть |
|
10-04-2007 09:29>>>Насколько я помню деление на 2 выполняется сдвигом даже для вещественных чисел.
Вы ошибаетесь. |
|
09-04-2007 06:39Спасибо.
Очень грамотно.
Антон!
при вычислении маш.епс. делить надо на 2. Но Вы не акценитировали почему.
Кроме двоичного представления данных
Насколько я помню деление на 2 выполняется сдвигом даже для вещественных чисел.
Поправьте, если я не прав
В порядке предложения, не критики.
Есть ещё подводные камни с этими "противными" вещественными числами
Например, "от перемены мест слагаемых сумма изменяется"
а виновато выравнивание порядков
procedure TForm1.Button1Click(Sender: TObject);
var
R2 : Single;
i : integer;
begin
R2 := 0;
for i := 1 to 1000000 do
R2 := R2 + 0.000001;
R2 := R2 + 1000000.0;
if R2 = 1000001.0 then
Label1.Caption := 'равно'
else
Label1.Caption := 'не равно';
Label2.Caption := FloatToStr(R2);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
R2 : Single;
i : integer;
begin
R2 := 1000000.0; к 1000000 прибавляем 1000000 раз 1Е-6
for i := 1 to 1000000 do
R2 := R2 + 0.000001;
if R2 = 1000001.0 then
Label1.Caption := 'равно'
else
Label1.Caption := 'не равно';
Label2.Caption := FloatToStr(R2);
end;
|
|
15-08-2006 06:59Замечательная статья!
Могу добавить только следующее: если важно (например, в финансовых вычислениях), чтобы вычисленный результат был в точности такой же, как и при ручном счёте, на бумажке, можно производить вычисления в десятичном представлении.
Для этого в Delphi есть модуль FMTBcd, который позволяет производить 4 арифметических действия над числами в так называемом двоично-десятичном представлении (BCD = Binary Coded Decimal). Подробнее можно посмотреть в справке Delphi или покопаться в интерфейсной части исходного кода модуля FMTBcd.pas. |
|
30-07-2006 17:38> Не обольщайтесь - там не 0.1 даже при использовании Extended, не говоря уже про Double. Не может число 0.1 быть представлено конечной двоичной дробью. Просто отладчик при показе слегка округляет число, чтобы оно выглядело не так страшно (на мой взгляд, зря он это делает, т.к. путаница от этого только увеличивается, но что есть, то есть).
По-видимому, использует FloatToStr - она не с макс. точностью выводит. |
|
10-05-2005 20:09To Антон Григорьев:
> ... То, что не все коенчные десятичные дроби точно представимы
> в двоичной арифметике, на результат не влияет
Лучшая новость за месяц. Спасибо.
Контроллеры - GE Fanuc (у меня например VersaMax). Так-же Сиеменсовские (Siematic C-300? не уверен). Вероятно какие-нибудь еще...
Ссылка на характеристики VersaMax: http://www.advantekengineering.ru/versamax.htm (раздел ЦПУ)
Post Scriptum О простоте использования - врут. :)
Post Post Scriptum Извиняюсь за офтоп. |
|
07-05-2005 05:37сообщение от автора материала Дмитрию:
Погрешность ведь не рассчитывается точно, а только оценивается, поэтому при оценке погрешности компьютерных вычислений используется обычная теория ошибок. То, что не все коенчные десятичные дроби точно представимы в двоичной арифметике, на результат не влияет, т.к. порядок погрешности от этого не меняется.
P.S. А разве тип real поддерживается какими-то контроллерами аппаратно? Нникогда о таком не слышал. |
|
01-05-2005 12:00Статья понравилась, хотя многое знал.
Вопрос имею. Можно ли рассчитать максимальную погрешность при использовании допустим real'ов в уравнении с известными операторами?
Конкретно - мне сейчас необходимо совершить порядка 400 действий типа сложение-умножение-экспонента-мать-ее-так, и я абсолютно не уверен, что результирующая точность будет приемлимой.
Дабла хватает. Но его в этом [бииип] ПЛК нету. |
|
03-02-2005 04:12
04-12-2004 14:06сообщение от автора материала Последнее сообщение написал я. Не знаю, почему оно осталось неподписанным - я нормально залогинился.
Григорьев Антон |
|
04-12-2004 10:37У меня вот еще какой вопросик: процессор AMD Athlon, как он относится к вещественным числам?
Так же, как и все остальные. Стандарты IEEE едины для всех процессоров, в т.ч. и тех, которые несовместимы с Intel.
x:=0.1;
Если х - Double, то в отладке смотрим, х получился ровно 0.1 Ну, это понатно - проц работает в режиме Double.
Не обольщайтесь - там не 0.1 даже при использовании Extended, не говоря уже про Double. Не может число 0.1 быть представлено конечной двоичной дробью. Просто отладчик при показе слегка округляет число, чтобы оно выглядело не так страшно (на мой взгляд, зря он это делает, т.к. путаница от этого только увеличивается, но что есть, то есть).
Для проверки попытайтесь вычислить, чему равно 10*x-1 при типе Double - там будет не 0. Неточность возникнет именно потому, что x изначально неточно предсталял 0.1, потому что остальные числа в этом выражении - целые, которые представляются точно.
Кстати, если x будет иметь тип Extended, то в результате вы получите 0. Но не потому, что в этом случае x буде точно 0.1, а потому, что при умножении этого числа на 10 процессор использует соответствующее округление.Сообщение не подписано |
|
02-12-2004 11:04Прошу прощения за свою глупую писанину - "на счет решения проблемы" . Так можно лишь добавить себе больше проблем. Я еще не совсем осознал проблемы, когда писал это. Еще раз извиняюсь. Сообщение не подписано |
|
01-12-2004 08:40Насчет решения проблемы... Сам я начинающий программер, так что не взыщите..Буду рад Вашим отзывам!!!
Конечно, проблемы не будет, если не смешивать типы представления вещественных чисел. Но если, например, вам нужно представить число 0.2475 в виде Double (проц работает в режиме "полного" Extended) то при выполнении
var x: Double;
begin
x:=0.2475;
x получит не 0.2475 , a 0.2474999748
Чтобы не исказить значение 0.2475 , нужно перед присвоением округлить 0.2475 до 4-го знака после запятой(именно столько знаков в нашем числе) по правилам математики, затем, используя Trunc , отсечь "лишние" разряды.
Как вам решение?
Конечно, на каждый случай - свое решение, но все-таки буду рад Вашей критике. |
|
01-12-2004 05:14Вот код в Delphi:
x:=0.1;
Если х - Double, то в отладке смотрим, х получился ровно 0.1 Ну, это понатно - проц работает в режиме Double.
Если х - Single - то в отладке мы видим
0.10000000149 , с этим тоже все ясно...
Но когда х - Currency, мы опять видим, что х принял значние 0.1 Из справки узнаю, что Currency имеет 4 знака после запятой, значит при переводе в Extended мы получим 0.1000 , но ведь число 0.1 из правой части выражения тоже переводится в Extended и оно будет равно 0.10000000000001 примерно (точно не знаю) , так почему же Currency и Extended равны? |
|
30-11-2004 11:54СПАСИБО ЗА СТАТЬЮ!!!
У меня вот еще какой вопросик: процессор AMD Athlon, как он относится к вещественным числам? К сожалению, не очень разбираюсь в маркировках, а то бы и сам посмотрел. Маркировку щас не привожу, если нужна - позже скажу, но, наверное здесь имеет место общий случай для какого-то типа процессоров. |
|
05-10-2004 18:38Статья хорошая.
А вопросы на КС продолжают сыпаться...
offtopic:
Надо как-то заставить вновь прибывающих пройтись по материалам сайта прежде чем позволить им задавать вопросы |
|
01-06-2004 17:45Кстати, был весьма огорчён, что нельзя в Паскале написать
if R = Single(0.1) then...
IMHO неоправланная и гадкая подстава. |
|
16-05-2004 19:23Могу сказать, почему у вас не сошлись результаты тестов на машинный эпсилон. Управляющее слово меняют НЕКОТОРЫЕ функции WinAPI, а не все. В одном случае эти функции могли вызываться в процессе работы программы - в другом нет. Следовательно, результат зависит от того, что именно делают другие процессы. |
|
19-02-2004 17:37сообщение от автора материала Нет, всё правильно: AND сбрасывает в ноль биты 8 и 9, оставляя другие биты без изменения, а OR устанавливает 9 бит в 1, оставляя все другие (в т.ч. и восьмой) биты без изменения. В результате в битах 9 и 8 будет 1 и 0 соответственно, т.е. 53-битная точность. А OR 300h включит максимальную, 64-битную точность. |
|
19-02-2004 15:57Извиняюсь, мне кажется в статье неточность
Чтобы, например, установить 53-значную точность, не изменив при этом другие биты ....
asm
FNSTCW MyCW
AND MyCW,0FCFFh
OR MyCW,200h
FLDCW MyCW
end;
На самом деле этот код отключит точность, надо OR MyCW,300h |
|
22-01-2004 10:25поставил 8 - c double работает нормально, с single нет. Видно на счет double поправили сравнение. |
|
21-01-2004 13:18Как с такими вещами работают последние компиляторы 7 и 8, просто у меня стоит 6 - всё сходится. Но ни одной описанной проблемы в с# не обнаружено, может уже и борланд исправился. |
|
09-01-2004 07:35Пришел с проблемой ушел с решением.
Проблема была с FloatToStr. После замены на FloatToStrF все проблемы решились.
PS:
flNum:=803.2;
FloatToStr(flNum)='803.19999999978'
Вот такой глюк.
Спасибо. |
|
26-06-2003 22:42Нужная статья. Спасибо за труд. |
|
20-06-2003 13:28
03-04-2003 11:20Наиболее интересным показалось обсуждение статьи |
|
10-01-2003 15:29сообщение от автора материала Для Maxim:
Это место содержит ещё одну, более важную неточность. Если все биты в экспоненте равны нулю, то это означает не 0, а -127, поэтому формально эта комбинация соответсвует числу 0*2^(-127). Упустил я этот момент, каюсь. |
|
06-01-2003 01:14Цитата автора:
>>>С учётом всех этих правил комбинация, когда и в мантиссе, и в экспоненте все биты равны нулю, даёт 0^0. С математической точки зрения эта комбинация бессмысленна, но разработчики формата договорились считать такую комбинацию нулём.
Это неверно, когда в мантиссе и экспоненте нули, то получится 0*2^0. С математической точки зрения эта комбинация имеет смысл. Иначе проверка на ноль занимала бы дополнительное время.
|
|
28-11-2002 13:02сообщение от автора материала Для Konstantin:
>>>Существует ли проблема представления десятичных чисел в других операционных системах (напр. UNIX)? Имеются ли такие проблемы у других языков и других трансляторов (C,FORTRAN)? Как там решаются аналогичные проблемы?
Боюсь, вы зря читали статью. Повторяю ещё раз то, что вы не поняли: все проблемы с вещественными числами возникают из-за особенностей их аппаратной обработки. Язык, ОС и т.д. ни на что не влияют.
>>>Автору следовало бы указать хотя бы некоторые решения данной проблемы. Тем более, что в DELPHI они есть, но в документации внимание на них не акцентируется.
А вот это уже противоречит моим принципам. Те, кто разобрался в причинах проблемы, найдут решение и без моей помощи, а подносить готовое решение на тарелочке с голубой каёмочкой тем, кто не хочет сам разбираться, - это не в моих правилах.
>>>Возьмем первый пример из статьи:
>>>var R:Single;
>>>begin
>>> R:=0.1;
>>> Label1.Caption:=FloatToStr(R)
>>>end;
>>>Изменим функцию FloatToStr(R) на FloatToStrF(R,ffFixed,7,3) и получите точно 0.1
Ерунду пишете. Смысл этого примера - не вывести на экран строку "0.1", а показать, что в результате выполнения оператора "R:=0.1" в переменной R оказывается не 0.1, а другое число. И использование FloatToStrF в данном случае - это не решение проблемы, а сокрытие, сама же проблема может всплыть в другом месте, не связанном с выводом на экран.
>>>Замените R=0.1 на SameValue(R,0.1,1E-7) и результат будет "Равно"
Это где же в Delphi 5 вы нашли функцию SameValue? А более новых версий Delphi на момент написания статьи не было. |
|
11-11-2002 03:26Статья очень полезна для не программистов, которые должны много программировать по роду своей деятельности (хотя и несколько длинновата и туманно написана). Профессионалы это и так обязаны знать. Автору не стоит забывать, что все мы чайники, но только каждый в своей области.
По сути.
- Существует ли проблема представления десятичных чисел в других операционных системах (напр. UNIX)? Имеются ли такие проблемы у других языков и других трансляторов (C,FORTRAN)? Как там решаются аналогичные проблемы?
- Автору следовало бы указать хотя бы некоторые решения данной проблемы. Тем более, что в DELPHI они есть, но в документации внимание на них не акцентируется.
Возьмем первый пример из статьи:
var R:Single;
begin
R:=0.1;
Label1.Caption:=FloatToStr(R)
end;
Изменим функцию FloatToStr(R) на FloatToStrF(R,ffFixed,7,3) и получите точно 0.1
Второй пример:
var R:Single;
begin
R:=0.1;
if R=0.1 then
Label1.Caption:="Равно"
else
Label1.Caption:="Не равно"
end;
Замените R=0.1 на SameValue(R,0.1,1E-7) и результат будет "Равно"
|
|
17-04-2002 08:50Даааа.... А я то думал, что я опытный программист...
Спасибо! |
|
29-03-2002 17:46Так становятся волшебниками.
С благодарностью.
vb-программер. |
|
23-11-2001 10:08сообщение от автора материала Насчёт того, почему 0.1*5=0.5. Если мы будем тупо на бумажке умножать 0.1 во внутреннем представлении компьютера, то получим двоичную дробь, хотя и конечную, но всё же не влезающую в отведённые для неё разряды. Поэтому сопроцессор вынужден её округлять. Видимо, разница мажду 0.5 и получившимся значением столь незначительна, что при округлении пропадает. Кроме того, режим округления сопроцессора можно менять с помощью управляющего слова. Я подозреваю, что по умолчанию устанавливается такой режим, когда сопроцессор пытается подчистить ошибки округления. Точнее это можно узнать в документации на сопроцессор, которой у меня, к сожалению, нет. |
|
22-11-2001 17:00Извиняюсь, не подписал предыдущее сообщение ;0) |
|
22-11-2001 14:17Привет!
спасибо за статью.
есть неясность с extended.
например :
var r : extended;
begin
r := 0.1;
if r*5=0.5 then ShowMessage("равно") else ShowMessage("не равно")
end;
почему в результате возвращается 'равно', хотя вроде бы число 0.1 не может быть представлено точно ни в одном вещественном типе (в т.ч. в extended), тогда как 0.5 может быть точно представлено. Следовательно число приблизительно равное (0.1*5) не должно быть равно точному представлению 0.5?
Сообщение не подписано |
|
16-08-2001 18:17
28-06-2001 01:31Очень полезный материал. Спасибо! Я начинающий программер, и для меня такое поведение машины было бы просто колдовством! Слава богу, что я не успел с ним столкнуться, а то я бы изгнал из нее бесов, чем нибудь тяжелым... Тем более что мой родной язык ASM для переферийных контроллеров, и там я привык к однозначности на математических операциях. Ругателям статьи скажу что дело не всегда в том с какой точностью выдан результат. Простой пример, переход по проверке условия
XOR literal and W
GOTO IF ZERO
поведение системы будет не предсказуемо, если не учитывать вышеизложенный материал. Еще раз спасибо. |
|
08-06-2001 15:49сообщение от автора материала С помощью своих знакомых я провёл некоторые дополнительные эксперименты и обнаружил, что из 11 проверенных таким образом машин с Windows 95/98 эффект потери точности был замечен на четырёх. Причём никакой корреляции между наличием эффекта и версией Windows установить не удалось. Также пока не удалось связать потерю точности с аппаратной конфигурацией и установленными программами. К сожалению, среди четырёх компьютеров с этим эффектом два - это те, которые мне наиболее доступны: мой домашний и рабочий. Так как при написании статьи я экспериментировал в первую очередь именно с этими машинами, я и сделал в ней столь категоричный вывод.
Однако эксперименты преподнесли один весьма неожиданный сюрприз: компьютер Королевы, на котором стоит NT, тоже продемонстрировал эффект снижения точности. Самое интересное - что этот компьютер участвовал и в тех экспериментах, которые проводились при подготовке статьи, и никакого снижения точности не наблюдалось. Система на компьютере не переустанавливалась, только доустанавливались некоторые программы, хотя на других компьютерах с такими же программами эффекта нет. Так что со снижением точности всё не так однозначно, как я написал в статье. Тем не менее, он существует, и даже встречается не слишком редко, хотя причину его я сейчас указать не могу. |
|
17-05-2001 19:54Я перепроверил своё утверждение на трёх различных WINDOWS 9x:
4.0.1111 B
4.10.1998
4.10.2222 A
и ни где указанного автором статьи бага не обнаружил.
Антон, если Вас не затруднит, то укажите пожалуйста,
при каких обстоятельствах Вы это наблюдали.
Если верить Вам, то использование FPU под MS WIN являeтся
недопустимой роскошью :)
P.S. Сейчас я убегаю, но возможно смогу прокомментировать Ваш ответ позже.
V. |
|
17-05-2001 12:50сообщение от автора материала Насчёт непонятной фразы прошу прощения. Редактировал несколько раз и вот доредактировался. Правильно она должна звучать так: 'Впрочем, напоминаю, что использование Set8087CW нежелательно, так как эта функция влияет на значение переменной Default8087CW, а это значение может понадобиться'.
Что касается того, что 'автор не прав'. Витёк, вы меня что, совсем безответсвенным идиотом считаете, что позволяете себе заявлять так безапелляционно?! Уж наверное, прежде чем написать такое, я это проверил на разных компьютерах. И не только я (см. благодарность Елене Филипповой в конце статьи). И вопрос этот был задан на борландовской конференции. И, судя по ответам, эта проблема знакома не только мне. Заодно просмотрите другие отзывы: люди тоже сталкивались с такой проблемой. Я вполне допускаю, что, начиная с какой-то подверсии Windows 98 эта ошибка исправлена, и вам попалась именно такая версия. Советую не забывать: по одному эксперименту нельзя судить о явлении в целом, надо проделать серию экспериментов.
А чтобы несколько уменьшить вашу уверенность по поводу того, что 'что любые версии WINDOWS (от 3.1) корректно сохраняют управляющее слово сопроцессора', настоятельно рекомендую вам повнимательнее посмотреть исходные коды VCL. В частности, модуль Dialogs.pas, функцию TCommonDialog.TaskModalDialog. Вот кусок кода из неё (комментарии не мои, а оригинальные от Borland"а):
asm
// Avoid FPU control word change in NETRAP.dll, NETAPI32.dll, etc
FNSTCW FPUControlWord
end;
try
CreationControl := Self;
Result := TDialogFunc(DialogFunc)(DialogData);
finally
asm
FNCLEX
FLDCW FPUControlWord
end;
Application.UnhookMainWindow(MessageHook);
end;
Аналогичный код (правда, без комментариев) можно найти и в некоторых других местах исходников (я просматривал исходники Delphi 5.0 (Build 6.18) Update Pack 1).
От вас, Витёк, я жду либо аргументированных возражений, либо публичных извинений. |
|
16-05-2001 19:05Опять извиняюсь, но в предыдущем сообщении я очепятался в своём почтовом адресе :(( |
|
16-05-2001 19:02Премного извиняюсь, с уважением и пр., но !!!
По поводу “плюхи” от MS.
Я повторил на WIN98SE четвёртый пример из статьи, и обнаружил следующие:
Single даёт -7,3015691270939E-8;
Real даёт -1,11413101102709E-12; при {$REALCOMPATIBILITY ON}
double даёт 1,44327637948555E-16
extended даёт -6,7762635780344E-20
Легко заметить, что утверждение автора о глюке ошибочно.
Не сомневаюсь, что любые версии WINDOWS (от 3.1) корректно сохраняют управляющее слово сопроцессора, и не дают другим приложениям его испортить. Впрочем, могут ли это сделать драйвера – я не знаю.
Заодно добавлю, мне совершенно не понятна фраза из статьи:
“Впрочем, напоминаю, что использование нежелательно, Default8087CW.”
V.
|
|
15-03-2001 22:26
15-03-2001 09:24Статья очень понравилась.Побольше бы таких. |
|
14-03-2001 10:59Очень полезная статья. JINX |
|
14-03-2001 09:14Я сталкивался с тем, что моя математическая программа в разных ОС работает по разному, и теперь понял почему! - благодаря плюхе от M$ |
|
13-03-2001 21:52
13-03-2001 21:39Весьма интересно и полезно. |
|
|
|