Pull to refresh

Comments 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» например в bitcoinrpc.cpp
Спасибо большое! Весьма поучительно ;)
Деньги можно считать через 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;
}
Через 50 лет на Хабре будет очень трудно написать статью, чтобы она не была заминусована ворчливыми стариками… Помнится года 4 назад точно такая же статья стала хитом.
Судя по всему, имеется в виду эта статья («Что нужно знать про арифметику с плавающей запятой»). Так а вы сравните:
1) Здесь читается тезис: «А я вот узнал, что в Java...». В статье по ссылке — полномасшабный, основательный разбор стандарта IEEE754.
2) Статья по ссылке оформлена приятнее (ну, на мой личный взгляд).
3) Тема начинает быть избитой (см. тезис и рейтинг первого комментария).
Я тоже не пойму причем тут Java.
Насколько помню, округлять FP числа при выводе учат еще в школе, ну или институте курсе так на первом.
Инфа хранится в двоичной форме, выводится в десятичной, отсюда все проблемы.
А ну если хотите точных вычислений с точкой для денег или еще чего, используйте BigDecimal, если на то пошло.
> Инфа хранится в двоичной форме, выводится в десятичной, отсюда все проблемы.

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

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

Only those users with full accounts are able to leave comments. Log in, please.