The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

форумы  помощь  поиск  регистрация  майллист  вход/выход  слежка  RSS
"неправильные вычисления с плавающей точкой"
Вариант для распечатки  
Пред. тема | След. тема 
Форумы Программирование под UNIX (Public)
Изначальное сообщение [ Отслеживать ]

"неправильные вычисления с плавающей точкой"  
Сообщение от XoRe email(ok) on 18-Фев-09, 15:06 
Скрипт:
------------------------------
#!/usr/bin/perl

$a = 601; foreach $b (0..9) { $c = $a + $b / 10; print "$c: " . (($c * 100) - ($c * 10 * 10)) . "\n"; }
------------------------------

Выдает:
601: 0
601.1: 0
601.2: 7.27595761418343e-12
601.3: -7.27595761418343e-12
601.4: 0
601.5: 0
601.6: 0
601.7: 7.27595761418343e-12
601.8: -7.27595761418343e-12
601.9: 0

Вопрос:
Почему???

Проверялось на разных машинах с конфигурациями:
Perl 5.8.8/FreeBSD 7.0;
Perl 5.8.8/Linux gentoo (kernel 2.6.20-gentoo-r8);
Perl 5.8.8/Linux gentoo (kernel 2.4.32-gentoo-r7);
Perl 5.10.0/Linux Ubuntu 8.10 (kernel2.6.27-12-generic);

Высказать мнение | Ответить | Правка | Cообщить модератору

 Оглавление

Сообщения по теме [Сортировка по времени | RSS]


1. "неправильные вычисления с плавающей точкой"  
Сообщение от NuINu (??) on 18-Фев-09, 15:21 
>[оверквотинг удален]
>601.9: 0
>
>Вопрос:
>Почему???
>
>Проверялось на разных машинах с конфигурациями:
>Perl 5.8.8/FreeBSD 7.0;
>Perl 5.8.8/Linux gentoo (kernel 2.6.20-gentoo-r8);
>Perl 5.8.8/Linux gentoo (kernel 2.4.32-gentoo-r7);
>Perl 5.10.0/Linux Ubuntu 8.10 (kernel2.6.27-12-generic);

И чем же они неправильны?

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

2. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe email(ok) on 18-Фев-09, 15:30 
>[оверквотинг удален]
>>Вопрос:
>>Почему???
>>
>>Проверялось на разных машинах с конфигурациями:
>>Perl 5.8.8/FreeBSD 7.0;
>>Perl 5.8.8/Linux gentoo (kernel 2.6.20-gentoo-r8);
>>Perl 5.8.8/Linux gentoo (kernel 2.4.32-gentoo-r7);
>>Perl 5.10.0/Linux Ubuntu 8.10 (kernel2.6.27-12-generic);
>
>И чем же они неправильны?

Есть число 601.8 * 100.
И есть число 601.8 * 10 * 10.
Перл мне говорит, что эти числа не равны.
601.8 * 100 чуть меньше, чем 601.8 * 10 * 10.

Причем только тогда, когда справа после запятой стоит 2, 3, 7, или 8.
Проверял на разных конфигурациях машин, и на разных версиях перла.
Интересно, почему так.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

3. "неправильные вычисления с плавающей точкой"  
Сообщение от Andrey Mitrofanov on 18-Фев-09, 15:38 
>Интересно, почему так.

Как? Вы не знали, что _плавающая_ точка потому так и называется, что подвержена тепловому дрейфу под влиянием нейтринного фоновага излучения Космоcа?? %))

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

4. "неправильные вычисления с плавающей точкой"  
Сообщение от const86 (ok) on 18-Фев-09, 15:55 
>Есть число 601.8 * 100.
>И есть число 601.8 * 10 * 10.
>Перл мне говорит, что эти числа не равны.

Ну это не перл, а процессор. Правильно говорит.

>Интересно, почему так.

Хороший ответ потянет на целую лекцию. Прочитайте её сами хотя бы даже в википедии.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

5. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe email(ok) on 18-Фев-09, 16:09 
>>Есть число 601.8 * 100.
>>И есть число 601.8 * 10 * 10.
>>Перл мне говорит, что эти числа не равны.
>
>Ну это не перл, а процессор. Правильно говорит.
>
>>Интересно, почему так.
>
>Хороший ответ потянет на целую лекцию. Прочитайте её сами хотя бы даже
>в википедии.

