Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Что за фокусы с числами float<->double


Автор: Aprol 21.12.2009, 17:16
Первый фокус: Кусок кода
Код

public class KoordSpusk {
    ArrayList<Float> nv = new ArrayList<Float>();
    float[] nvi;
    float[] ni;
    float mx = 0, dx = 0, sko = 0;
    int k;

    public void prepare() {

        //чтение выборки
        try {
            FileInputStream inputStream = new FileInputStream("D:\\stat.txt");
            DataInputStream dataInputStream = new DataInputStream(inputStream);
            String line = "";
            while ((line = dataInputStream.readLine()) != null) {
                nv.add(Float.valueOf(line));
           
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
  float xmin = nv.get(0);
        float xmax = nv.get(0);
        for (int i = 0; i < nv.size(); i++) {
            if (xmin > nv.get(i)) xmin = nv.get(i);
            if (xmax < nv.get(i)) xmax = nv.get(i);
        }
        k = 10;
        float h = Math.abs(xmin - xmax) / (k);
        nvi = new float[k + 1];
        nvi[0]=xmin;
        for (int i = 1; i < nvi.length; i++) {
            nvi[i] = nvi[i-1] + h;
        }

На тестовых данных k=10, h=2.29,xmin=3.6,xmax=26.5.Это достаточно, чтобы заполнить массив nvi
 Делим интервал [3.6;26.5] с шагом 2.29, значения подынтервалов записываются в nvi.
По логике вещей значения должны быть следующими:
3,6
5,89
8,18
10,47
12,76
15,05
17,34
19,63
21,92
24,21
26,5

[0] = 3.6
[1] = 5.89
[2] = 8.18
[3] = 10.47
[4] = 12.76
[5] = 15.05
[6] = 17.34
[7] = 19.630001
[8] = 21.920002
[9] = 24.210003
[10] = 26.500004

Откуда эти хвосты в 7,8,910 элементах?
Второе:
mx=15.05
Код

 double a_t = mx,

После присваивания получаем
a_t= 15.050000190734863

Это откуда такой хвост?

Автор: LSD 21.12.2009, 17:26
Цитата(Aprol @  21.12.2009,  17:16 Найти цитируемый пост)
Откуда эти хвосты в 7,8,910 элементах?

Дело в том, что float и double хранятся как двоичная дробь, а ты оперируешь десятичными дробями. И проблема в том, что не всякая десятичная дробь может быть представлена как двоичная, приходится считать приближенно. Отсюда погрешность и берется.

Автор: Aprol 21.12.2009, 17:35
Странно, такие простые вычисления, а уже погрешность...
как мне быть в такой ситуации? округлять? но по каким правилам, чтобы в других данных не обрубить мне ничего нужного
Код

 double a_t = 15.05,

после присваивания a_t=15.05  как надо... что-то не догоняю... mx=15.05 откуда лаги...

Автор: MaxPayneC 21.12.2009, 17:47
LSD, не могли бы вы рассказать подробнее на тему двоичных и десятичных дробей, или дать ссылку где почитать?

Автор: LSD 21.12.2009, 18:02
Цитата(Aprol @  21.12.2009,  17:35 Найти цитируемый пост)
как мне быть в такой ситуации? округлять? но по каким правилам, чтобы в других данных не обрубить мне ничего нужного

Ну тут вопрос, в том что за вычисления ты делаешь. В принципе для расчетов с фиксированной точностью (n-знаков после запятой) хорошо подоходит BigDecimal, но у него один недостаток он медленно работает и требует много памяти (каждое вычисление создает новый объект). Еще есть такой класс http://commons.apache.org/lang/api-2.4/org/apache/commons/lang/math/Fraction.html он делает вычисления оперируя натуральными дробями, можешь попробовать его. Но вообще, надо побольше знать о задаче, чтобы посоветовать что-то конкретное.



Цитата(MaxPayneC @  21.12.2009,  17:47 Найти цитируемый пост)
LSD, не могли бы вы рассказать подробнее на тему двоичных и десятичных дробей, или дать ссылку где почитать? 

Базовое это стандарт http://en.wikipedia.org/wiki/IEEE_754. Еще есть http://user.cs.tu-berlin.de/~lordmaik/projects/IEEE754Converter/ieee754.htm, который показывает как это число будет представлено в памяти. Ну и можешь в качестве упражнения на бумажке попробовать привести дробь 5/100 к основанию 2^x smile

Автор: Aprol 21.12.2009, 18:15
Да в данном случае задача простая разбить интервал с шагом на подинтервалы, что и делает в первом куске кода.
А вообще задача состоит в оптимизации значения аргументов функции, то бишь математическая задача, и там вот такие левые хвосты не нужны... потому то алгоритм и не работает. Результат не сходится с тем что посчитано в экселе.

Цитата(LSD @  21.12.2009,  18:02 Найти цитируемый пост)
(каждое вычисление создает новый объект).

Это как? 

Автор: LSD 21.12.2009, 18:22
Цитата(Aprol @  21.12.2009,  18:15 Найти цитируемый пост)
Это как?  

Когда ты складываешь два BigDecimal числа, то старые не меняются, а создается новый с результатом вычисления суммы.



Цитата(Aprol @  21.12.2009,  18:15 Найти цитируемый пост)
А вообще задача состоит в оптимизации значения аргументов функции, то бишь математическая задача, и там вот такие левые хвосты не нужны... потому то алгоритм и не работает. Результат не сходится с тем что посчитано в экселе.

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

Автор: MaxPayneC 21.12.2009, 18:26
Цитата(LSD @  21.12.2009,  18:02 Найти цитируемый пост)
Базовое это стандарт IEEE 754. Еще есть IEEE-754 Conversion Applet, который показывает как это число будет представлено в памяти. Ну и можешь в качестве упражнения на бумажке попробовать привести дробь 5/100 к основанию 2^x smile


Спасибо)

Автор: Aprol 21.12.2009, 18:33
Цитата(LSD @  21.12.2009,  18:22 Найти цитируемый пост)
округляй после каждого шага. 

Какой-то есть метод для округления дробей не до целых чисел а до требуемого разряда?

Автор: COVD 21.12.2009, 18:37
Цитата

Результат не сходится с тем что посчитано в экселе.


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

Автор: Дрон 21.12.2009, 18:59
Цитата(Aprol @  21.12.2009,  18:33 Найти цитируемый пост)
Какой-то есть метод для округления дробей не до целых чисел а до требуемого разряда? 

Не сочтите за наезд, но можно узнать сколько вам лет? И место учёбы? Вопросы про погрешности в float/double встречаются регулярно, я на них даже иногда подробно отвечаю. А тут вот любопытно стало, кто же их всё-таки задаёт? smile

А насчёт округления... ну вот например до двух знаков после запятой:
Код
float a = 1.234567;
float b = Math.Round(a * 100) / 100f;


Добавлено @ 19:06
Цитата(Aprol @  21.12.2009,  17:35 Найти цитируемый пост)
после присваивания a_t=15.05  как надо... что-то не догоняю... mx=15.05 откуда лаги...

Недавно уже в разделе .NET приводил пример. Считаем в уме:
1 / 3 = 0.3333(3)
1/3 * 3 = 1
0.3333(3) * 3 = 0.9999(9)  1
Вот и тут то же самое smile 

Автор: Aprol 21.12.2009, 19:19
Цитата(Дрон @  21.12.2009,  18:59 Найти цитируемый пост)
1 / 3 = 0.3333(3)
1/3 * 3 = 1
0.3333(3) * 3 = 0.9999(9) ≠ 1

причем тут бесконечные дроби? когда результат вполне конкретен и равен он 15.05. Это можно ручками взять и посчитать smile 
Спасибо за пример, я нашел способ. ток мне кажется округляя мы только повышаем ошибку. Раньше она появлялась в меньших разрядах, а теперь мы поднимем ее до , скажем 10тысячных...

Конфиденциальные данные не разглашаю.

Автор: LSD 21.12.2009, 19:33
Цитата(Aprol @  21.12.2009,  19:19 Найти цитируемый пост)
причем тут бесконечные дроби?

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

Автор: Дрон 21.12.2009, 19:34
Цитата(Aprol @  21.12.2009,  19:19 Найти цитируемый пост)
причем тут бесконечные дроби? когда результат вполне конкретен и равен он 15.05

Это в десятичных дробях он конкретен. Допустим 15.05 нормально представляется в float, но во время вычислений где-то появилось непредставимое число (считай что бесконечная дробь) и в итоге появилась погрешность.
Я же пример с 1/3 не зря привёл -- результат вычислений (1 / 3) * 3 зависит от того, в какой системе мы считаем. Если считаем в обыкновенных дробях, то всё хорошо, если считаем в десятичных то есть погрешность.

Попробуй ещё вариант:
Код
System.out.println(0.1 * 3 - 0.3);

У меня Java под рукой нет, но результат должен быть совсем не 0.

Цитата(Aprol @  21.12.2009,  19:19 Найти цитируемый пост)
ток мне кажется округляя мы только повышаем ошибку. Раньше она появлялась в меньших разрядах, а теперь мы поднимем ее до , скажем 10тысячных

Не знаю. Не буду ничего утверждать, чтобы не вводить в заблуждение. Этому должны в институте учить... Меня учили, но давно smile
Недавно, кстати, столкнулся с проблемой, что при суммировании большого количества чисел результат был близким к 0 (что-то вроде 1e-10) и, к моему удивлению, в зависимости от того, какой тип я использовал у меня получался разный знак результата, а он-то мне как раз и был нужен smile
В Java нет встроенного типа decimal, как в C#, поэтому либо считать в double, либо в медленном BigDecimal, как сказал LSD.

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

Цитата(Aprol @  21.12.2009,  19:19 Найти цитируемый пост)
Конфиденциальные данные не разглашаю. 

Ок, проехали smile

Автор: serger 22.12.2009, 08:43
Цитата(Дрон @  21.12.2009,  19:34 Найти цитируемый пост)
Только вот мне кажется, что в твоей задаче вполне должно хватать точности double. Да даже те результаты во float, которые ты пишешь вполне нормальны -- это ж последовательное приближение, оно не может быть точным... Не говоря уж о том, что в реальном мире исходные данные не бывают точными, а считать с большей точностью, чем есть в исходных данных -- бесполезно smile

Особенно если это деньги...  smile 

Автор: jk1 22.12.2009, 11:52
Цитата

считать с большей точностью, чем есть в исходных данных -- бесполезно

В общем случае это не так и зависит от http://ru.wikipedia.org/wiki/%D0%A3%D1%81%D1%82%D0%BE%D0%B9%D1%87%D0%B8%D0%B2%D0%BE%D1%81%D1%82%D1%8C_(%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B)#.D0.90.D1.81.D0.B8.D0.BC.D0.BF.D0.B8.D0.BE.D1.82.D0.B8.D1.87.D0.B5.D1.81.D0.BA.D0.B0.D1.8F_.D1.83.D1.81.D1.82.D0.BE.D0.B9.D1.87.D0.B8.D0.B2.D0.BE.D1.81.D1.82.D1.8C численного метода.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)