C# - Ошибки округления вещественных чисел?
От: Mak_71_rus Россия  
Дата: 15.07.10 17:19
Оценка:
Читаю книгу Джозефа и Бена Албахари "C# 3.0 Полный справочник". В пункте "Ошибки округления вещественных чисел" написано следущее:


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

float tenth = 0.1f;
float one = 1f;
Console.WriteLine(one - tenth * 10f); //-1.490116E-08


Поэтому типы float и double не подходят для экономических расчётов. В отличие от нихтип decimal работает в десятичной системе счисления и может точно представлять числа, выраженные в этой системе...


Я написал программку:


using System;
class ПерваяПрограмма
{
    static void Main()
    {
        Console.WriteLine("Привет, мир!");
        float Десятая = 0.1f;
        float Один = 1f;
        Console.WriteLine(Один - Десятая * 10f);
        Console.ReadKey();
    }
}


Результат её работы:


Привет, мир!
0


Никакой ошибки не произошло. Я не понимаю о какой ошибке идёт речь. В моём понимании 0.1 будет записано так:
мантисса: 00...01 (не знаю сколько конкретно бит)
и степень: 10..01 (в смысле -1 в двоичном представлении)
Какие ошибки, откуда?! Я понимаю, если это число 0.0000000000000000000000000000000000001, то оно во float будет 0.
Но с этим примером что-то не то. Что они могли иметь в виду?
Re: C# - Ошибки округления вещественных чисел?
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.07.10 17:40
Оценка: +1
Здравствуйте, Mak_71_rus, Вы писали:

M__>Никакой ошибки не произошло. Я не понимаю о какой ошибке идёт речь. В моём понимании 0.1 будет записано так:

M__>мантисса: 00...01 (не знаю сколько конкретно бит)
M__>и степень: 10..01 (в смысле -1 в двоичном представлении)
Это неверное понимание. Степень, конечно же, пишется не десятичная, а двоичная.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: C# - Ошибки округления вещественных чисел?
От: nikov США http://www.linkedin.com/in/nikov
Дата: 15.07.10 17:41
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

M__>Какие ошибки, откуда?! Я понимаю, если это число 0.0000000000000000000000000000000000001, то оно во float будет 0.

M__>Но с этим примером что-то не то. Что они могли иметь в виду?

Console.WriteLine(0.0003 * 10000 - 3);
Re[2]: C# - Ошибки округления вещественных чисел?
От: Mak_71_rus Россия  
Дата: 15.07.10 18:49
Оценка:
Что значит двоичная степень? как хранится число 0,0003?
Re[3]: C# - Ошибки округления вещественных чисел?
От: bober_maniac Россия http://bober-maniac.livejournal.com/
Дата: 15.07.10 19:34
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

M__>Что значит двоичная степень? как хранится число 0,0003?


Двоичная степень — это степень двойки.

Например, 0,5 — это 2^(-1), 0,25 — это 2^(-2), 0,125 — это 2^(-3), 0,0625 — это 2^(-4), и так далее.
Re: C# - Ошибки округления вещественных чисел?
От: stele Россия www.stele.su
Дата: 15.07.10 19:54
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

M__>Результат её работы:



M__>

M__>Привет, мир!
M__>0


А чем и как ты компилировал, что у тебя не получилось правильного ответа -1,490116E-08 ?
... << My edition based on RSDN@Home 1.2.0 alpha 4 rev. 1476 >>
В задаче спрашивается:
Сколько вытечет портвейна из открытого бассейна?
Re[2]: C# - Ошибки округления вещественных чисел?
От: Mak_71_rus Россия  
Дата: 16.07.10 05:32
Оценка:
S>А чем и как ты компилировал, что у тебя не получилось правильного ответа -1,490116E-08 ?

Visual Studio 2008 настройки компиллятора по умолчанию
Re: C# - Ошибки округления вещественных чисел?
От: hardcase Пират http://nemerle.org
Дата: 16.07.10 05:54
Оценка: +1
Здравствуйте, Mak_71_rus, Вы писали:


M__>Результат её работы:



M__>

M__>Привет, мир!
M__>0


Занятно.
А у меня:

Привет, мир!
-1.490116E-08

Какой у вас процессор?
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: C# - Ошибки округления вещественных чисел?
От: Mak_71_rus Россия  
Дата: 16.07.10 10:17
Оценка:
H>Какой у вас процессор?

Intel Core 2 DUO E8200 @2,66GHz

РЕжим компилляции: Debug, Any CPU (работаю на 64 разрядной Винде)
Re: C# - Ошибки округления вещественных чисел?
От: Mak_71_rus Россия  
Дата: 16.07.10 10:44
Оценка:
Так, понятно, что число с плавающей точкой представляется в виде
мантисса * 2^показатель.

Вопрос: каков алгоритм такого представления? Например, нужно представить число 0,0003.
Re[3]: C# - Ошибки округления вещественных чисел?
От: hardcase Пират http://nemerle.org
Дата: 16.07.10 10:46
Оценка: 1 (1)
Здравствуйте, Mak_71_rus, Вы писали:

H>>Какой у вас процессор?


M__>Intel Core 2 DUO E8200 @2,66GHz


M__>РЕжим компилляции: Debug, Any CPU (работаю на 64 разрядной Винде)


Такой эффект c нулем действительно наблюдаем на x64 из-за более широких регистров для чисел с плавающей точкой (ST0 — ST7).
Видимо в действительности вычисления происходят с максимальной аппаратной точностью (80 бит) и потому получается честный ноль на выходе, который преобразуется в 32-битное вещественное число.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[3]: C# - Ошибки округления вещественных чисел?
От: hardcase Пират http://nemerle.org
Дата: 16.07.10 10:54
Оценка:
Здравствуйте, Mak_71_rus, Вы писали:

H>>Какой у вас процессор?


M__>Intel Core 2 DUO E8200 @2,66GHz


M__>РЕжим компилляции: Debug, Any CPU (работаю на 64 разрядной Винде)


Ах да, забыл. Соберите в x86 (CLR не будет использовать расширенные ST0-ST7 регистры) и все встанет на свои места.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: C# - Ошибки округления вещественных чисел?
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 16.07.10 11:10
Оценка: 29 (3) +1
Здравствуйте, Mak_71_rus, Вы писали:

M__>Вопрос: каков алгоритм такого представления? Например, нужно представить число 0,0003.


Все очень просто. Во-первых, видно что ноль целых. А потом поехали:

0.0003 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0...)
0.0006 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00...)
0.0012 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.000...)
0.0024 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0000...)
0.0048 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00000...)
0.0096 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.000000...)
0.0192 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0000000...)
0.0384 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00000000...)
0.0768 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.000000000...)
0.1536 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0000000000...)
0.3072 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00000000000...)
0.6144 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.000000000001...)
0.2288 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0000000000010...)
0.4576 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00000000000100...)
0.9152 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.000000000001001...)
0.8304 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.0000000000010011...)
0.6608 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.00000000000100111...)
0.3216 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.000000000001001110...)
0.6432 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.0000000000010011101...)
0.2864 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00000000000100111010...)
0.5728 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.000000000001001110101...)
0.1456 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0000000000010011101010...)
0.2912 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00000000000100111010100...)
0.5824 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.000000000001001110101001...)
0.1648 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0000000000010011101010010...)
0.3296 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00000000000100111010100100...)
0.6592 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.000000000001001110101001001...)
0.3184 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0000000000010011101010010010...)
0.6368 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.00000000000100111010100100101...)
0.2736 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.000000000001001110101001001010...)
0.5472 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.0000000000010011101010010010101...)
0.0944 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.00000000000100111010100100101010...)
0.1888 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.000000000001001110101001001010100...)
0.3776 меньше чем 0.5, поэтому пишем 0, дробную часть умножаем на два                   (0.0000000000010011101010010010101000...)
0.7552 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.00000000000100111010100100101010001...)
0.5104 больше чем 0.5, поэтому пишем 1, из дробной части вычитаем 0.5 и умножаем на два (0.000000000001001110101001001010100011...)


Итого, в результате этого процесса мы получили 25 точные цифры:

0.000000000001001110101001001010100011...
             1234567890123456789012345


Формат IEEE подразумевает для 32-битного числа только 23 бита (но без первого, установленного в единицу)

Поэтому мантисса будет

00111010100100101010010

Курсивом выделено округление, поскольку 25-й бит равен единице, то по правилам, когда его откидываешь, надо добавлять единицу.
Re[3]: C# - Ошибки округления вещественных чисел?
От: Mak_71_rus Россия  
Дата: 17.07.10 18:44
Оценка:
Спасибо!
Re: C# - Ошибки округления вещественных чисел?
От: Silver_s Ниоткуда  
Дата: 20.07.10 16:16
Оценка:
Кстати, вопрос вдогонку о double.
Почему при умножении с очень большими отрицательными степенями сильно увеличивается время выполнения?
Например, при умножении чисел double, если происходит недостаток точности 1e-320*1e-100 (в результате выдается 0), то время выполнения операции увеличивается в 8-10 раз.
Re[2]: C# - Ошибки округления вещественных чисел?
От: dilmah США  
Дата: 20.07.10 16:28
Оценка:
S_>Кстати, вопрос вдогонку о double.
S_>Почему при умножении с очень большими отрицательными степенями сильно увеличивается время выполнения?
S_>Например, при умножении чисел double, если происходит недостаток точности 1e-320*1e-100 (в результате выдается 0), то время выполнения операции увеличивается в 8-10 раз.

как гипотеза: обычно числа с плав точкой нормализованы т.е. старший бит мантиссы неявно подразумевается 1. Для очень маленьких по модулю чисел же часто применяется gradual underflow в результате получаются субнормальные числа. Соответственно, субнормальные числа могут обрабатываться другими менее эффективными алгоритмами.
Re[3]: C# - Ошибки округления вещественных чисел?
От: Silver_s Ниоткуда  
Дата: 20.07.10 16:46
Оценка: 2 (1)
Здравствуйте, dilmah, Вы писали:

D>как гипотеза: обычно числа с плав точкой нормализованы т.е. старший бит мантиссы неявно подразумевается 1. Для очень маленьких по модулю чисел же часто применяется gradual underflow в результате получаются субнормальные числа. Соответственно, субнормальные числа могут обрабатываться другими менее эффективными алгоритмами.

Да, что-то там меняется если такая погрешность вылазит: 1e-300*1e-23 == 9.88131291682493E-324
А тормоза включаются уже на 1e-300*1e-8, сейчас проверил, раз в 20 медленнее чем 1e-300*1e-7
Это на Athlon, может на Intel по-другому?

var sw = new System.Diagnostics.Stopwatch();
sw.Start();

double v=0 ;
for (int i = 0; i < (int)1e7; i++)
   v = 1e-300 * 1e-8; //1e-7

sw.Stop();
var time= sw.Elapsed.TotalMilliseconds;


Но как-то это не хорошо. В результате моделирования чего-то вполне может что-то устремиться к нулю.
Такая точность может и не нужна. Хватило бы и 1E-307 считать нулем, но совсем ненужные тормоза включаются.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.