Можно ссылку?
Или хотя бы озвучьте тему лекции.

Прошу учесть, что такое поведение только тогда, когда справа после запятой стоит 2, 3, 7, или 8.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

6. "неправильные вычисления с плавающей точкой"  
Сообщение от gaa (ok) on 18-Фев-09, 17:01 
>Или хотя бы озвучьте тему лекции.

"Представление чисел с плавающей точкой в компьютере"

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

7. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe email(ok) on 18-Фев-09, 17:09 
>>Или хотя бы озвучьте тему лекции.
>
>"Представление чисел с плавающей точкой в компьютере"

Понятно.
Отлично.

А теперь, прошу конкретики.

Почему результат операции "* 100" зависит от числа после запятой?
На числах с 0, 1, 4, 5, 6, 9 после запятой операция работает по законам математики.
На числах с 2, 3, 7, или 8 после запятой операция не работает по законам математики.

Заранее прошу учесть - один и тот же "глюк" на разных машинах с разными системами и разными версиями Perl.
(это указано в первом посте данной темы)

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

9. "неправильные вычисления с плавающей точкой"  
Сообщение от XAnder (ok) on 18-Фев-09, 18:50 
>Почему результат операции "* 100" зависит от числа после запятой?

Ещё до "* 100" у вас было "/ 10", а эта операция может дать только приблизительный результат, т. к. процессор оперирует двоичными числами, в том числе и дробями. К примеру, 1/10 в двоичном виде будет 0,000110011..., т. е. бесконечная дробь. Естественно, на практике используется приблизительное значение, хотя и довольно-таки хорошее.

>На числах с 0, 1, 4, 5, 6, 9 после запятой операция
>работает по законам математики.
>На числах с 2, 3, 7, или 8 после запятой операция не
>работает по законам математики.

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

На первом же году обучения программированию нужно вдолбить ученикам, что точных вычислений с плавающей точкой НЕ БЫВАЕТ! Только приблизительные. Мне в своё время учитель это вдолбил - весьма благодарен ему.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

11. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe email(ok) on 19-Фев-09, 09:45 
>[оверквотинг удален]
>>На числах с 2, 3, 7, или 8 после запятой операция не
>>работает по законам математики.
>
>Вот тут важно понять разницу между "работает по законам математики" и "выглядит
>так, будто работает по законам математики". Да и понять, что в
>компьютерах, если поглубже копнуть, - только дискретная математика.
>
>На первом же году обучения программированию нужно вдолбить ученикам, что точных вычислений
>с плавающей точкой НЕ БЫВАЕТ! Только приблизительные. Мне в своё время
>учитель это вдолбил - весьма благодарен ему.

Ну... спасибо за более подробный ответ.
К сожалению, такого вдолбления не имел.

Тогда ещё два вопроса:

1. Правильно ли я понимаю, что современные x86 процессоры не могут точно работать с непериодическими десятичными дробями с одной цифрой после запятой?
Или здесь стоит грешить на Perl?

2. Как тогда работают банковские системы?
Насколько я знаю, там подсчет ведется с точностью до 5-6 знаков после запятой.

P.S.
Сей глюк был замечен при переписывании скрипта на REXX (regina).

Подсчитывалось число копеек:
N = N // 1 * 100 % 1

