Как стать автором
Обновить

Комментарии 32

Ну очень старая тема. Перетертая очень много раз и не только на хабре…
> Вопреки всем моим ожиданиям

А каковы были ваши ожидания от арифметики с плавающей точкой? 1.0? :)

Может быть тайну открою, но это не только в Java справедливо.
+1
Читал статью и недоумевал — а Джава то причем?

Кстати, по теме есть хороший текст на английском «What Every Computer Scientist Should Know About Floating-Point Arithmetic», David Goldberg, March, 1991

Доступен в т.ч. на сайте Оракл: docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
По-моему, это уже в школе преподают, не говоря уже о университете.
В какой школе, вы о чём? В школах либо учат алгоритмы чистки картошки, либо играют в Prehistoric/CS/Worms и т.д.
Ах да, ещё изучают стили Word и анимации PowerPoint.
Как минимум в школе, где информатику ведёт моя мама. А ещё у нас (электронщиков) в первом семестре информатики в университете было.
НЛО прилетело и опубликовало эту надпись здесь
Любимая книга детства
Очередное поколение узнаёт про существование сопроцессора.

(шёпотом) А ещё Деда Мороза не существует.
Конечно не ново, но мне кажется надо каждые лет пять-семь постить такой текст. Чтобы очередное поколение «хакеров» выучило. А то будут снова деньги через double считать…
Вы, конечно, будете очень смеяться, но биткойн-клиент считает именно с плавающей точкой.
(поищите «double» например в bitcoinrpc.cpp — я уж не буду копипастить код сюда).
Почему это было сделано так — мне лично не понятно.
В принципе деньги можно считать с плавающей точкой, если их количество не превышает определенного максимума и знаков после запятой тоже немного. Но зачем из буханки делать троллейбус?
Заинтересовался и посмотрел. Действительно на уровне API принимает double и конвертирует в int64_t. Странное решение, но на уровне протокола всё нормально. Только пользователям будем неудобно, нельзя посылать количество денег, которое бы и можно бы выразить на уровне протокола, да точности параметров API не хватает.

Ещё забавная проверка на «dAmount > 21000000.0». API не даст даже попытаться послать все деньги в мире сразу :)

Но, по крайней мере, на уровне bitcoin протокола всё в порядке.
Деньги можно считать через double. При этом никаких проблем с накоплением ошибки не будет, если округлять только на последнем шаге и не использовать округлённое значение в последующих расчетах.

Насколько я понимаю, главное правило при работе с валютой — никогда не использовать деление. То есть вместо деления пополам мы берем 50% (умножаем на 0.5). Таким образом мы, например, признваем, что невозможно разделить сумму на три. Можно это сделать только с заданной точностью. И это будет управляемо. B = A * 0.333

А все остальные операции — и сложение, и вычитание, и умножение не дают накопления ошибки. И разницы между использованием десятичной и двоичой системы счисления никакой.
При этом никаких проблем с накоплением ошибки не будет, если округлять только на последнем шаге
Проблемы могут быть, так как конечное представление чисел в любом случае требует промежуточных округлений. Если длина мантиссы 23 бита, то при перемножении двух чисел имеющих 20-битную мантису ошибки будут появляться.
>Начиная с процессоров Pentium модели MMX модуль операций с плавающей точкой интегрирован в центральный процессор.
Это немного пораньше произошло, последний сопроцессор вроде на 386-м был.
А что тогда это?
image
А это...
полноценный процессор, который не работал без i486SX, и ставился в слот для сопроцессора, и продавался как сопроцессор.
Иногда мне кажется, что это такой очень тонкий намёк. «Странно, почему-то не работают теги...»
Впрочем, имею право ошибиться ;)
upd: Упс, товарищ WarAngel_alk, это ответ вам.
Не хватает статьи как правильно складывать суммы с плавающей точкой, чтобы результат получался более точным.
Более точным в общем случае не получится, если не увеличивать разрядность мантиссы.
Да, можно порассуждать, что десять раз по 0.1 должен получиться литр… тьфу! килограмм… да нет же! единица ровно должна получиться.
А сколько должно получиться, если десять раз по 0.100000000001? А Пи-в-степени-E?
В общем, в стандартах явно указано с какой точностью считаются числа с плавающей точкой.
Для 99% вычислений — этого достаточно (но конечно нужно помнить, что вычисления не абсолютно точные)
Если вас стандартная точность не устраивает — можно исхитряться разными способами, но за счет уменьшения производительности
Есть кстати весьма полезных алгоритм суммирования Кохэна (вики), который сильно уменьшает погрешность суммирования.

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

Пример:

float[] floats = new float[100];
Arrays.fill(floats, 0.01f);

double[] doubles = new double[100];
Arrays.fill(doubles, 0.01);

float fsum = 0;
for (float x: floats) {
  fsum += x;
}
System.out.println(fsum);
// -> выведет 0.99999934, позорная ошибка округления

double dsum = 0;
for (double x: doubles) {
  dsum += x;
}
System.out.println(dsum);
// -> выведет 1.0000000000000007 - уже неплохо

System.out.println(kahanSum(floats));
// -> выведет ровно 1.0 - ура!


Само суммирование (из вики):
static float kahanSum(float... input) {
  float sum = 0;
  float c = 0;          
  for (float v : input) {
    float y = v - c; 
    float t = sum + y;
    c = (t - sum) - y;
    sum = t;
  }
  return sum;
}
Просто оставлю это здесь, для будущих поколений переводчиков. Товарищ кстати из Valve. ИМХО очень компетентен.
Через 50 лет на Хабре будет очень трудно написать статью, чтобы она не была заминусована ворчливыми стариками… Помнится года 4 назад точно такая же статья стала хитом.
Судя по всему, имеется в виду эта статья («Что нужно знать про арифметику с плавающей запятой»). Так а вы сравните:
1) Здесь читается тезис: «А я вот узнал, что в Java...». В статье по ссылке — полномасшабный, основательный разбор стандарта IEEE754.
2) Статья по ссылке оформлена приятнее (ну, на мой личный взгляд).
3) Тема начинает быть избитой (см. тезис и рейтинг первого комментария).
Я тоже не пойму причем тут Java.
Насколько помню, округлять FP числа при выводе учат еще в школе, ну или институте курсе так на первом.
Инфа хранится в двоичной форме, выводится в десятичной, отсюда все проблемы.
А ну если хотите точных вычислений с точкой для денег или еще чего, используйте BigDecimal, если на то пошло.
> Инфа хранится в двоичной форме, выводится в десятичной, отсюда все проблемы.

Побуду Капитаном Очевидностью, но скажу, что проблемы не только в этом.
Ни при каком способе хранения, ни при каком выводе ни в какой системе счисления вы не сможете точно представить любое вещественное число, просто потому что мощность множества вещественных чисел равна континууму, а количество разрядов в компьютере (и даже атомов в Солнечной системе, и даже фотонов в Галактике) конечно. Следовательно, для любого представления чисел в компьютере существуют числа которые не могут быть представлены (и обработаны) точно.
вы не сможете точно представить любое вещественное число

Да это нахрен не надо никому :-)
Стандартный 80 битный формат покрывает 99% вычислений — 19,20 знаков.
Для всего остального можно реализовать ручками, если припрет.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории