RE:double ошибки представления как победить?
От: Аноним  
Дата: 16.07.14 08:29
Оценка: -3
Здравствуйте, netch80, Вы писали:


Внесу небольшие исправления опечаток в код и подведу грустные итоги:

1) Исправления
вместо —

   f ( _d06, _d059999999999999998, "0.06", "0.059999999999999998" );
   f ( _d01, _d059999999999999998, "0.06", "0.059999999999999998" );


правим на :

   f ( _d06, _d059999999999999998, "0.06", "0.059999999999999998" );
   f ( _d06, _d059999999999999998, "0.06", "0.059999999999999998" );


2) Итоги.

Я плакаль. Ни один не догнал в чем суть "проблемы" но поучений было выданно "кучками".

N>Это Питон; '%' это аналог форматирования по printf. Как видите, после '%.53g' уже ничего нового нет — представление вывелось точно, и это никак не 0.059999999999999998


А причем тут нахрен питон — если тут С++ и формат double — 64 бита, 8 байт... Какие 53g )))



N>Разберитесь с этими всеми деталями и тогда сможете сформировать правильные вопросы.

N>Ну и рядом уже прозвучали советы, что надо начинать с алгоритма. Присоединяюсь к ним.

Очень грустно. Я тут давеча собеседование проходил, мне какие то вопросы с умным видом задавали с целью узнать не обманываю ли я их про себя — программист ли я.

Глядя на все выше прочитанное печаль берет — сколько народу надо гнать в шею. Атасс. ))

Но попробую упростить понимание для все —


double _d3 = 0.05 + 0.01;
double _d4 = 0.6 / 10.;


Чему должны быть равны d3 И d4?
Надеюсь сумели посчитать — 0.06.
Ну дык — возмите и стравните их нахрен.
Достал квт совсем.
Re[2]: double ошибки представления как победить?
От: LaptevVV Россия  
Дата: 16.07.14 08:49
Оценка: +3
Здравствуйте, Аноним, Вы писали:

А>
А>double _d3 = 0.05 + 0.01;
А>double _d4 = 0.6 / 10.;
А>


А>Чему должны быть равны d3 И d4?

А>Надеюсь сумели посчитать — 0.06.
Фигня. Это — на бумаге.
А в компьютере 0.1 точно в двоичную систему не переводится. Ибо периодическая дробь при переводе получается.
Она там либо с недостатком, либо с избытком. Сие зависит от некоторых битов в регистрах сопроцессоре.
Там 4 (четыре) варианта округления, между прочим!
А>Ну дык — возмите и стравните их нахрен.
А>Достал квт совсем.
Вместо истерики — повторите матчасть — сопроцессор Intel.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: double ошибки представления как победить?
От: uzhas Ниоткуда  
Дата: 16.07.14 08:59
Оценка: +1
Здравствуйте, Аноним, Вы писали:

А>Но попробую упростить понимание для все —


А>
А>double _d3 = 0.05 + 0.01;
А>double _d4 = 0.6 / 10.;
А>


А>Чему должны быть равны d3 И d4?

А>Надеюсь сумели посчитать — 0.06.
хороший пример, вот на нем хорошо поучиться. результат сложения не обязан быть 0.6. почему? выше уже написали почему : из-за неточного представления десятичных дробей в виде double

возвращаясь к исходной проблеме я предположу, что вам нужны вычисления с фиксированной десятичной точкой (fixed point decimal)
вики для понимания: http://en.wikipedia.org/wiki/Decimal_floating_point

могу порекомендовать две библиотеки:
Intel: https://software.intel.com/en-us/articles/intel-decimal-floating-point-math-library
IBM: http://speleotrove.com/decimal/

анализ производительности:
http://iccd.et.tudelft.nl/2009/proceedings/465Anderson.pdf
http://www.ibm.com/developerworks/rational/cafe/docBodyAttachments/3212-102-1-6349/DecimalFloatingPoint.pdf

только учтите, что скорость работы по сравнению с double печальная (разница в 1000 раз и более), зато девятки после запятой не возникают
Re[2]: double ошибки представления как победить?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.07.14 09:18
Оценка: +3
Здравствуйте, Аноним, Вы писали:

А>Я плакаль. Ни один не догнал в чем суть "проблемы" но поучений было выданно "кучками".


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

N>>Это Питон; '%' это аналог форматирования по printf. Как видите, после '%.53g' уже ничего нового нет — представление вывелось точно, и это никак не 0.059999999999999998

А>А причем тут нахрен питон — если тут С++ и формат double — 64 бита, 8 байт... Какие 53g )))

При том, что методы одинаковы и если Вы повторите то же самое на C++, получите ровно те же результаты. Хотите подтверждения? Рисуем код:

[c++]

#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double a = 0.06;
cout << setprecision(60) << a << endl;
return 0;
}
[/c++]

И что видим на выходе:

0.059999999999999997779553950749686919152736663818359375


На Питоне я показал результаты с тем же double внутри и тем же форматированием. Зато на нём не нужно компилировать, потому удобнее проверять такие простые вещи.

N>>Разберитесь с этими всеми деталями и тогда сможете сформировать правильные вопросы.

N>>Ну и рядом уже прозвучали советы, что надо начинать с алгоритма. Присоединяюсь к ним.
А>Очень грустно. Я тут давеча собеседование проходил, мне какие то вопросы с умным видом задавали с целью узнать не обманываю ли я их про себя — программист ли я.
А>Глядя на все выше прочитанное печаль берет — сколько народу надо гнать в шею. Атасс. ))

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

А>Но попробую упростить понимание для все —



А>
А>double _d3 = 0.05 + 0.01;
А>double _d4 = 0.6 / 10.;
А>


А>Чему должны быть равны d3 И d4?

А>Надеюсь сумели посчитать — 0.06.
А>Ну дык — возмите и стравните их нахрен.

Для Вас новость, что вычисления с плавающей точкой в принципе неточные? И что ни одно число, если оно не кратно степени двойки, не может быть представлено точно в каком-то из стандартных IEEE binary форматах? Тогда у Вас ещё очень много открытий. Вот, например, второй пример, которому учат в вузе на соответствующем курсе:

#include <stdio.h>
int main() {
        double i, last;
        for (i = 0.0; i < 10.0; i += 0.1)
                last = i;
        printf("%g\n", last);
        return 0;
}


Как вы думаете, какой ответ она напишет? 9.9? Нет, она напишет 10. Потому что внутри этот 10 является 9.9999999999999804600747665972448885440826416015625.

И вот там же на лекциях чётко говорят: хотите сравнивать числа на равенство, полученные из разных даже очень простых вычислений? Нет проблем, определите так называемый эпсилон для такого сравнения и проверяйте abs(_d3 — _d4) < epsilon.
Чему этот epsilon будет равен в Вашем случае — не знаю. Но если Вы хотите двух цифр после запятой, он равен 0.005.

А>Достал квт совсем.


Конечно, он "достанет", если, нифига в теме не зная и не представляя особенности, сунуться с заявлением "почините-ка это мне", считая, что это какие-то "ошибки представления". Нету тут ошибок представления, не надейтесь. Есть законно сделанные по всем правилам вычисления и есть естественные проблемы округлений при таких вычислениях. Не нравится — у Вас есть масса вариантов:
1. Применить вместо этого библиотеку работы с плавающей точкой с десятичными порядком и мантиссой. Это очень правильный метод, если задача — не из матфизики, а из финансов, бухгалтерии и т.д.
2. Применять округление вручную, как в прошлом моём ответе.
3. Сравнивать через epsilon, как в этом сообщении.
4. Уйти вообще от проблемы сравнения через замену алгоритма.
5. Бросить всё и заплакать.

Выбирайте.
The God is real, unless declared integer.
Re[3]: double ошибки представления как победить?
От: icWasya  
Дата: 16.07.14 11:57
Оценка: 9 (1)
Здравствуйте, netch80, Вы писали:

N>...

N>И вот там же на лекциях чётко говорят: хотите сравнивать числа на равенство, полученные из разных даже очень простых вычислений? Нет проблем, определите так называемый эпсилон для такого сравнения и проверяйте abs(_d3 — _d4) < epsilon.
N>Чему этот epsilon будет равен в Вашем случае — не знаю. Но если Вы хотите двух цифр после запятой, он равен 0.005.
N>...
N>Выбирайте.

Кстати, epsilon не всегда помогает. Например, как в вышеприведённом цикле.

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

float a,b,c,d,e;
int Result;

  a = 16.0;
  b = 2.0;
  c = 3.0;

  d = a + b / c;

  e = d * 3;

  if(e<50) 
    Result = 0;
  else
    Result = 5;

Так вот, на х86 результат равен нулю, а при портировании на один контроллер — 5
При анализе выяснилось, что на контроллере библиотека эмуляции плавающей точки
вычислила, что e точно равно 50.
Хотя на х86, как здесь и пишут, 49.9999980.
epsilon помогает при сравнении на равенство, а при сравнении на больше/меньше могут быть интересные эффекты.
Re[4]: double ошибки представления как победить?
От: B0FEE664  
Дата: 16.07.14 13:04
Оценка: +2
Здравствуйте, icWasya, Вы писали:

W>epsilon помогает при сравнении на равенство, а при сравнении на больше/меньше могут быть интересные эффекты.

epsilon надо применять при любом сравнении, а не только на равенство.
И каждый день — без права на ошибку...
Re[4]: double ошибки представления как победить?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 16.07.14 13:21
Оценка:
Здравствуйте, icWasya, Вы писали:

W>У меня был реальный случай.

W>В настоящем проекте использовались не константы и вычисления были раскиданы по большому коду,
W>а для простоты

W>float a,b,c,d,e;

W>int Result;

W> a = 16.0;

W> b = 2.0;
W> c = 3.0;

W> d = a + b / c;


W> e = d * 3;


W> if(e<50)

W> Result = 0;
W> else
W> Result = 5;

W>Так вот, на х86 результат равен нулю, а при портировании на один контроллер — 5

W>При анализе выяснилось, что на контроллере библиотека эмуляции плавающей точки
W>вычислила, что e точно равно 50.
W>Хотя на х86, как здесь и пишут, 49.9999980.

Кто пишет?
Я сейчас проверил — ровно 50. Это на: FreeBSD/i386 во всех режимах размерности (single, double, extended) через fpsetprec(); FreeBSD/amd64 опять со всеми размерами (хотя не уверен, что оно на что-то влияет — в коде видны mulss и divss); Linux/x86-64 (тоже mulss, divss).
Я даже пометил все переменные как volatile, чтобы запретить транслятору что-то оптимизировать. Всё равно не могу добиться никаких 49.9999980. Надо уточнить обстоятельства у того, кто выдал это число.

W>epsilon помогает при сравнении на равенство, а при сравнении на больше/меньше могут быть интересные эффекты.


Да, там добавляется вариант "ну типа меньше, но мы ещё не уверены"...
The God is real, unless declared integer.
Re[2]: double ошибки представления как победить?
От: andy1618 Россия  
Дата: 16.07.14 14:32
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Глядя на все выше прочитанное печаль берет — сколько народу надо гнать в шею. Атасс. ))


Перечитайте эти свои сообщения лет через пять — будет стыдно, уверяю!


А>Но попробую упростить понимание для все —


А>
А>double _d3 = 0.05 + 0.01;
А>double _d4 = 0.6 / 10.;
А>


А>Чему должны быть равны d3 И d4?

А>Надеюсь сумели посчитать — 0.06.

В десятичной системе (на бумаге) — да, но процессоры обычно двоичные, поэтому в _данном_ случае значение не будет в точности равным 0.06.
Вот вывод из Питона (лень С++ поднимать, там примерно та же картина будет):

>>> _d3=0.05 + 0.01
>>> print "%.30f" % _d3
0.060000000000000004718447854657
>>>
>>> _d4=0.6 / 10.
>>> print "%.30f" % _d4
0.059999999999999997779553950750


Как видно, тут ни разу не 0.06

Но вот если использовать точные двоичные дроби — всё будет хоккей:
>>> _d3=0.5 + 0.25
>>> print "%.30f" % _d3
0.750000000000000000000000000000
>>>
>>> _d4=7.5 / 10.
>>> print "%.30f" % _d4
0.750000000000000000000000000000


Собственно, об этом и писали люди выше.

Всё это очень известные грабли, усугубляемые тем, что в современных отладчиках любят округлять значения, т.е. вместо 0.060000000000000004718447854657 программисту могут показать обманчивые 0.06.
Re[5]: double ошибки представления как победить?
От: uzhas Ниоткуда  
Дата: 16.07.14 14:43
Оценка: 1 (1)
Здравствуйте, B0FEE664, Вы писали:

BFE>epsilon надо применять при любом сравнении, а не только на равенство.


зависит от специфики задачи. я бы рекомендовал пользоваться точным сравнением в купе с проверкой на равенство с точностью до epsilon. так яснее намерения будут видны.
проблема в сравнении вкупе с epsilon это то, что оно не транзитивно. сортировать контейнеры (std::set<double>, std::map<double, ...>) таким сравнением может вылиться в UB. точное сравнение даблов такой проблемой не обладает имхо.

на примере метакода я бы ввел разные функции:
bool IsClose(double x, double y, double epsilon)
{
  return abs(x - y) < epsilon;
}

bool IsCloseToZero(double x, double epsilon)
{
  return IsClose(x, 0.0, epsilon);
}

//usable for sorting
bool IsLessStrict(double x, double y)
{
  return x < y;
}

//not usable for container sorting
bool IsLess(double x, double y, double epsilon)
{
  return IsLessStrict(x + epsilon, y);
//or return IsLessStrict(x, y) && !IsClose(x, y, epsilon); <-- вот здесь на уровне кода мы подчеркиваем, что для близких значений мы не хотим, чтобы Less возрващал true
}
Re[3]: double ошибки представления как победить?
От: B0FEE664  
Дата: 16.07.14 18:11
Оценка:
Здравствуйте, andy1618, Вы писали:

А>>Глядя на все выше прочитанное печаль берет — сколько народу надо гнать в шею. Атасс. ))

A>Перечитайте эти свои сообщения лет через пять — будет стыдно, уверяю!
Вряд ли анониму будет стыдно — он просто хочет странного, такого желания никто не ожидает, вот и не могут его понять.

А>>Но попробую упростить понимание для все —

А>>
А>>double _d3 = 0.05 + 0.01;
А>>double _d4 = 0.6 / 10.;
А>>

А>>Чему должны быть равны d3 И d4?
А>>Надеюсь сумели посчитать — 0.06.
A>В десятичной системе (на бумаге) — да, но процессоры обычно двоичные, поэтому в _данном_ случае значение не будет в точности равным 0.06.

Аноним ранее писал:
А>>Понятно что есть "не точность", представления, понятно что есть "не точность вычисления".
А>>Но как быть с ошибкой сложения?
А>>То есть хотелось бы получив некое число (не известно как оно было полученно, например "сложением") как-то его, так "обработать" чтобы получить его равным форме представления.

Я не понимаю анонима, но вижу две "разумных" возможности:

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

2) аноним не знает как подсчитывать ошибки вычислений:

Но как быть с ошибкой сложения?

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

Странно здесь то, что ни первый, ни второй вариант практического смысла обычно не имеют. Я бы понял, если бы вопрос стоял так: есть число, хочу его распечатать в виде 0.0ddd +/- delta. А так это похоже на сферического коня в вакууме.
И каждый день — без права на ошибку...
Re[4]: double ошибки представления как победить?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 20.07.14 06:44
Оценка:
Здравствуйте, B0FEE664, Вы писали:

А>>>Глядя на все выше прочитанное печаль берет — сколько народу надо гнать в шею. Атасс. ))

A>>Перечитайте эти свои сообщения лет через пять — будет стыдно, уверяю!
BFE>Вряд ли анониму будет стыдно — он просто хочет странного, такого желания никто не ожидает, вот и не могут его понять.

Не за странное желание, оно как раз нормально — всё новое изначально "странное". А за наезды за то, что его никто не может понять, хотя формулировать он не умеет и даже не пытался.

BFE>Я не понимаю анонима, но вижу две "разумных" возможности:

BFE>1) на ошибки вычислений он хочет забить, а вот ошибку представления учесть.
BFE>Т.е., если я правильно понимаю все его пассажи, задачу можно переформулировать так:
BFE>есть число double после неизвестно каких вычислений. Задача: найти самую короткую обычную (не в экспоненциальной форме) десятичную запись числа соответствующую заданному числу в рамках точности представления double.

Как раз это ему не нужно с самого начала — примеры с этими двумя представлениями вокруг 0.06 с самого начала показали, что он видит разницу. Но при этом категорически не желает, чтобы железо давало ему эту разницу как реальную. Почему я и начал говорить о принудительном округлении. Он же в итоге выкрутился на сравнение через подсчёт количества дискретных шагов, которое отличает два значения. Возможно даже, увидев, что этих шагов в реальном случае мало (до 10), поставил какое-то значение с большим запасом (как 1000) и успокоился.
Нет, конечно, как временная затычка это годится. Но, судя по уровню апломба анонима, он думает, что эта затычка есть идеальное решение. Тогда ему предстоит ещё много открытий...
К сожалению, у подхода IEEE754 с непрерывной цепочкой бинарных представлений от 0 до INF есть и такие недостатки — искушение инженерного одоления сложных проблем.

BFE>2) аноним не знает как подсчитывать ошибки вычислений:

BFE>

BFE>Но как быть с ошибкой сложения?

BFE>Тогда можно предположить, что анониму нужно найти самую короткую десятичную запись с учётом ошибок не только представления, но и с учётом накопленных ошибок вычисления.

То есть округлить до нужной точности, если он вообще в состоянии подсчитать эту точность.

BFE>Странно здесь то, что ни первый, ни второй вариант практического смысла обычно не имеют. Я бы понял, если бы вопрос стоял так: есть число, хочу его распечатать в виде 0.0ddd +/- delta. А так это похоже на сферического коня в вакууме.


Так он и не хочет реально считать эти погрешности.
The God is real, unless declared integer.
Re[3]: double ошибки представления как победить?
От: slava_phirsov Россия  
Дата: 29.07.14 15:19
Оценка:
Здравствуйте, uzhas, Вы писали:

U>возвращаясь к исходной проблеме я предположу, что вам нужны вычисления с фиксированной десятичной точкой (fixed point decimal)


Что-то я не догоняю, а что, в формате с фиксированной точкой, скажем, 1.15 можно представить число 0.6 точно? Вот уж не думал
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[4]: double ошибки представления как победить?
От: rusted Беларусь  
Дата: 29.07.14 15:52
Оценка: 1 (1) +1
Здравствуйте, slava_phirsov, Вы писали:

_>Здравствуйте, uzhas, Вы писали:


U>>возвращаясь к исходной проблеме я предположу, что вам нужны вычисления с фиксированной десятичной точкой (fixed point decimal)


_>Что-то я не догоняю, а что, в формате с фиксированной точкой, скажем, 1.15 можно представить число 0.6 точно? Вот уж не думал


Вы слово decimal попустили.
Re[5]: double ошибки представления как победить?
От: pagid Россия  
Дата: 30.07.14 08:18
Оценка:
Здравствуйте, rusted, Вы писали:

R>Вы слово decimal попустили.

И как в decimal представить 1/3 точно?
... << RSDN@Home 1.2.0 alpha 5 rev. 1495>>
Re[6]: double ошибки представления как победить?
От: uzhas Ниоткуда  
Дата: 30.07.14 08:49
Оценка:
Здравствуйте, pagid, Вы писали:

P>И как в decimal представить 1/3 точно?


<шутка>
как 0, ибо целочисленное деление
</шутка>

точно — никак, ибо это бесконечная десятичная дробь
можно представить только приближенно
для подобных сценариев уже нужна работа с рациональными числами, например, как здесь: http://www.boost.org/doc/libs/1_55_0/libs/rational/rational.html
Re[7]: double ошибки представления как победить?
От: pagid Россия  
Дата: 30.07.14 14:04
Оценка:
Здравствуйте, uzhas, Вы писали:

U>для подобных сценариев уже нужна работа с рациональными числами, например, как здесь: http://www.boost.org/doc/libs/1_55_0/libs/rational/rational.html

Для каких именно? С вероятностью 99.999% у ТС не та задача для которой нужна подобная библиотека. Скорее всего double или может быть decimal вполне подойдут. Но первом случае об особенностях представления чисел и округлени помнить точно нужно, но и во втором может оказаться, что тоже не стоит забывать. Пример с 1/3 как раз был намёком на это.
... << RSDN@Home 1.2.0 alpha 5 rev. 1495>>
Re[8]: double ошибки представления как победить?
От: uzhas Ниоткуда  
Дата: 30.07.14 14:20
Оценка:
Здравствуйте, pagid, Вы писали:

P>Для каких именно?

для работы с 1/3 точно (для манипуляций типа сложения и деления на такие же числа, то есть дроби в целыми числителем и знаменателем)

P>С вероятностью 99.999% у ТС

ваш вопрос вообще не ясно к чему был и связь с проблемой ТС не видна, явный офтопик, на который я, видимо зря, дал прямой ответ
Re[9]: double ошибки представления как победить?
От: slava_phirsov Россия  
Дата: 01.08.14 20:19
Оценка:
Здравствуйте, uzhas, Вы писали:

U>ваш вопрос вообще не ясно к чему был и связь с проблемой ТС не видна, явный офтопик, на который я, видимо зря, дал прямой ответ


Рискну предположить, что проблема одна и та же. В случае 1.0 / 3.0, очевидно, что не получится сравнить результат деления с заранее известным значением, записанным литералом. По причине того, что в C++ нет синтаксиса записи бесконечных периодических десятичных дробей (кстати, а почему? Ау, WG21, добавьте в C++ эту фишку; пожалуйста!). В случае 6.0 / 10.0 это уже не очевидно — заранее известное значение выражается конечной десятичной дробью, почему бы не сравнить с ней? То, что компьютер считает не в десятичных, а в двоичных дробях, не сразу приходит в голову. Вот когда человечество (скорее бы уж!) откажется от десятичной системы счисления (все равно на пальцах уже никто не считает) и перейдет к 16-ричной, а десятичная система останется в истории, вместе с римскими цифрами и арифметикой без нуля — (кажущийся) парадокс исчезнет: 6.0 / 10.0, как и 1.0 / 3.0 выражается бесконечной периодической шестнадцатиричной дробью. Вот как-то так
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.