Переделка на Perl:
$n = int(($n * 100) % 100;

В общем-то такая особенность считать разные числа после запятой по-разному не смертельна.
Это можно обойти, написав вычисление по-другому.
Например так:
$n = int(100 * sprintf ("%.2f", $n));

Чтобы доверить Perl округление.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

13. "неправильные вычисления с плавающей точкой"  
Сообщение от Keeper email(??) on 19-Фев-09, 09:59 
>2. Как тогда работают банковские системы?
>Насколько я знаю, там подсчет ведется с точностью до 5-6 знаков после
>запятой.

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

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

14. "неправильные вычисления с плавающей точкой"  
Сообщение от angra (ok) on 19-Фев-09, 10:01 
>1. Правильно ли я понимаю, что современные x86 процессоры не могут точно
>работать с непериодическими десятичными дробями с одной цифрой после запятой?

Процессоры используют двоичную систему исчисления, а не десятичную, то что есть периодическая дробьв одной может не быть в другой и наоборот. Хотя x86 имеет инструкции для работы с десятичными целыми числами на практике они не используются и в любом случае alu и fpu работают с двоичными данными.
>Или здесь стоит грешить на Perl?

А он то здесь причем?

>2. Как тогда работают банковские системы?
>Насколько я знаю, там подсчет ведется с точностью до 5-6 знаков после
>запятой.

Вы действительно не видите разницу между 5-6 знаков и 11(в вашем примере)? Да и странно приводить банки в пример, там одинарной точности хватает, взяли бы уже научные расчеты.

>Это можно обойти, написав вычисление по-другому.
>Например так:
>$n = int(100 * sprintf ("%.2f", $n));
>
>Чтобы доверить Perl округление.

Это опять таки будет не Perl :)

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

15. "неправильные вычисления с плавающей точкой"  
Сообщение от const86 (ok) on 19-Фев-09, 12:05 
>Процессоры используют двоичную систему исчисления, а не десятичную, то что есть периодическая дробьв одной может не быть в другой и наоборот.

Нет, периодические дроби - это рациональные числа, независимо от системы счисления. И кстати, рациональные числа - счётное множество. Что есть упомянутое выше принципиальное ограничение компьютеров.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

16. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe email(ok) on 19-Фев-09, 12:44 
>[оверквотинг удален]
>Да и странно приводить банки в пример, там одинарной точности хватает,
>взяли бы уже научные расчеты.
>
>>Это можно обойти, написав вычисление по-другому.
>>Например так:
>>$n = int(100 * sprintf ("%.2f", $n));
>>
>>Чтобы доверить Perl округление.
>
>Это опять таки будет не Perl :)

Большое спасибо за разъяснения.
Ещё по советам отписавшихся здесь, прочитал по теме:
http://mylearn.ru/kurs/14
Курс адресован учащимся 10–11-х классов общеобразовательных школ.
Очень жаль, что сие мне не было преподано.

Прошу уважаемых рассказать, как в таких условиях лучше всего работать с дробными частями в том же Perl.

Мне приходит на ум такое решение:

$x = 601.865765;
$x = sprintf "%.2f", $x;
($x) = (split /\./, $x)[1];
if(length($x) < 2)
{
  $x = $x . 0 x (2 - length $x);
}
print $x;

Согласен, напоминает индусский код =)
Буду рад более рациональным предложениям обхода вышеуказанного момента.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

18. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe email(ok) on 19-Фев-09, 15:59 
>[оверквотинг удален]
>
>$x = 601.865765;
>$x = sprintf "%.2f", $x;
>($x) = (split /\./, $x)[1];
>if(length($x) < 2)
>{
>  $x = $x . 0 x (2 - length $x);
>
>}
>print $x;

Как менее индусский способ нахождения дробной части, без уменьшения этой части, пришло на ум вот такое:

$rang = 3;
$n = int(sprintf ("%.2f", $n * 10**$rang)) % 10**$rang;

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

19. "неправильные вычисления с плавающей точкой"  
Сообщение от XAnder (ok) on 19-Фев-09, 17:35 
>Как менее индусский способ нахождения дробной части, без уменьшения этой части, пришло
>на ум вот такое:
>
>$rang = 3;
>$n = int(sprintf ("%.2f", $n * 10**$rang)) % 10**$rang;

Что-то вы не делом занимаетесь, по-моему. Зачем это всё? Если нужно работать с числами до, скажем, шестого знака после запятой, храните их целыми, умноженными на 1000000, а при выводе вставляйте запятую, где надо.

А вот если продолжать с действительными числами, то определитесь, какая точность нужна. Например, вместо $x == $y пишите abs($x - $y) < $eps, где $eps - это достаточно маленькое положительное число, числа меньше которого вы согласны считать, грубо говоря, нулём.

Скажем, если $eps = 1.0e-8, то погрешности в двенадцатом знаке становятся незаметными.

Чтобы получить дробную часть, для положительных чисел достаточно $x - int($x). И не важно, какие там хвосты в шеварнадцатом знаке появятся. Важно ведь только то, как это будет выведено в итоге - выводите с помощью printf, и нет проблем. А до итога не парьтесь с этим.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

20. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe (ok) on 20-Фев-09, 12:10 
>Чтобы получить дробную часть, для положительных чисел достаточно $x - int($x). И
>не важно, какие там хвосты в шеварнадцатом знаке появятся. Важно ведь
>только то, как это будет выведено в итоге - выводите с
>помощью printf, и нет проблем. А до итога не парьтесь с
>этим.

Мне всего лишь нужно было получить 2 цифры после запятой.
Но вот пример.
Есть число 601.80.
Если у него взять дробную часть и умножить на 100, то получится 79 (потому что 601.80 - это не 601.80, а 601.79999 и т.д.....).
А нужно именно 80.
Тот же sprintf "%.2f" округляет в меньшую сторону.
Отсюда и вся возня.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

21. "неправильные вычисления с плавающей точкой"  
Сообщение от XAnder (ok) on 20-Фев-09, 17:08 
>Есть число 601.80.
>Если у него взять дробную часть и умножить на 100, то получится
>79 (потому что 601.80 - это не 601.80, а 601.79999 и
>т.д.....).
>А нужно именно 80.
>Тот же sprintf "%.2f" округляет в меньшую сторону.
>Отсюда и вся возня.

% perl -e '$x=601.8; $y=$x-int($x); print $y, " ", sprintf("%.2f", $y), "\n";'
0.799999999999955 0.80
% perl -e '$x=601.8; $y=($x-int($x)) * 100; print $y, " ", sprintf("%.0f", $y), "\n";'
79.9999999999955 80

У вас как-то по другому?

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

22. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe (ok) on 20-Фев-09, 17:59 
>% perl -e '$x=601.8; $y=$x-int($x); print $y, " ", sprintf("%.2f", $y), "\n";'
>
>0.799999999999955 0.80
>% perl -e '$x=601.8; $y=($x-int($x)) * 100; print $y, " ", sprintf("%.0f",
>$y), "\n";'
>79.9999999999955 80
>
>У вас как-то по другому?

У меня так же.
Прошу прощения, где-то я проглядел.

Хочу спросить, чем можно помочь такому:
# perl -e '$x=5.75 * 0.18; $y=$x-int($x); print $y, " ", sprintf("%.4f", $y), "\n";'
0.0349999999999999 0.0350
# perl -e '$x=5.75 * 0.18; $y=$x-int($x); print $y, " ", sprintf("%.3f", $y), "\n";'
0.0349999999999999 0.035
# perl -e '$x=5.75 * 0.18; $y=$x-int($x); print $y, " ", sprintf("%.2f", $y), "\n";'
0.0349999999999999 0.03

По идее должно быть 0.04.
Или нет?

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

23. "неправильные вычисления с плавающей точкой"  
Сообщение от XAnder (ok) on 20-Фев-09, 18:58 
>Хочу спросить, чем можно помочь такому:
>...
># perl -e '$x=5.75 * 0.18; $y=$x-int($x); print $y, " ", sprintf("%.2f", $y), "\n";'
>0.0349999999999999 0.03
>
>По идее должно быть 0.04.
>Или нет?

Ха! Интересный вопрос. Чисто как головоломку можно порешать. Математически особой роли не играет куда округлять пятёрку - вниз или вверх, но если дело касается денег, то, как говорит один мой знакомый: "здесь вопрос другой".

Решение в лоб (некрасивое):

% perl -e '$x=5.75 * 0.18; $y=$x-int($x); print $y, " ", sprintf("%.2f", sprintf("%.8f", $y)), "\n";'
0.0349999999999999 0.04

Тут я предположил, что цифры после восьмого знака нам не интересны ($eps = 1.0e-8).

Могут быть и другие варианты, зависит от задачи. Если задача действительно финансовая, мой вам совет: плюньте на вещественную арифметику вообще, считайте всё в целых числах. За единицу можно взять копейку или какую-нибудь долю копейки.

PS. Покидаю форум как минимум до вторника.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

24. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe (ok) on 24-Фев-09, 12:08 
>[оверквотинг удален]
>
>Тут я предположил, что цифры после восьмого знака нам не интересны ($eps
>= 1.0e-8).
>
>Могут быть и другие варианты, зависит от задачи. Если задача действительно финансовая,
>мой вам совет: плюньте на вещественную арифметику вообще, считайте всё в
>целых числах. За единицу можно взять копейку или какую-нибудь долю копейки.
>
>
>PS. Покидаю форум как минимум до вторника.

Математически 5 должна округляться вверх.
По правилам:
0,1,2,3,4 - округляется вниз.
5,6,7,8,9 - округляется вверх.
Собственно, изза чего вся суета - другой язык (с которого переписывается код) лишен сего недостатка и округляет корректно.

Хорошее решение насчет sprintf("%.8f", $y), так и сделаю.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

25. "неправильные вычисления с плавающей точкой"  
Сообщение от angra (ok) on 24-Фев-09, 15:22 
>Математически 5 должна округляться вверх.
>По правилам:
>0,1,2,3,4 - округляется вниз.
>5,6,7,8,9 - округляется вверх.
>Собственно, изза чего вся суета - другой язык (с которого переписывается код)
>лишен сего недостатка и округляет корректно.

С каких пор округление 0.0349999999999999 до 0.04 это корректно? Округление делается за один раз, а не поразрядно, так что как ни крути 0.0349999999999999 все-таки меньше 0.035 и значит округлятся будет вниз до 0.03. Если округлять поразрядно, то 0.0346 тоже округлится сначала до 0.035, а потом до 0.04.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

26. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe (ok) on 24-Фев-09, 16:04 
>С каких пор округление 0.0349999999999999 до 0.04 это корректно? Округление делается за
>один раз, а не поразрядно, так что как ни крути 0.0349999999999999
>все-таки меньше 0.035 и значит округлятся будет вниз до 0.03. Если
>округлять поразрядно, то 0.0346 тоже округлится сначала до 0.035, а потом
>до 0.04.

Согласен.
Посчитал так же и забил на разности в округлениях у разных языков.
Благо, perl в этом случае ведет себя так же, как C, т.е. более мейнстримно.

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

10. "неправильные"  
Сообщение от Andrey Mitrofanov on 18-Фев-09, 19:55 
>Или хотя бы озвучьте тему лекции.

Ошибки округления при арифметике плавающих
Ошибки представления дейчтвительных в _десятичной_ системе счисления -- машинными (=двоичными) плавающими
Ошибки округления при арифметике рациональных дробей в представлении с ограниченной точностью

Ошибки плавающей арифметики -- в гугле, должно _бы_ удовлетворить %) жажду.

Кстати! Пока не забыл: "Ошибки программирования" + "сравнение плавающих чисел". Азы %) профессии, однако. Преподы это преподают "молодым програмерам", но обоснование обычно оставляется "для самостоятельного изучения".

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

12. "неправильные"  
Сообщение от XoRe email(ok) on 19-Фев-09, 09:56 
>[оверквотинг удален]
>
>Ошибки округления при арифметике плавающих
>Ошибки представления дейчтвительных в _десятичной_ системе счисления -- машинными (=двоичными) плавающими
>Ошибки округления при арифметике рациональных дробей в представлении с ограниченной точностью
>
>Ошибки плавающей арифметики -- в гугле, должно _бы_ удовлетворить %) жажду.
>
>Кстати! Пока не забыл: "Ошибки программирования" + "сравнение плавающих чисел". Азы %)
>профессии, однако. Преподы это преподают "молодым програмерам", но обоснование обычно оставляется
>"для самостоятельного изучения".

http://www.google.ru/search?q=%22%D0%9E%...
=)

Но по предмету уже читаю

Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

17. "неправильные вычисления с плавающей точкой"  
Сообщение от XoRe email(ok) on 19-Фев-09, 13:11 
Сказываются пробелы в образовании.
http://rt.perl.org/rt3/Public/Bug/Display.html?id=63304
Проблему можно считать решенной в общем случае)
Высказать мнение | Ответить | Правка | Наверх | Cообщить модератору

Архив | Удалить

Индекс форумов | Темы | Пред. тема | След. тема
Оцените тред (1=ужас, 5=супер)? [ 1 | 2 | 3 | 4 | 5 ] [Рекомендовать для помещения в FAQ]




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2025 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру