Читаю книгу Джозефа и Бена Албахари "C# 3.0 Полный справочник". В пункте "Ошибки округления вещественных чисел" написано следущее:
В техническом отношении типы float и double представляют числа в двоичной системе счисления. По этой причине только значения, которые могут быть выражены в двоичной системе, представлены точно. На практике это означает, что большинство литералов с дробной частью (выраженных в десятичной системе счисления) не будет представлено точно. Например:
Поэтому типы 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.
Но с этим примером что-то не то. Что они могли иметь в виду?
Здравствуйте, Mak_71_rus, Вы писали:
M__>Никакой ошибки не произошло. Я не понимаю о какой ошибке идёт речь. В моём понимании 0.1 будет записано так: M__>мантисса: 00...01 (не знаю сколько конкретно бит) M__>и степень: 10..01 (в смысле -1 в двоичном представлении)
Это неверное понимание. Степень, конечно же, пишется не десятичная, а двоичная.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Mak_71_rus, Вы писали:
M__>Какие ошибки, откуда?! Я понимаю, если это число 0.0000000000000000000000000000000000001, то оно во float будет 0. M__>Но с этим примером что-то не то. Что они могли иметь в виду?
Здравствуйте, Mak_71_rus, Вы писали:
H>>Какой у вас процессор?
M__>Intel Core 2 DUO E8200 @2,66GHz
M__>РЕжим компилляции: Debug, Any CPU (работаю на 64 разрядной Винде)
Такой эффект c нулем действительно наблюдаем на x64 из-за более широких регистров для чисел с плавающей точкой (ST0 — ST7).
Видимо в действительности вычисления происходят с максимальной аппаратной точностью (80 бит) и потому получается честный ноль на выходе, который преобразуется в 32-битное вещественное число.
Здравствуйте, Mak_71_rus, Вы писали:
H>>Какой у вас процессор?
M__>Intel Core 2 DUO E8200 @2,66GHz
M__>РЕжим компилляции: Debug, Any CPU (работаю на 64 разрядной Винде)
Ах да, забыл. Соберите в x86 (CLR не будет использовать расширенные ST0-ST7 регистры) и все встанет на свои места.
Здравствуйте, 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 точные цифры:
Кстати, вопрос вдогонку о double.
Почему при умножении с очень большими отрицательными степенями сильно увеличивается время выполнения?
Например, при умножении чисел double, если происходит недостаток точности 1e-320*1e-100 (в результате выдается 0), то время выполнения операции увеличивается в 8-10 раз.
S_>Кстати, вопрос вдогонку о double. S_>Почему при умножении с очень большими отрицательными степенями сильно увеличивается время выполнения? S_>Например, при умножении чисел double, если происходит недостаток точности 1e-320*1e-100 (в результате выдается 0), то время выполнения операции увеличивается в 8-10 раз.
как гипотеза: обычно числа с плав точкой нормализованы т.е. старший бит мантиссы неявно подразумевается 1. Для очень маленьких по модулю чисел же часто применяется gradual underflow в результате получаются субнормальные числа. Соответственно, субнормальные числа могут обрабатываться другими менее эффективными алгоритмами.
Здравствуйте, 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 считать нулем, но совсем ненужные тормоза включаются.