Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Использование double вместо float, повысится ли точность вычислений? 
:(
    Опции темы
Нитонисе
Дата 26.8.2012, 12:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



В моих программах часто выполняются различные расчеты. Нередко встречаются очень маленькие цифры (например первая значащая цифра появляется в десятом знаке после запятой) и мне нужна достаточная точность, что бы 0.00000000001 не стало вдруг нулем. Сейчас я всегда использую тип float и вижу что часто присваивая переменно например 1 на деле могу получить 1.999999856752. В подавляющем большинстве случаев это приемлемая точность, но тем не менее было бы здорово, если бы вычисления стали еще точнее. Поможет ли мне в этом double или этот тип всего лишь увеличивает емкость, позволяя использовать бОльшие числа?
PM MAIL   Вверх
boostcoder
Дата 26.8.2012, 12:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


pattern`щик
****


Профиль
Группа: Завсегдатай
Сообщений: 5458
Регистрация: 1.4.2010

Репутация: 49
Всего: 110



Цитата(Нитонисе @  26.8.2012,  12:50 Найти цитируемый пост)
Поможет ли мне в этом double

нет. создаст еще больше проблем.

для подобных задач существует прекрасная и очень сильно проверенная библиотека GMP.
ну или на худой конец, переходи на использование std::complex.
PM WWW   Вверх
Фантом
Дата 26.8.2012, 15:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вы это прекратите!
***


Профиль
Группа: Участник Клуба
Сообщений: 1516
Регистрация: 23.3.2008

Репутация: нет
Всего: 49



Цитата(Нитонисе @  26.8.2012,  13:50 Найти цитируемый пост)
В подавляющем большинстве случаев это приемлемая точность, но тем не менее было бы здорово, если бы вычисления стали еще точнее. Поможет ли мне в этом double или этот тип всего лишь увеличивает емкость, позволяя использовать бОльшие числа? 

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

Но лучше давайте сразу отделим мух от котлет. Вам нужна относительная погрешность вычислений 10^{-10} или абсолютная погрешность 10^{-10} (с сравнительно большей относительной)? В первом случае без double не обойтись, во втором - float вполне может и хватить.
PM   Вверх
kosmonaFFFt
Дата 27.8.2012, 07:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 538
Регистрация: 14.4.2008
Где: Иннополис

Репутация: нет
Всего: 5



Цитата(boostcoder @  26.8.2012,  16:56 Найти цитируемый пост)
ну или на худой конец, переходи на использование std::complex.

А чем поможет std::complex? Он ведь тоже базируется на double, float и т. д.


--------------------
user posted image
PM MAIL ICQ   Вверх
math64
Дата 27.8.2012, 07:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Лучше с самого начала везде использовать double - кроме массивов и других случаев, когда нужно экономить память.
Все функции из math.h работают с double - при их вызове float конвертируется в double, а результат наоборот.
Регистры математического сопроцессора имеют тип long double, команды сохранения из его регистра в float и загрузки из float более медленные.
Так что с double будет работать не только точнее, но и чуть быстрее.
А если точности double не хватает - нужна специализированная библиотека.
PM   Вверх
Alexeis
Дата 27.8.2012, 08:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



Не всегда поможет. Числа float и double имеют фиксированное число цифр. Например если к числу 9 000 000 прибавить 0. 000 009, то мы потеряем младшую часть, она будет попросту отброшена хоть то float, хоть то double. Бывает, что при вычислениях такие прибавления или умножения делаются в качестве промежуточных вычислений, а конечный результат малый. В этом случае можно получить погрешность в 1000% и больше. Кроме того на точность вычислений еще влияют настройки сопроцессора. Например при подключении DirectX автоматически снижается точность всех флоат вычислений и даже дабл будут считаться с точностью флоатов. Также потери точности легко добиться при возведении чисел в степень. Возвели в квадрат или куб, че нить прибавили, извлекли корень и младшие разряды пошли по бороде.

Добавлено через 4 минуты и 58 секунд
Цитата(math64 @  27.8.2012,  08:44 Найти цитируемый пост)
Так что с double будет работать не только точнее, но и чуть быстрее.

  Если говорить в общем случае, то это неверно. Лично я работал с аппаратурой где флоат считался раз в 50 быстрее дабла, не говоря уже об операциях типа копирования массивов или о объеме занимаемой памяти. Кроме того операции с флоатами могут оптимизироваться на SSE и в результате получаем скорости много выше.


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Нитонисе
Дата 27.8.2012, 10:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(boostcoder @  26.8.2012,  12:56 Найти цитируемый пост)
для подобных задач существует прекрасная и очень сильно проверенная библиотека GMP

А есть где-то обзор данной библиотеки на русском? И есть ли смысл полностью заменить float на данный тип? Речь идет о скорости вычислений и занимаемой памяти. Может быть уместно разделять задачи, где нужна повышенная точность и использовать там GMP и менее важные вычисления, где и float сойдет?

Цитата(Фантом @  26.8.2012,  15:36 Найти цитируемый пост)
Вам нужна относительная погрешность вычислений 10^{-10} или абсолютная погрешность 10^{-10} (с сравнительно большей относительной)?

Мне нужна точность хотябы в 10-ом знаке после запятой, чего float не позволяет. Порой заведомо зная что в некоторой части вычислений будут малые числа я специально их увеличиваю в 100 000 раз, чтобы программа не сочла где-то что значением малого числа можно пренебречь и приравнять его к нулю, в результате чего бывает ошибка деления на ноль.

Попутный вопрос. Имеет ли смысл увеличивать числа типа float с целью увеличения точности? Вот если записать float a=1, то можно получить a=0.99999957482. А если записать float a=100000, ведь не получится a=99999.957482, скорее что-то типа а=99999.99999957482. 

Это сообщение отредактировал(а) Нитонисе - 27.8.2012, 10:22
PM MAIL   Вверх
Alexeis
Дата 27.8.2012, 10:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



Цитата(Нитонисе @  27.8.2012,  11:21 Найти цитируемый пост)
Попутный вопрос. Имеет ли смысл увеличивать числа типа float с целью увеличения точности?

-  Нет. числа float отдельно хранят значащие цифры, отдельно степень. Просто увеличиться значение степени. float хранит 7-8 десятичных цифр. double что-то около 15-16. Чтобы сказать точнее нужно смотреть спецификацию. По степени запас флоата 10^38й степени. Точность в 10м знаке обеспечит только double (long double) и то если не будет операций умножения на большие числа или возведения в степень.

Добавлено через 4 минуты и 41 секунду
Можно кстати википедию почитать http://ru.wikipedia.org/wiki/%D0%A7%D0%B8%...%82%D0%BE%D0%B9
Более подробно описано.

Это сообщение отредактировал(а) Alexeis - 27.8.2012, 10:37


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Фантом
Дата 27.8.2012, 11:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вы это прекратите!
***


Профиль
Группа: Участник Клуба
Сообщений: 1516
Регистрация: 23.3.2008

Репутация: нет
Всего: 49



Цитата(Нитонисе @  27.8.2012,  11:21 Найти цитируемый пост)

Мне нужна точность хотябы в 10-ом знаке после запятой, чего float не позволяет. 

Вот меня и интересует, что Вы под этим понимаете. Вообще говоря, float позволяет работать с числами до 10^{-38}. только вот относительная погрешность будет плохой.

Цитата(Нитонисе @  27.8.2012,  11:21 Найти цитируемый пост)

Попутный вопрос. Имеет ли смысл увеличивать числа типа float с целью увеличения точности? Вот если записать float a=1, то можно получить a=0.99999957482. А если записать float a=100000, ведь не получится a=99999.957482, скорее что-то типа а=99999.99999957482. 

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

P.S. Кстати, полезно иметь в виду, что 99% проблем такого рода решаются не выбором типов, библиотек и т.п., а разумным преобразованием алгоритма вычислений. 
PM   Вверх
Нитонисе
Дата 27.8.2012, 11:43 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Фантом @  27.8.2012,  11:08 Найти цитируемый пост)
Вот меня и интересует, что Вы под этим понимаете.

Я может не совсем понимаю об чем речь smile Мне хотелось бы, чтобы умножая 10^{-10} на 10^{-10} я бы получил 10^{-20}.
PM MAIL   Вверх
Alexeis
Дата 27.8.2012, 11:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



Цитата(Фантом @  27.8.2012,  12:08 Найти цитируемый пост)
Вот меня и интересует, что Вы под этим понимаете. Вообще говоря, float позволяет работать с числами до 10^{-38}. только вот относительная погрешность будет плохой.

  Относительная погрешность постоянная. Она вычисляется как отношение погрешности к самой величине. Величина увеличилась, ну так и погрешность увеличилась пропорционально, а отношение сохранилось.

Добавлено @ 11:54
Цитата(Нитонисе @  27.8.2012,  12:43 Найти цитируемый пост)
Мне хотелось бы, чтобы умножая 10^{-10} на 10^{-10} я бы получил 10^{-20}. 

  Так и будет. Числа с плавающей точкой как раз для этого. Вопрос лишь сколько значащих цифр нужно сохранять.


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Нитонисе
Дата 27.8.2012, 12:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Alexeis @  27.8.2012,  11:53 Найти цитируемый пост)
Так и будет. Числа с плавающей точкой как раз для этого. Вопрос лишь сколько значащих цифр нужно сохранять.

Да нет, так не будет. Получается что-то типа 0.9999995742 Е-20.
PM MAIL   Вверх
Фантом
Дата 27.8.2012, 12:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вы это прекратите!
***


Профиль
Группа: Участник Клуба
Сообщений: 1516
Регистрация: 23.3.2008

Репутация: нет
Всего: 49



Цитата(Нитонисе @  27.8.2012,  12:43 Найти цитируемый пост)

Я может не совсем понимаю об чем речь smile Мне хотелось бы, чтобы умножая 10^{-10} на 10^{-10} я бы получил 10^{-20}. 


float для этого более чем достаточно.

Цитата(Нитонисе @  27.8.2012,  13:15 Найти цитируемый пост)

Да нет, так не будет. Получается что-то типа 0.9999995742 Е-20. 


Ну да, правильно. А что, собственно, Вы планировали получить? Операнды имеют относительную погрешность около 10^{-6}, при перемножении относительные погрешности складываются, соответственно, относительная погрешность результата имеет тот же порядок.

Добавлено через 2 минуты и 5 секунд
Цитата(Alexeis @  27.8.2012,  12:53 Найти цитируемый пост)

  Относительная погрешность постоянная. Она вычисляется как отношение погрешности к самой величине. Величина увеличилась, ну так и погрешность увеличилась пропорционально, а отношение сохранилось.

Не совсем. На границах возможных значений типа относительная погрешность в любом случае будет расти.
PM   Вверх
Нитонисе
Дата 27.8.2012, 12:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Фантом @  27.8.2012,  12:20 Найти цитируемый пост)
Ну да, правильно. А что, собственно, Вы планировали получить?

А планировал получить 1 Е-20 smile Собственно именно так я понимаю точность вычислений. У меня программа выполняет инженерные расчеты. Много возведений в степень, извлечений корней, тригонометрических функций, короче весь математический арсенал используется очень сильно. И вот в результате расчета я получаю некоторые величины. Существует проверка этих величин. Подставляя их в некоторые выражения я должен получить ноль. Но из-за всех этих неточностей вычисления у меня чистый ноль не получается. Может получиться 0.0001 или 0.1 (в зависимости от порядка тех самых первоначально искомых величин).
PM MAIL   Вверх
math64
Дата 27.8.2012, 12:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Цитата(Нитонисе @  27.8.2012,  12:15 Найти цитируемый пост)
Да нет, так не будет. Получается что-то типа 0.9999995742 Е-20.

В float под мантиссу выделяется 23 бита, это  6 знаков с небольшим запасом в десятичной системе. При умножении относительная погрешность удваивается.
Вот и получаете 6 знаков точности.
У long double - мантисса 64 бита, примерно 18 знаков точность. больше без спец. библиотеки не получите.
Денежные суммы можно хранить в копейках вместо рублей, чтобы не было ошибок округления, В других случаях умножение на коэффициент обычно не поможет.

Если решаешь уравнение:
X^2 + 2000*X + 1 = 0;
X1 = 1000 + sqrt(999999);
X2 = 1000 - sqrt(999999);
X2 лучше вычислять по формуле X2 = 1 / X1;

PM   Вверх
Фантом
Дата 27.8.2012, 12:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вы это прекратите!
***


Профиль
Группа: Участник Клуба
Сообщений: 1516
Регистрация: 23.3.2008

Репутация: нет
Всего: 49



Цитата(Нитонисе @  27.8.2012,  13:36 Найти цитируемый пост)

А планировал получить 1 Е-20 


Так Вы его и получили. Я совершенно серьезно: при имеющейся точности операндов этот ответ равен 1E-20.

Цитата(Нитонисе @  27.8.2012,  13:36 Найти цитируемый пост)
Собственно именно так я понимаю точность вычислений. У меня программа выполняет инженерные расчеты. Много возведений в степень, извлечений корней, тригонометрических функций, короче весь математический арсенал используется очень сильно. И вот в результате расчета я получаю некоторые величины. Существует проверка этих величин. Подставляя их в некоторые выражения я должен получить ноль.


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

Так что дело действительно не в типах и библиотеках. Вам надо просто честно получить оценку погрешности вычисляемых результатов, затем - такую же оценку для "нуля" в контрольном критерии, а затем сравнивать получающуюся невязку с нулем с учетом этой оценки. Если разность меньше - все нормально.
PM   Вверх
Нитонисе
Дата 27.8.2012, 13:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Фантом @  27.8.2012,  12:53 Найти цитируемый пост)
 Вам надо просто честно получить оценку погрешности вычисляемых результатов, затем - такую же оценку для "нуля" в контрольном критерии, а затем сравнивать получающуюся невязку с нулем с учетом этой оценки. Если разность меньше - все нормально.

А как оценить погрешность вычисления? Я-то сейчас просто грубо отсекаю числа меньше 0.001, как равняющиеся нулю. Но иногда такой подход нежелателен, я отсекаю не нули, а просто малые числа. А так бы действительно - сравнивал бы с погрешностью и жилось бы немного лучше smile
PM MAIL   Вверх
Alexeis
Дата 27.8.2012, 13:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



Цитата(Нитонисе @  27.8.2012,  13:15 Найти цитируемый пост)
Да нет, так не будет. Получается что-то типа 0.9999995742 Е-20. 

   Не все десятичные дробные представимы в двоичном виде не периодичной дробью + погрешность вычислений.

Добавлено через 5 минут и 23 секунды
Цитата(Нитонисе @  27.8.2012,  14:35 Найти цитируемый пост)
А как оценить погрешность вычисления? Я-то сейчас просто грубо отсекаю числа меньше 0.001, как равняющиеся нулю.

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


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
math64
Дата 27.8.2012, 13:48 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



При умножении, делении - относительные погрешности складываются.
При сложении и вычитании - складываются абсолютные погрешности.
Т.о. при сложении двух чисел одного знака относительная погрешность сохраняется.
Вычитания чисел одного знака следует избегать, по возможности использовать альтернативные формулы для вычислений.
PM   Вверх
Нитонисе
Дата 27.8.2012, 13:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Alexeis @  27.8.2012,  13:41 Найти цитируемый пост)
 Числа с плавающей точкой не сравнивают с нулем, если с ними производились математические операции. Правильнее проверить, что модуль разницы чисел меньше заранее заданной погрешности. 

Я так и поступаю. Просто я погрешность задаю жестко 0.001. Наверно это не лучший способ.

Цитата(math64 @  27.8.2012,  13:48 Найти цитируемый пост)
При умножении, делении - относительные погрешности складываются.При сложении и вычитании - складываются абсолютные погрешности.Т.о. при сложении двух чисел одного знака относительная погрешность сохраняется.Вычитания чисел одного знака следует избегать, по возможности использовать альтернативные формулы для вычислений.

А можно пример? А то несовсем понятно.
Код

float a = 1.23456789;
float b = 2.34567891;
float c = a*b + a;

Какова точность вычисления с?
PM MAIL   Вверх
Alexeis
Дата 27.8.2012, 14:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



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

dc = |b * da  + da| + |a * db|; 

Если da = db = eps - машинное эпсилон
Тогда dc = (|b + 1| + |a|) * eps



--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Нитонисе
Дата 27.8.2012, 14:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Alexeis @  27.8.2012,  14:13 Найти цитируемый пост)
Если da = db = eps - машинное эпсилон

А что такое машинное эпсилон?
PM MAIL   Вверх
Фантом
Дата 27.8.2012, 14:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вы это прекратите!
***


Профиль
Группа: Участник Клуба
Сообщений: 1516
Регистрация: 23.3.2008

Репутация: нет
Всего: 49



Цитата(Нитонисе @  27.8.2012,  14:35 Найти цитируемый пост)

А как оценить погрешность вычисления?


Хм... вообще говоря, в двух словах не расскажешь. Есть древняя, но хорошая книга Демидовича и Марона "Основы вычислительной математики", ее легко найти в сети. Скачайте и прочитайте первую главу, это при таких занятиях будет очень полезно.
PM   Вверх
Alexeis
Дата 27.8.2012, 14:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



Цитата(Нитонисе @  27.8.2012,  15:19 Найти цитируемый пост)
А что такое машинное эпсилон? 

http://ru.wikipedia.org/wiki/%D0%9C%D0%B0%...%BE%D0%BB%D1%8C


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Нитонисе
Дата 27.8.2012, 15:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Alexeis @  27.8.2012,  14:13 Найти цитируемый пост)
Тогда dc = (|b + 1| + |a|) * eps

Немного изменю вычисление.
Код

float a = 1.23456789;
float b = 2.34567891;
float c = a + b;

Погрешность вычисления получается dc = (a + b)*eps, где eps = 1E-5.
Точный результат сложения с = 3.5802468.
Погрешность dc = 0.000035802468.
'Это значит, что фактически я могу получить результат сложения с = c +/- dc, так?
Как же ж мне вычислить погрешность, когда у меня таких вчислений тысячи?  smile  параллельно основным вычислениям вести и расчет погрешности?


Цитата(Фантом @  27.8.2012,  14:27 Найти цитируемый пост)
Хм... вообще говоря, в двух словах не расскажешь. Есть древняя, но хорошая книга Демидовича и Марона "Основы вычислительной математики", ее легко найти в сети. Скачайте и прочитайте первую главу, это при таких занятиях будет очень полезно.

Я посмотрел самое начало - как я понял книга предназначена для приближенных ручных вычислений. Думаю приближенный ручной счет и приближенный машинный - не стоит сравнивать.
PM MAIL   Вверх
Alexeis
Дата 27.8.2012, 15:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



Цитата(Нитонисе @  27.8.2012,  16:09 Найти цитируемый пост)
Погрешность вычисления получается dc = (a + b)*eps, где eps = 1E-5.

  Неправильно. в данном случае максимальная погрешность будет 2 * eps . Производная функции y = a + b по а = 1, по b тоже равна 1, поэтому (1 + 1) * eps = 2 * eps .

Цитата(Нитонисе @  27.8.2012,  16:09 Найти цитируемый пост)
Как же ж мне вычислить погрешность, когда у меня таких вчислений тысячи?  smile  параллельно основным вычислениям вести и расчет погрешности?

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

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

  Приведу пример. У меня вычислялась экстраполяция параметра по времени. Для отсчета использовался виндовый высокоточный счетчик. Экстраполяция была квадратичной, поэтому аргумент возведенный в квадрат давал число, которое не влезало в дабл, соответственно младшие разряды красиво отрезались и после операций сложения у меня погрешность улетала в бесконечность. Ситуацию удалось исправить использовав в качестве аргумента не само значение счетчика, а разность между текущим значением и значением в опорной точке. Сразу же значение аргумента уменьшилось на 3-4 порядка, а возведенное в квадрат число стало меньше на 6-8 порядков. 




--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Нитонисе
Дата 27.8.2012, 15:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Alexeis @  27.8.2012,  15:44 Найти цитируемый пост)
Перед тем как забивать выражения в компьютер нужно оценивать порядки величин и величины погрешности, которые могут возникать на каждом участке выражения.

Дело в том, что это сложно. По одному и тому же алгоритму могут быть обсчитаны как очень крупные, так и очень малые числа...
PM MAIL   Вверх
math64
Дата 27.8.2012, 15:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Можете оценивать точность так:
Код

class Approx {
private:
  double min, max;
public:
  Approx(double _min, double _max) {
    if (_min <= _max) {
      min = _min; max = _max; 
    } else {
      min = _max; max = _min; 
    }
  }
  static Approx fromMinMax(double _min, double _max) {return Approx(_min._max); }
  static Approx fromDelta(double v, double delta) {return Approx(v-delta. v+delta); }
  static Approx fromEps(double v, double eps) {return Approx(v*(1.-eps). v*(1.+eps); }
  operator double () { return (min + max)/2; }
  double delta() { return (max - min)/2; }
  double eps() { return  (max > 0 && min > 0)  || (max < 0 && min < 0) ? (max - min)/(max+min) : DBL_MAX; }
  Approx operator+(Approx n2) {
     return Approx (min+n2.min, max+n2.max);
  }
  // Другие операторы
};


Это сообщение отредактировал(а) math64 - 27.8.2012, 16:02
PM   Вверх
Alexeis
Дата 27.8.2012, 16:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



Цитата(Нитонисе @  27.8.2012,  16:54 Найти цитируемый пост)
Дело в том, что это сложно. 

  Не сомневаюсь. Вероятно, следует использовать использовать различные тесты для проверки таких алгоритмов и делать шаг проверки аргументов экспоненциальным, чтобы охватить проверкой широкий спектр аргументов. Простого способа "сделать все хорошо" я не знаю  smile 


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Нитонисе
Дата 27.8.2012, 16:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(math64 @  27.8.2012,  15:57 Найти цитируемый пост)
Можете оценивать точность так:

Покажите на примере c = a + b как воспользоваться вашим классом. 
PM MAIL   Вверх
Фантом
Дата 27.8.2012, 17:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вы это прекратите!
***


Профиль
Группа: Участник Клуба
Сообщений: 1516
Регистрация: 23.3.2008

Репутация: нет
Всего: 49



Цитата(Нитонисе @  27.8.2012,  16:09 Найти цитируемый пост)

Как же ж мне вычислить погрешность, когда у меня таких вчислений тысячи?  smile  параллельно основным вычислениям вести и расчет погрешности?

Если аналитическими методами не воспользоваться - да.

Цитата(Нитонисе @  27.8.2012,  16:09 Найти цитируемый пост)
Я посмотрел самое начало - как я понял книга предназначена для приближенных ручных вычислений. Думаю приближенный ручной счет и приближенный машинный - не стоит сравнивать. 


В данном случае разницы никакой. У машинных вычислений есть дополнительные особенности, но сначала все равно стоит разобраться с общей базой. И... не сочтите за наезд, но, судя по задаваемым вопросам, Вы с вычислительной математикой совершенно не знакомы, так что отдельными комментариями на форумах тут не отделаться.

Цитата(Нитонисе @  27.8.2012,  16:54 Найти цитируемый пост)
Дело в том, что это сложно. По одному и тому же алгоритму могут быть обсчитаны как очень крупные, так и очень малые числа... 

А Вы уверены, что Вашу задачу нельзя предварительно обезразмерить? Ситуации, когда параметры вычислительного алгоритма действительно необходимо менять на несколько порядков, в реальной жизни встречаются очень редко.
PM   Вверх
math64
Дата 27.8.2012, 17:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Код

Approx a = Approx::fromDelta(1.5, 0.001); // a = 1.5 +- 0.001 // число с абс. погрешностью
Approx b = Approx::fromEps(0.8, 0.001; // b = 0.8 += 0.1% // число с относит. погрешностью
Approx c = a + b;
double v = c, delta = c.delta();// v - среднее значение, delta - абсолютная погрешность

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


Это сообщение отредактировал(а) math64 - 27.8.2012, 21:27
PM   Вверх
mrgloom
Дата 28.8.2012, 17:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 829
Регистрация: 8.6.2011

Репутация: нет
Всего: нет



 а вообще есть какая нибудь книжка про числа с плавающим знаком приминительно к численным методам?
PM MAIL   Вверх
Нитонисе
Дата 28.8.2012, 17:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(mrgloom @  28.8.2012,  17:34 Найти цитируемый пост)
а вообще есть какая нибудь книжка про числа с плавающим знаком приминительно к численным методам?

Присоединяюсь к вопросу. Хотелось бы поподробнее разобраться с практической реализацией вычислений и оценкой точности.
PM MAIL   Вверх
Randajad
Дата 28.8.2012, 19:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 295
Регистрация: 15.3.2012

Репутация: 8
Всего: 8



А есть еще __float128...
PM MAIL   Вверх
bsa
Дата 28.8.2012, 23:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 63
Всего: 196



Цитата(Нитонисе @  28.8.2012,  18:57 Найти цитируемый пост)
Присоединяюсь к вопросу. Хотелось бы поподробнее разобраться с практической реализацией вычислений и оценкой точности. 

Все очень просто. В компьютерах числа с плавающей точкой представлены в виде структуры, состоящей из 4-х полей: знак мантиссы (+ или -), мантисса (собственно само число), знак экспоненты и сама экспонента (степень двойки). Легче всего это понять тем, кто хорошо знает физику. Там всегда числа представлены в виде: +1,23456 * 10^-10. Тут тоже самое (+0100101 * 2 ^ -010111011). В отличие от физики у машин, обычно, все числа имеют ограничения по количеству знаков (т.е. цифр). Например, число одинарной точности может иметь 23 разряда мантиссы. Таким образом, это где-то 7-8 десятичных знаков. А теперь представим, что у нас есть число 1,2345678 и мы прибавляем к нему 0,00000001. Ожидается результат 1,23456781; но будет, например, 1,2345678. Это и есть одна из основных проблем. Именно поэтому числа с плавающей точкой нельзя использовать для финансовых расчетов. Другая проблема заключается в том, что числа могут быть почти равными, но не быть равными. При выводе на экран они равны, но операция сравнения возвращает false. Именно поэтому необходимо всегда сравнивать модуль разницы таких чисел с некоторым "малым числом" (эпсилон), которое выбирается согласно поставленной задаче, так как самая малая разница чисел с экспонентой +30 будет отличаться от самой большой для чисел с экспонентой -20.
Детально: Число одинарной точности
PM   Вверх
mrgloom
Дата 29.8.2012, 08:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 829
Регистрация: 8.6.2011

Репутация: нет
Всего: нет



http://redwood.berkeley.edu/w/images/b/bd/20080815pi.pdf

наткнулся на статью по CUDA  и Floating Point.

например страница 15-16 не знал что так бывает и что вообще надо над этим задумываться.
PM MAIL   Вверх
FCM
Дата 29.8.2012, 09:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 461
Регистрация: 30.3.2009

Репутация: нет
Всего: 9



Цитата(mrgloom @  28.8.2012,  17:34 Найти цитируемый пост)
а вообще есть какая нибудь книжка про числа с плавающим знаком приминительно к численным методам? 


http://www.softelectro.ru/ieee754.html

Кроме того, полезным будет посмотреть начало
Вержбицкий "Основы численных методов" 2-ое изд-е М.: "Высшая школа" 2005.
PM MAIL   Вверх
Нитонисе
Дата 29.8.2012, 11:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



FCM, немного не то. По вашей ссылке описано как хранит числа с плавающей запятой компьютер. Меня же больше интересует как программировать с учетом способа записи таких чисел компьютером. Некоторые примеры в описании стандарта автор привел, но это так - для общего сведения. Хотелось бы более почитать более объемную и системную работу в этом направлении. 
PM MAIL   Вверх
math64
Дата 29.8.2012, 13:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Нитонисе, если не волнует скорость вычислений, может восползоваться классом Approx (дописав нехватающие методы)
Принципы такие:
- каждое приближённое число хранится в виде пары (min, max)
- при каждом действии (сложение, вычитание, умножение, деление, ...) вычисляются новые (min, max).
- с учётом того, как хранятся данные в компьютере, min округляется в меньшую сторону, max в большую.
- при делении на число, приблизительно равное 0, результат (-DBL_MAX, DBL_MAX)
- если есть две формулы для вычислений (как я показывал на примере квадратного уравнения) можно почитать по обеим и объединить результаты.

PM   Вверх
FCM
Дата 30.8.2012, 11:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 461
Регистрация: 30.3.2009

Репутация: нет
Всего: 9



Цитата(Нитонисе @  29.8.2012,  11:49 Найти цитируемый пост)
FCM, немного не то. По вашей ссылке описано как хранит числа с плавающей запятой компьютер. Меня же больше интересует как программировать с учетом способа записи таких чисел компьютером. Некоторые примеры в описании стандарта автор привел, но это так - для общего сведения. Хотелось бы более почитать более объемную и системную работу в этом направлении. 


Вержбицкого все же посмотри и указанную там библиографию.
А вообще -  "Двойная точность - вежливость программистов" Саттер. 
PM MAIL   Вверх
Нитонисе
Дата 30.8.2012, 12:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



А насколько перспективна идея представлять все числа в виде дробей?
Код

class TNumber
{
    private:
        int Numerator;      // числитель
        int Denominator;   // знаменатель
        int Exp;                 // степень дополнительного множителя (десятка) для числителя (для больших чисел)
}

PM MAIL   Вверх
math64
Дата 30.8.2012, 13:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Если у тебя только операции + - * / можешь работать с дробями. Но вероятно, точности int и даже int64 не хватит

a/b + c/d = (a*d + b*c)/(b*d)

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

Ещё можно хранить числа в виде (мат. ожидание, среднее квадратическое отклонение), полагая что распределение близкое к нормальному.
PM   Вверх
Нитонисе
Дата 30.8.2012, 13:48 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



максимальное значение int - 2 147 483 647. Любую последовательность чисел можно представить 9-ю разрядами, если реальное число требует больше, то можно выделить еще один int для второй части числа из 9-ти разрядов. То есть например число 999 999 999 888 888 888 будет храниться в двух переменных типа int, в одной будет 999 999 999, во второй 888 888 888, только надо придумать как оперировать такими числами.
PM MAIL   Вверх
math64
Дата 30.8.2012, 14:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Как хранить число в двух int думать не надо, для этого есть __int64 (long long в gcc).
Для более длинных целых чисел можно найти в инете соответствующую библиотеку.
С помощью такой библиотеки можно посчитать Пи с тысячью знаков после запятой.
Только при хранении чисел в виде дробей это чаще всего будет не оправдано.
PM   Вверх
Нитонисе
Дата 30.8.2012, 15:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(math64 @  30.8.2012,  14:29 Найти цитируемый пост)
Только при хранении чисел в виде дробей это чаще всего будет не оправдано.

Потому что какие бы не были длинные числа в числителе и знаменателе - все равно будут переполняться?
PM MAIL   Вверх
Нитонисе
Дата 30.8.2012, 15:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Если я буду иметь возможность хранить числа в дробях, где числитель и знаменатель могут быть представлены числами из 300 (что соответсвует максимальной емкости типа double) или дальше больше знаков, неужели этого не хватит для практических вычислений? Конечно, в определенный момент может наступить переполнение, но ведь оно может наступить и при работе с double. При этом работая с double точность теряется, чем больше вычислений мы производим, а при работе с дробями не теряется вообще.
PM MAIL   Вверх
Alexeis
Дата 30.8.2012, 15:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 12
Всего: 459



Цитата(Нитонисе @  30.8.2012,  16:19 Найти цитируемый пост)
где числитель и знаменатель могут быть представлены числами из 300 (что соответсвует максимальной емкости типа double) или дальше больше знаков, неужели этого не хватит для практических вычислений?

   Какие еще 300 ? У дабла 15-16 цифр. Если нужно максимальное число цифр, то тут я думаю поможет __int64 . У него 19 значащих цифр.  По размеру он как double, только не хранит в себе показатель степени и вообще сам по себе число целое.


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Нитонисе
Дата 30.8.2012, 16:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(Alexeis @  30.8.2012,  15:32 Найти цитируемый пост)
 Какие еще 300 ? У дабла 15-16 цифр.

Я не про точность, а про емкость. Тип double может иметь максимальное значение состоящее из порядка трехсот знаков. При этом точность будет, как вы говорите, в 15-16-ти. А что если int будет иметь возможность хранить такие же большие числа? Он все равно не будет годиться для дробного представления чисел?

Я тут набросал класс super_int, который может хранить числа примерно такой же размерности как и double. Вот если ими представить дробь, неужели в практических вычислениях все равно будет переполняться этот тип?

Код

#include <vcl.h>

class super_int
{
    public:
        super_int();
        super_int(int v);
        super_int operator+(super_int n2);
        UnicodeString to_string();

    private:
        int involve(int n, int d);

    private:
        short int value[100];
        short int rang;
};

super_int::super_int()
{
    value[0] = 0;
    rang = 1;
}

super_int::super_int(int v)
{
    if (v >= 1000000000)
        rang = 4;
    else if (v >= 1000000)
        rang = 3;
    else if (v >= 1000)
        rang = 2;
    else
        rang = 1;
    for (short int i=0; i<rang; i++)
    {
        if (i != rang-1)
            value[i] = (v - involve(1000,i+1)*(v/involve(1000,i+1)))/involve(1000,i);
        else
            value[i] = v/involve(1000,i);
    }
}

super_int super_int::operator+(super_int n2)
{
    short int rang_max;
    if (rang > n2.rang)
        rang_max = rang;
    else
        rang_max = n2.rang;
    super_int n;
    n.rang = rang_max;
    for (short int i=0; i<rang_max; i++)
    {
        n.value[i] = n.value[i] + value[i] + n2.value[i];
        if (n.value[i] > 999)
        {
            n.value[i+1] = n.value[i] - 999;
            n.value[i] = 999;
            if (i == rang_max-1)
                n.rang++;
        }
        else n.value[i+1] = 0;
    }
    return n;
}

int super_int::involve(int n, int d)
{
    int rn = 1;
    for (int i=0; i<d; i++)
        rn *= n;
    return rn;
}

UnicodeString super_int::to_string()
{
    UnicodeString str = "";
    UnicodeString unit;
    for (short int i=0; i<rang; i++)
    {
        unit = IntToStr(value[i]);
        if (i != rang-1)
        {
            if (unit.Length() == 1)
                unit = "00" + unit;
            else if (unit.Length() == 2)
                unit = "0" + unit;
        }
        str = unit + str;
    }
    return str;
}


Основная идея - в каждом элементе массива value хранятся данные по трем разрядам. То есть число 123456 будет представлено как value[0] = 456, value[1] = 123, rang = 2. Таким образом можно хранить числа примерно 1E+300. Из математических операторов реализовал только оператор сложения super_int. Каковы перспективы представления дробей такими типами данных? То есть в числителе и знаменателе будут super_int.
PM MAIL   Вверх
FCM
Дата 30.8.2012, 16:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 461
Регистрация: 30.3.2009

Репутация: нет
Всего: 9



Цитата(Нитонисе @  30.8.2012,  15:19 Найти цитируемый пост)
При этом работая с double точность теряется, чем больше вычислений мы производим, а при работе с дробями не теряется вообще. 


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

PM MAIL   Вверх
math64
Дата 30.8.2012, 22:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



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

Вот как должно делаться некоторые операции с неограниченной разрядностью:
Код

typedef unsigned __int64 unit64;
class SuperInt {
int length;
unsigned* data;
SuperInt() { length = 0; data = NULL; }
public:
SuperInt(unsigned n) { data = new int[length = 1]; data[0] = n; }
SuperInt(uint64 n) { data = new int[length = 2]; data[0] = (unsigned)n; data[1] = (unsigned)(n >> 32); }
SuperInt(const SupeInt& n) { length = n.length; data = new int[length]; for(int i=0; i < length; i++) data[i] = n.data[i]; }
SuperInt(const char*s) {
  length = 1;
  data = new unsigned[1];
  data[0] = 0;
  while(*s >= '0' && *s <= '9') {
   *this = *this * 10U + SuperInt((unsigned)(*s-'0'));
  }
}
~SuperInt() { delete data; }
SuperInt operator+(const SupeInt& n) const{
  SuperInt res;
  res.length = max(length, n.length) + 1;
  res.data = new int[length];
  uint64 sum = 0;
  for (int i = 0; i < res.length; i++) {
     if ( i < length) sum += data[i];
     if ( i < n.length) sum += n.data[i];
     res.data[i] = (unsigned)sum;
     sum = sum >> 32;
  }
  return res;
}
SuperInt operator*(const unsigned n) const{
  SuperInt res;
  res.length = length + 1;
  res.data = new int[length];
  uint64 sum = 0;
  for (int i = 0; i < res.length; i++) {
     if ( i < length) sum += (uint64)data[i]*n;
     res.data[i] = (unsigned int)sum;
     sum = sum >> 32;
  }
  return res;
}
SuperInt operator/(const unsigned n) const{
  SuperInt res;
  res.length = length;
  res.data = new int[length];
  uint64 rem = 0;
  for (int i = res.length-1; i >=0 ; i--) {
    rem = rem << 32;
    rem += data[i];
    res.data[i] = (unsigned)(rem / n);
    rem = rem % n;
  }
  return res;
}
unsigned operator%(const unsigned n) const{
  uint64 rem = 0;
  for (int i = res.length-1; i >=0 ; i--) {
    rem = rem << 32;
    rem += data[i];
    rem = rem % n;
  }
  return (unsigned)rem;
}
char* toString(char* buf, int len) const {
  SuperInt tmp = *this;
  for (int i = len - 1; i >= 0; i--) {
     buf[i] = '0' + (tmp%10U);
     tmp = tmp / 10U;
  }
  return buf;
}
};

PM   Вверх
Нитонисе
Дата 31.8.2012, 00:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(math64 @  30.8.2012,  22:15 Найти цитируемый пост)
Как только будет не хватать значащих цифр - будут ошибки округления, и цель не будет достигнута.

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

Ну например - неужели не хватит для практических вычислений использование дробей, где числитель и знаменатель могут состоять из миллиона значащих цифр?
PM MAIL   Вверх
math64
Дата 31.8.2012, 07:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Цитата(Нитонисе @  31.8.2012,  00:22 Найти цитируемый пост)
Ну например - неужели не хватит для практических вычислений использование дробей, где числитель и знаменатель могут состоять из миллиона значащих цифр? 

Ну попробуй, используя только обычный int, без потери точности посчитать сумму первых членов ряда в виде дроби:
1 + 1/2 + 1/3 + 1/5 + 1/7 + 1/11 + 1/13 + ... + 1/p
До какого p хватит точности?

Сложение с неограниченным числом знаком, умножение и деление на обычное число, преобразование к десятичному виду я тебе написал (для чисел без знаков).
Возможно, в коде есть ошибки - писал прямо на экране. Например, нет оператора копирования.
Вычитание делается аналогично сложению.
Умножение в столбик написать тоже очень просто, быстрое умножение - сложнее.
Нужно добавить код для убирания незначащих нулей.
Преобразовывать в десятичный вид и обратно можно сразу по 9 цифр - деля и умножая на 1000000000U.
Дробь можно хранить в виде структуры (знак, числитель, знаменатель);

PM   Вверх
Нитонисе
Дата 31.8.2012, 09:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(math64 @  31.8.2012,  07:24 Найти цитируемый пост)
Ну попробуй, используя только обычный int, без потери точности посчитать сумму первых членов ряда в виде дроби:1 + 1/2 + 1/3 + 1/5 + 1/7 + 1/11 + 1/13 + ... + 1/pДо какого p хватит точности?

А зачем использовать обычный int? Надо использовать super_int с неограниченным количеством значащих цифр (ну, конечно, условно неограниченным). С вашим классом я, к сожалению, не разберусь. Либо у меня знаний языка не хватает, либо у вас опечатки. Наример член класса data указывает на неизвестный мне тип unsigned.
PM MAIL   Вверх
math64
Дата 31.8.2012, 11:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



unsigned - это ключевое слово языка C++. эквивалентно unsigned int -  тот же int, но без знака.
Если вам нужно для дробей, знак проще учитывать отдельно, чем реализовавать знаковую арифметику.
data указывает на массив беззнаковых unsigned int длиной length.
В полной реализации нужно две length: сколько выделено и сколько используется.
data[0] - младшие 32 бита, data[1] - следующие 32 бита и т.д.
Реализуется сложенене, вычитание, умножение, деление "в столбик", c "цифрой" unsigned int (беззнакое 32-разрядное целое в диапазоне 0..0xFFFFFFFFu).
При операциях с такими "цифрами" используется typedef unsigned __int64 uint64 - беззнакое 64-разрядное целое в диапазоне 0..0xFFFFFFFFFFFFFFFFuLL - две 32-разрядные "цифры" - чтобы не было переполнение. старшая "цифра" получается сдвигом на 32 бита.

PM   Вверх
Нитонисе
Дата 31.8.2012, 11:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Вот ваш оператор сложения. Прокомментируйте вопросы, которые я закомментировал.
Код

SuperInt operator+(const SupeInt& n) const{
  SuperInt res;
  res.length = max(length, n.length) + 1; // зачем увеличивать рамзер результата на 1? если складывать два числа 2+3 - увеличивать на 1 не нужно
  res.data = new int[length];  // здесь наверное должно быть не length, а res.length?
  uint64 sum = 0;
  for (int i = 0; i < res.length; i++) {
     if ( i < length) sum += data[i];
     if ( i < n.length) sum += n.data[i];
     res.data[i] = (unsigned)sum; // а если в sum(unit64) число больше, чем может хранить unsigned int?
     sum = sum >> 32; // поясните что делает этот код?
  }
  return res;
}

PM MAIL   Вверх
math64
Дата 31.8.2012, 15:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Код

SuperInt operator+(const SupeInt& n) const{
  SuperInt res;
  //Нитонисе: зачем увеличивать рамзер результата на 1? если складывать два числа 2+3 - увеличивать на 1 не нужно
  // А если 0xFFFFFFFF + 1 = 0x1,0x00000000 - была длина 1, стала 2 - рассчитываем на худший результат, заранее на знаем что получится.
  res.length = max(length, n.length) + 1; 
  // Нитонисе: здесь наверное должно быть не length, а res.length?
  // Правильно, опечатка
  res.data = new int[res.length];
  uint64 sum = 0;
  for (int i = 0; i < res.length; i++) {
     if ( i < length) sum += data[i];
     if ( i < n.length) sum += n.data[i];
     // Нитонисе: а если в sum(unit64) число больше, чем может хранить unsigned int?
     // Младшие 32 бита sum записываем в res.data[i], старшие переносим
     res.data[i] = (unsigned)sum; 
     // Нитонисе: поясните что делает этот код?
     // Сдвиг на 32 бита право. Аналогичен делению на 0x100000000UL
     sum = sum >> 32; 
  }
  res.pack(); // убираем незначащие нули
  return res;
}
// убрать незначащие нули
void pack() {
  int oldlength = length;
  while (length > 0) {
     if (data[length-1] != 0)
       break;
       length--;
  }
  if (length != oldLength) {
    unsigned* olddata = data;
    data = new data[length];
    for(int i=0; i < length; i++)
      data[i] = olddata[i];
    delete olddata;
  }
}

PM   Вверх
Нитонисе
Дата 31.8.2012, 16:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



math64, попробовал реализовать ваш класс. Исправил опечатки и мелкие неточности. Не разобрался с вашей функцией toString, мне проще применить для индекации возвращаемое значение типа UnicodeString. Но и без этой функции возникли ошибки утечки памяти. Что-то надо дорабатывать. И еще я так понял надо в класс добавить переменную, хранящую знак числа.

И вообще, насколько я понимаю суть вашего класса в том, что это "супер число" равно сумме чисел массива data? Это ж сколько может понадобиться памяти, чтобы представить число с миллионом значащих цифр?... Мне кажется это не лучший вариант. Я тут переделал свой класс super_int. Число хранится в std::vector<short int> value. Каждый элемент вектора не более 9999. Например число 111122223333 будет записано в таком векторе так value[0] = 3333, value[1] = 2222, value[2] = 1111. Мне кажется перспектив у такой конструкции больше с точки зрения хранения больших чисел с меньшими затратами памяти. На малых числах этот тип скорее будет проигрывать. Ведь если таким образом хранить число 1 уходит 32 байта. Правда я не урезаю вектор под фактическую заполненность. Он ведь вроде резервирует пустые ячейки для записи. 

Это сообщение отредактировал(а) Нитонисе - 31.8.2012, 17:01
PM MAIL   Вверх
math64
Дата 31.8.2012, 18:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



std::vector<> хранит внутри себя массив + небольшую кучку информации - простой массив, однозначно, требует меньше места.
Ты хранишь в каждом short число от 0 до 9999, когда unsigned short может хранить числа от 0 до 65535 - у тебя однозначно, уйдёт больше памяти.
У меня числа хранятся в двоичном виде - компактнее не придумаешь (с условием без потери точности). Но сложнее преобразовывать в десятичную систему.
Я использую unsigned (32 разряда) и unsigned __int64 (64 разряда) вместо unsigned short (16 разрядов) и unsigned - это не даёт экономии памяти, но ускоряет вычисления.
(32 разряда - не менее 9 десятичных цифр, против твоих 8 цифр в двух short)

Утечка памяти - я упоминал, что нужен оператор присваивания. без него будет утечка.

По уму, нужно реализовывать так:
Код

#include <stdio.h>
typedef unsigned __int64 uint64;
inline int max(int a, int b) { return a > b ?a : b; }
class SuperInt {
private: 
  struct Private { // все данные храним в защищенной структуре со счётчиком, позволяющим не копировать данные при присваивании
    int usecnt; // счётчик использования
    int alloc; // выделенная длина массива
    int length; // используемая длина массива
    unsigned data[1]; // данные; реально выделяем память больше, чем на 1 элемент
    static Private* allocate(unsigned n, int len) {
      Private* p = (Private*)new char[sizeof(Private) + sizeof(unsigned)*(len-1)]; // Выделяем память под cnt элементов массива
      p->usecnt = 1;
      p->alloc = len;
      p->length = len;
      for(int i=0; i < len; i++)p->data[i] = n;
      return p;
    }
    void inccnt() { usecnt++; } // увеличение счётчика при повторном использовании (в копирующем конструкторе и операторе присваивания)
    void deccnt() { if (--usecnt == 0) delete[] (char*)this; } // Выделяли память как char[] - удаляем как char[]
  };
  Private* p;
public:
  SuperInt(unsigned n=0, unsigned len = 1) { p = Private::allocate(n, len); }// Конструктор из обычного целого(при len=1)
  SuperInt(const SuperInt& n) { n.p->inccnt(); p = n.p; } // Копирующий конструктор
  SuperInt& operator=(const SuperInt& n) { // Оператор копирования
    if (p == n.p) return *this; // присваивание числа самому себе - ничего не делаем
    p->deccnt(); // если счётчик обнулился - будет удалено
    n.p->inccnt();
    p = n.p;
    return *this;
 }
 ~SuperInt() { // деструктор - данные удаляются, если использовались 1 раз
   //printf("~%d\n",p->usecnt);
   p->deccnt(); p = NULL; 
 }
 SuperInt operator+(const SuperInt& n) const {
   int len = max(p->length, n.p->length) + 1;
   SuperInt res (0, len);
   uint64 sum = 0;
   for(int i =0; i< len; i++) {
      if (i < p->length) sum += p->data[i];
      if (i < n.p->length) sum += n.p->data[i];
      res.p->data[i] = (unsigned)sum;
      sum >>= 32;
   }
   res.pack();
   return res;
 }
 SuperInt operator*(unsigned n) const {
   if (n == 0) return SuperInt(0);
   int len = p->length + 1;
   SuperInt res (0, len);
   uint64 sum = 0;
   for(int i =0; i< len; i++) {
      if (i < p->length) sum += (uint64)p->data[i]*n;
      res.p->data[i] = (unsigned)sum;
      sum >>= 32;
   }
   res.pack();
   return res;
 }
 SuperInt operator/(unsigned n) const {
   int len = p->length;
   SuperInt res (0, len);
   uint64 sum = 0;
   for(int i =len-1; i >= 0; i--) {
      sum <<= 32;
      sum += p->data[i];
      res.p->data[i] = (unsigned)(sum/n);
      sum %= n;
   }
   res.pack();
   return res;
 }
 unsigned operator%(unsigned n) const {
   int len = p->length;
   uint64 sum = 0;
   for(int i =len-1; i >= 0; i--) {
      sum <<= 32;
      sum += p->data[i];
      sum %= n;
   }
   return (unsigned)sum;
 }
 void pack() {
   while(p->length > 0 && p->data[p->length-1] == 0)
     p->length--;
 }
 unsigned toUint32() { return p->length == 0 ? 0  : p->data[0]; }
 double toDouble() {
   double res = 0;
   for(int i = p->length - 1; i >= 0; i--)
     res = res * 0x100000000LL + p->data[i];
   return res;
 }
 void print() {
   if (p->length == 0) { printf("0"); return; }
   if (p->length == 1) { printf("%d", p->data[0]); return; }
   (*this/1000000000U).print();
   printf("%09u", *this%1000000000U);
 }
 void println() {
   print();
   printf("\n");
 }
};
int main() {
SuperInt a = 2U;
SuperInt b = a;
a = 4U;
SuperInt c = a * 10U + b;
unsigned d = (c / 2U).toUint32();
printf("%u\n", d);
SuperInt x = 999999999U;
(x*x.toUint32()*x.toUint32()*x.toUint32()).println();
}

Проверил - код работает.

Учти: я пишу объявление методов класса и реализацию вместе для краткости; тебе их нужно будет разнести на .h и .cpp 
Если пользуешься Visual Studio - можешь рассмотреть возможность перехода на C#.
Там есть BigInteger готовом виде.
BigInteger есть также а Java - с исходниками (алгоритмы можно стянуть оттуда).
Наверняка есть готовая реализация и на C++ - но придётся искать в инете.

Это сообщение отредактировал(а) math64 - 1.9.2012, 20:30
PM   Вверх
Нитонисе
Дата 31.8.2012, 21:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(math64 @  31.8.2012,  18:29 Найти цитируемый пост)
Учти: я пишу объявление методов класса и реализацию вместе для краткости; тебе их нужно будет разнести на .h и .cpp 

Я пишу в C++ Builder XE и разделять код на заголовочный файл и файл реализации необязательно, скинул все в файл h и отредактировал (у вас было много опечаток). Проект скомпилирвал, но при попытке сложить два простых числа выдал ошибку, подсветив строку деструктора, где освобождается память в data. Я не стал дальше разбираться. Конечно ваш метод привлекает тем, что память используется максимально компактно и арифметические действия вроде просто реализуются (у меня сложнее). Возможно и скорость вычислений будет выше (хотелось бы кстати сравнить). Но мне с вашим кодом сложно разобраться  smile  

По поводу памяти выделяемой для моего типа данных. Использую std::vector<short int>. Выделяемая память - sizeof(super_int) = 32 байта. Добавил переменную short int для хранения знака. Размер почему-то стал 40 байт, хотя по идее должен был стать 34. Судя по размеру вектора - при инициализации он зарезервировал 32/2=16 ячеек. А это значит, что максимальное число, котороя я могу в него помсетить без дополнительного выделения памяти - 9999 E+60. Какое максимальное число вы можете разместить в своем SuperInt в таком же объеме памяти (32 байта)?

Это сообщение отредактировал(а) Нитонисе - 31.8.2012, 21:50
PM MAIL   Вверх
math64
Дата 31.8.2012, 22:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



9999E+60 - 64 цифры. У Вас 16 short, 32 байт (полезные данные) в куче, плюс накладные расходы sizeof(super_int) - как я понял, ещё 32 байт, за счёт использования vector<>. Итого 64 байта, если без выравниваний.
У меня будет 214 бит - 7 unsigned, 28 байт (полезные данные). Накладные расходы: usecnt, alloc, length, p - 16 байт, из них 12 перед 28 байтами в куче (40 байт) + 4 байта на стеке. Второе число с тем же значением стоит 4 байта, вне зависимости от значения.

Весь класс в одном заголовке - неплохо для тестирования.
В рабочей программе, когда заголовок будет инклюдиться много раз (а полная версия класса будет длиннее тестовой), это существенное замедление работы. 

В предудущем сообщении исправил код на рабочий, добавил умножение и деление на обычное число и тестовый main();

Это сообщение отредактировал(а) math64 - 31.8.2012, 23:17
PM   Вверх
Нитонисе
Дата 31.8.2012, 23:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(math64 @  31.8.2012,  22:34 Найти цитируемый пост)
Итого 64 байта

Нет. У меня член класса вектор (пустой) занимает 32 байта. Этот размер я замерил функцией sizeof(). Поскольку там тип short int (2 байта), значит зарезервировано 16 ячеек. При заполнении 17-ой будет выделяться новая память, насколько я понимаю. Так вот до этого самого выделения новой памяти я в 32 байтах могу разместить огромадное число 9999E+60. У вас же, насколько я понял, истинное значение числа равняется сумме чисел из массива data. Один элемент массива (unsigned int) занимает 4 байта. В 32 байтах поместится 8 таких чисел. Каждое число максимально равно 4.3E+9, умножив на 8 получим максимальное значение вашего типа на 32 байтах - 34.4Е+9. То есть намного меньше чем у меня. Это не учитывая обслуживающие дополнительные члены (для хранения знака или длины массива).
PM MAIL   Вверх
Нитонисе
Дата 1.9.2012, 09:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Все таки идея с представлением чисел дробями имеет слабые перспективы, так как я что-то не представляю как такие числа возводить в дробную степень (звлечение квадратных, кубических корней).
PM MAIL   Вверх
mes
Дата 1.9.2012, 10:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


любитель
****


Профиль
Группа: Участник Клуба
Сообщений: 7954
Регистрация: 14.1.2006

Репутация: 144
Всего: 250



Цитата(Нитонисе @  1.9.2012,  08:38 Найти цитируемый пост)
Все таки идея с представлением чисел дробями имеет слабые перспективы, так как я что-то не представляю как такие числа возводить в дробную степень (звлечение квадратных, кубических корней). 

такое представление чисел имеет  свое название (не могу сходу вспомнить),  есть математика для них и библиотечки попадались.. это я к тому что все это уже придумано и реализовано, гугль должен помочь.. 



--------------------
PM MAIL WWW   Вверх
Нитонисе
Дата 1.9.2012, 10:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(mes @  1.9.2012,  10:44 Найти цитируемый пост)
такое представление чисел имеет  свое название (не могу сходу вспомнить),  есть математика для них и библиотечки попадались.. это я к тому что все это уже придумано и реализовано, гугль должен помочь.. 

Да, но корень квадратный, например, из 3 не извлекается. Стало быть надо хранить корень (а в общем случае - дробную степень) просто как характеристику числа и использовать каким-то образом ее в вычислениях. Вырисовывается сложная задача.

Это сообщение отредактировал(а) Нитонисе - 1.9.2012, 10:54
PM MAIL   Вверх
math64
Дата 1.9.2012, 20:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Извлечение квадратного корня:
Код

SuperInt x = 999999999U;
SuperInt x2 = x*x*x*x + x*x; // Нужно реализовать полное умножение
SuperInt y = sqrt(x2.toDouble()); // Приближённое значение; Нужен коструктор из double
for( ) {
   SuperInt y2 = (y + x2 / y) / 2U; // Нужно полное деление
   if (y2 == y) break;
   y = y2;
}


Добавлено через 6 минут и 42 секунды
Цитата(Нитонисе @  1.9.2012,  10:53 Найти цитируемый пост)
но корень квадратный, например, из 3 не извлекается

А я сразу про подобное предупреждал. Большинство чисел - иррациональны и даже трасцендентны, и в копмьютере представляются только приближенно.
Хотя некоторые, например, Пи, можно посчитать с любой заданной точностью.
PM   Вверх
Нитонисе
Дата 1.9.2012, 21:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(math64 @  1.9.2012,  20:21 Найти цитируемый пост)
Извлечение квадратного корня:

ну то что его приближенно можно вычислить - это понятно. Но ведь вся идея была в том, чтобы вычисления были точными, поэтому приближенные методы не годятся. В начале темы говорили о библиотеке GMP, видимо это лучший способ реализовать высокую точность в расчетах? 

Хотел бы вернуться к названию темы. Где-то в первых постах говорили, что double не может повысить точность по сравнению с float. Но почему? Ведь double имеет точность 15 знаков, а float 7. То есть по идее от операций с double следует и точность бОльшую ожидать. 

Попутно возник вопрос. Если я где-то в вычислениях использовал разнотипные данные (например double умножаю или складываю с float), как это отразится на точности? При выполнении операции данные ведь преобразуются к одному типу. Вопрос - к какому?
PM MAIL   Вверх
math64
Дата 1.9.2012, 21:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Цитата(Нитонисе @  1.9.2012,  21:02 Найти цитируемый пост)
Если я где-то в вычислениях использовал разнотипные данные (например double умножаю или складываю с float), как это отразится на точности?

При умножении double на float, предварительно float преобразуется в double.
Если float представлял число точно (например, 2.f) - результат будет с точностью double.
Если нет - с точностью float.

Использование double НЕ УМЕНЬШИТ точность вычислений - но в некоторых случаях не повысит.
Тоже вычисление корня квадратного уравнения, близкого к 0 - использование double с традиционной формулой даст худший результат, чем вычисление другого корня в float и использование теоремы Виета.
PM   Вверх
Нитонисе
Дата 2.9.2012, 11:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(math64 @  1.9.2012,  21:41 Найти цитируемый пост)
При умножении double на float, предварительно float преобразуется в double.Если float представлял число точно (например, 2.f) - результат будет с точностью double.Если нет - с точностью float.

Допустим мне надо произвести высиления с точностью double. Как их правильно записать:
Код

// вариант 1
double a = 0.123456789;
double b = a * (1/3); // похоже здесь я получу результат a, так как 1 и 3 преобразуются в int?

// вариант 2
double a = 0.123456789;
double b = a * (1.0/3.0); // к какому типу будут преобразованы 1 и 3?



Цитата(math64 @  1.9.2012,  21:41 Найти цитируемый пост)
Использование double НЕ УМЕНЬШИТ точность вычислений - но в некоторых случаях не повысит.

То есть хуже не будет, а скорее всего будет лучше, но не всегда?  smile 

Цитата(math64 @  1.9.2012,  21:41 Найти цитируемый пост)
Тоже вычисление корня квадратного уравнения, близкого к 0 - использование double с традиционной формулой даст худший результат, чем вычисление другого корня в float и использование теоремы Виета.

Но вот вы сразу опровергли предыдущее свое утверждение, показав, что в некоторых случаях double хуже float. Только не понятно почему?


math64, вы предлагали использовать класс Approx для контроля погрешности. Я бы его немного преобразовал.
Код

class my_double
{
  private:
    double v;   // само число
    double dv; // дельта числа
}


При всех математических операциях dv будет меняться. После длительных вычислений я получу некое dv с которым будет более менее удобно работать, выполняя логические действия. Например если v < dv - значит число равно нулю. Сравнивая числа, если fabs(n1.v-n2.v) < max(n1.dv,n2.dv), то числа равны. Только вот не совсем понимаю как мне этот параметр dv вычислять. Подскажите чему он будет равен при стандартных математических операциях (+, -, *, /), а также при возведении числа в дробную степень и применении тригонометрических функций?

Это сообщение отредактировал(а) Нитонисе - 2.9.2012, 11:14
PM MAIL   Вверх
Нитонисе
Дата 2.9.2012, 11:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Да и еще подумал - что тольку от этого параметра dv, если он и сам вычисляется не точно, а с погрешностью...
PM MAIL   Вверх
FCM
Дата 2.9.2012, 18:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 461
Регистрация: 30.3.2009

Репутация: нет
Всего: 9



Переход к double решает многие проблемы. Можешь проверить, написав свою программу через typedef-псевдоним, в качестве которого выбирать либо float, либо double.
(Есть еще 80-битные и 128-битные floating point, но без веских оснований  не стоит заморачиваться.) 


Это сообщение отредактировал(а) FCM - 2.9.2012, 19:02
PM MAIL   Вверх
math64
Дата 2.9.2012, 20:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Код

double b = a * (1/3);

В результате получишь 0. 1 делится на 3 как целое число, результат = 0.
Нужно
Код

double b = a * (1./3);

или
Код

double b = a * ((double)1/3);


Код

double x2 = 1000. - sqrt(999999.);

лучше чем
Код

float x2 = 1000.f - (float)sqrt(999999.f);

но хуже
Код

float x2 = 1.f/(1000.f + (float)sqrt(999999.f));

а лучше всего
Код

double x2 = 1./(1000. + sqrt(999999.));

Ещё пример:
Нужно посчитать 1 + 1/2 + 1/3 + ... 1/n; где 1/n - намного меньше "компьютерого эпсилон".
Если будешь считать попорядку - начиная с какого-то члена добавление очередного слагаемого не будет изменять сумму, а ведь ряд расходится!
А вот если начнёшь складывать с конца - получишь результат с приемлемой точностью даже с double, не прибегая к точному вычислению с помощью дробей из длинных чисел.

Для контроля за погрешностью я предлагал два врианта:
1. (минимум, максимумум) - абсолютно точно известно, что число не вылезет за пределы диапазона.
Но по мере вычисления диапазон часто быстро расширяется, в то время как чило имеет нормальное распределение с максимумом где-то в середине диапазона.
Поэтому второй вариант:
2. (мат. ожидание, среднее квадратичное отклонение)
С вероятностью 95% (если правильно помню тер.вер.) число находится диапазоне 3 СКО.
Но всё таки всегда будет ненулевая вероятность, что число может оказаться равным 0.


Это сообщение отредактировал(а) math64 - 2.9.2012, 20:24
PM   Вверх
math64
Дата 2.9.2012, 20:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Ну а подсчёт  вероятности такой:
X = x + dx*nx; // nx - случайная величина нормального распределения СКО = 1, среднее = 0
Y = y + dy*ny;
Считая, что dy и dy достаточно малы,
Z = F(X,Y) = F(x,y) + dx*nx*dF/dx + dy*ny*dF/dy + ошибка вычислений.
где dF/dx и dF/dy - частные производные
и вычисляй СКО для
dx*nx*dF/dx + dy*ny*dF/dy + ошибка вычислений,
считая что ошибка вычислений - случайная величина с равномерным распределением (-eps*Z,eps*Z)

Это сообщение отредактировал(а) math64 - 2.9.2012, 20:47
PM   Вверх
Нитонисе
Дата 2.9.2012, 21:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(FCM @  2.9.2012,  18:56 Найти цитируемый пост)
Переход к double решает многие проблемы.

Похоже что так. Единственная для меня проблема здесь в логических операциях с double. Сравниваю так |a-b| < min. То есть если модуль разницы меньше некоего минимального числа, то a = b. Весь вопрос в том что выбрать за минимальное число. До сих пор работая с double я брал число 0.001. Для моих задач чаще всего хватало, но не всегда. Хотелось бы как-то обоснованней назначать его. Вот если бы параллельно основным вычислениям шел бы и расчет погрешности, то эту погрешность я бы и принимал в качестве минимальной разницы чисел.


math64
Код

double b = a * (1./3);
double b = a * ((double)1/3);

Это две эквивалентные записи? В первом случае ведь единица приводится к double неявно. Может она в вычислениях к float приведется? И почемы вы тройку не приводите к double? В операции деления она автоматически преобразуется в double, так как единица принадлежит этому типу? А что если не преобразуется? А если к double привести тройку, а единицу нет (1/(double)3) - то тройка преобразуется к int?

Правильна ли ниже запись цикла, где надо считать с точностью double, но счетчик цикла типа int.
Код

double a;
for (int i=0; i<10; i++) a *= ((double)i/3); //


Цитата(math64 @  2.9.2012,  20:23 Найти цитируемый пост)
Ещё пример:Нужно посчитать 1 + 1/2 + 1/3 + ... 1/n; где 1/n - намного меньше "компьютерого эпсилон".Если будешь считать попорядку - начиная с какого-то члена добавление очередного слагаемого не будет изменять сумму, а ведь ряд расходится!А вот если начнёшь складывать с конца - получишь результат с приемлемой точностью даже с double, не прибегая к точному вычислению с помощью дробей из длинных чисел.

Так ведь и если с конца начать складывать, то до некоторого числа сумма будет равняться нулю.

Цитата(math64 @  2.9.2012,  20:23 Найти цитируемый пост)
Для контроля за погрешностью я предлагал два врианта:1. (минимум, максимумум) - абсолютно точно известно, что число не вылезет за пределы диапазона.
С диапазоном просто чуть сложнее работать, потому что чтобы получить само число - надо выполнять среднее арифметическое. Смысла нет отводить две переменные под верхний и нижний диапазон, потому что они равноудалены от среднего значения. Другое дело, что будь-т эти диапазоны, будь-то абсолютная погрешность по моему вараинту - вычисляются. А коли они вычисляются, значит тоже будет присутствовать погрешность. А коль там числа крайне малые, то думается что и погрешность вычисления этой самой погрешности будет значительна.
PM MAIL   Вверх
FCM
Дата 3.9.2012, 07:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 461
Регистрация: 30.3.2009

Репутация: нет
Всего: 9



Цитата(Нитонисе @  2.9.2012,  21:18 Найти цитируемый пост)
Похоже что так. Единственная для меня проблема здесь в логических операциях с double. Сравниваю так |a-b| < min. То есть если модуль разницы меньше некоего минимального числа, то a = b. Весь вопрос в том что выбрать за минимальное число. До сих пор работая с double я брал число 0.001. Для моих задач чаще всего хватало, но не всегда. Хотелось бы как-то обоснованней назначать его. Вот если бы параллельно основным вычислениям шел бы и расчет погрешности, то эту погрешность я бы и принимал в качестве минимальной разницы чисел.


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

Это сообщение отредактировал(а) FCM - 3.9.2012, 07:54
PM MAIL   Вверх
math64
Дата 3.9.2012, 08:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Цитата(Нитонисе @  2.9.2012,  21:18 Найти цитируемый пост)
Это две эквивалентные записи?

В первом случае 1. - число типа double (1.f - число типа float), во втором - (double)1 - целое, приводимое к типу double. Во втором случае приведение производится на этапе компиляции, так что записи эквивлентны.
((double)1/3) и (1/(double)3) - тоже эквиваленты ((double)1/(double)3), так как приведение одного операнда к double заставляет компилятора приводить к double другой операнд. Деление будет тоже призводиться на этапе компиляции - так, что писать лучше (1./3) чем 0,333333333333
В случае ((double)i/3); приведение типа и деление будут выполняться на этапе выполнения; 3 будет заменена на 3, на этапе компиляции.

Цитата(Нитонисе @  2.9.2012,  21:18 Найти цитируемый пост)
Так ведь и если с конца начать складывать, то до некоторого числа сумма будет равняться нулю.

Вычисление призводится с плавающей точкой, а не фиксированной. члены ряда не будут равны 0 очень долго, но будучи прибавлены к накопленной сумме (большей 1), будут сначала терять младшие разряды точности, а затем вовсе не изменять сумму.
Точнее свего алгоритм суммурования такой:
1. слагаемые записываются в вектор
2. вектор сортируется по убыванию абсолютной величины.
3. если в векторе не меньше 2 чисел, два последних складываются и заменяются на сумму ; переход к пункту 2;
Можешь попробовать на тесте (сложение без сортировки float; сложение c сортировкой float; сложение без сортировки double; сложение c сортировкой double) и сравнить результаты. Правда, сортировка будет происходить неоправданно долго.


Цитата(Нитонисе @  2.9.2012,  21:18 Найти цитируемый пост)
 Смысла нет отводить две переменные под верхний и нижний диапазон, потому что они равноудалены от среднего значения.

В том то и дело, что не равноудалены. Поэтому практичнее считать СКО.
(1+-0.1) * (2+-0.1) = (0.9 .. 1.1) * (1.9 .. 2.1) = (1.71 .. 2.31) - середина 2.01, а не 2


PM   Вверх
Нитонисе
Дата 3.9.2012, 10:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(FCM @  3.9.2012,  07:46 Найти цитируемый пост)
Все же посмотри первую главу Вержбицкого, насчет различных видов ошибок  и методов их оценки

Я посмотрел. Сложный материал для восприятия. Занялся бы более тщательным изучением с целью применения на практике, да вот только смущает, что при вычислении погрешности также допускается погрешность. 

Цитата(math64 @  3.9.2012,  08:08 Найти цитируемый пост)
В первом случае 1. - число типа double
Дело в том, что где-то мне попадалась на глаза фраза, что если тип приведения не указан явно, то компилятор может преобразовать такое число и к double и к float. Так что лучше наверное указывать тип явно, тем более, что преобразование состоится на этапе компиляции, а значит выполнение будет производиться быстрее.

PM MAIL   Вверх
FCM
Дата 3.9.2012, 10:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 461
Регистрация: 30.3.2009

Репутация: нет
Всего: 9



Цитата(Нитонисе @  3.9.2012,  10:26 Найти цитируемый пост)
Занялся бы более тщательным изучением с целью применения на практике, да вот только смущает, что при вычислении погрешности также допускается погрешность. 

Погрешность погрешности будем величиной более малого порядка, но рефлексировать (или рекурсировать) на эту тему слишком глубоко не стоит. У Вержбицкого коротенько и наглядно обсуждаюся статистический и технический подходы к оценке погрешности совокупности большого числа вычислений.


Цитата(math64 @  3.9.2012,  08:08 Найти цитируемый пост)
В случае ((double)i/3); приведение типа и деление будут выполняться на этапе выполнения

Разве приведение будет на этапе выполнения?

Это сообщение отредактировал(а) FCM - 3.9.2012, 10:39
PM MAIL   Вверх
Нитонисе
Дата 3.9.2012, 10:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Немного оффтопный вопрос. Я раньше работал с float и не было проблем перевода float в строку и обратно (FloatToStr и StrToFloat). А что с double? Готовых функций нет? Надо самому писать, либо чьей-то самоделкой воспользоваться?

Добавлено через 7 минут и 51 секунду
Похоже FloatToStrF работает со значением long double. А строка в double похоже конвертируется функцией ToDouble.
PM MAIL   Вверх
Нитонисе
Дата 4.9.2012, 08:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 917
Регистрация: 5.11.2009

Репутация: нет
Всего: 2



Цитата(FCM @  3.9.2012,  10:38 Найти цитируемый пост)
Погрешность погрешности будем величиной более малого порядка

Почему? Ведь при высилении погрешности оперируем крайне малыми числами, близкими к машинному епсилон. Очень вероятно, что относительная погрешность таких операций будет велика.
PM MAIL   Вверх
math64
Дата 4.9.2012, 10:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2505
Регистрация: 12.4.2007

Репутация: 8
Всего: 72



Погрешность хранится отдельно от числа, в виде с плавающей точкой - достаточно точно.
После прибавления к основному числу точность, да теряется.
Но ведь сравниваются же два числа так:
Код

if (abs(x-y) < dx + dy) { ... }

или если храним квадрат СКО, при нормальном распределении, с допуском 3 СКО:
Код

double delta = x.value-y.value;
if (delta*delta < (x.deviation2 + y.deviation2)*9) { ... }

Главное место где теряется точность - вычисление delta.
Нужно также следить, чтобы при вычислении квадрата не было переполнения и антипереполнения.
PM   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.2712 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.