Re[12]: Округление? Почему так?
От: Niswn  
Дата: 03.07.08 16:46
Оценка: 13 (3)
Здравствуйте, zbanned, Вы писали:
Z>Как уже сказали на 4 сообщения выше — ошибка из-за преобразования и я это сразу же понял после первого поста.
Z>Я уже два раза преобразовал и в двоичном виде, и в десятичном, все равно не нашел момента где у нас теряется эта маленькая величина. Если мы разложим 0,9 как aq^n при q = 2 то у нас может быть только два варианта:
Z>0,9 ~= 9*2^-3 = 9/8 = 1,125
Z>или
Z>0,9 ~= 9*2^-4 = 9/16 = 0,5625
Z>Ни один из них не дает нам ни первоначальный 0.9, ни сишный 0,8999999.

Z>Помогите правильно преобразовать 0,9 в формат с плавающей запятой, я был бы очень рад.


Хорошо. Поясню на примере. gandjustas уже написал один пример, но нас учили немного по другому переводить число в двоичное, поэтому это может будет кому интересно (хотя нас этому учили на информатике в школе).
число 9 = 1001 (это надеюсь понятно)
число 0.9 НЕ РАВНО 0.1001
Нас учили так преобразовывать дроби:
1. берем число от 0 до 1.
2. умножаем его на два.
3. если число больше еденицы, то вычитаем еденицу (в результате опять получится число 0..1). И записываем 1 как i-тую цифру после запятой двоичного числа.
4. если число после умножение меньше еденицы, то ничего не делаем. И записываем 0 как i-тую цифру после запятой двоичного числа.
5. повторяем шаги 2-4 пока в результате не получится ноль.

0.9*2=1.8 => 0.8*2=1.6 => 0.6*2=1.2 => 0.2*2=0.4 => 0.4*2=0.8 => 0.8*2=1.6 => 0.6 и т.д.
      1            1            1            0            0            1

получаем двоичное число 0.111001.....
Это можно продолжаться до бесконечности, а мантисса у нас ограничена. отсюда и появляется погрешность. чем больше двоичное число будет содержать разрядов после запятой (а это определяется разрядностью мантиссы), тем меньше будет погрешность.
К примеру, число 0.75 сводится в ноль за 2 шага и в двоичном виде будет выглядеть как 0.11. Поэтому оно не будет иметь погрешность при преобразовании.
ICQ: 326084932
Округление? Почему так?
От: zbanned  
Дата: 03.07.08 05:36
Оценка: 4 (2)
Почему так? Округление?

C#:
int f;
f = (int)(10 * 0.9f); //returns 8
f = Convert.ToInt16(10 * 0.9f); //returns 9
Re[14]: Округление? Почему так?
От: Niswn  
Дата: 09.07.08 06:35
Оценка: 6 (1)
Здравствуйте, GGoga, Вы писали:

GG>Здравствуйте, Niswn, Вы писали:


N>>Хорошо. Поясню на примере. ... нас учили немного по другому переводить число в двоичное, поэтому это может будет кому интересно (хотя нас этому учили на информатике в школе).

N>>...

GG>А как по такой схеме перевести его обратно?


Да как и с целыми числами, только степень у двойки пойдет в отрицательную область.
^ — знак степени.
Для целых:
1001 = 1*2^0 + 0*2^1 + 0*2^2 + 1*2^3 = 1 + 8 = 9

Для дробных:
0.111001 = 1*2^(-1) + 1*2^(-2) + 1*2^(-3) + 0*2^(-4) + 0*2^(-5) + 1*2^(-6) = 0.890625
0.11 = 1*2^(-1) + 1*2^(-2) = 0.75
ICQ: 326084932
Re: Округление? Почему так?
От: Александр Кузнецов Россия  
Дата: 03.07.08 05:56
Оценка: 4 (1)
Здравствуйте, zbanned, Вы писали:

Z>Почему так? Округление?


Z>C#:

Z>
Z>int f;
Z>f = (int)(10 * 0.9f); //returns 8
Z>f = Convert.ToInt16(10 * 0.9f); //returns 9
Z>


Да. Округление. (см. MSDN)
public static short ToInt16 (
    double value
)

Return Value
value rounded to the nearest 16-bit signed integer. If value is halfway between two whole numbers, the even number is returned; that is, 4.5 is converted to 4, and 5.5 is converted to 6.

В то время как явное приведение типов через (int) возвращает ближайшее целое, меньшее, чем результат. А что касается внутреннего представления float, то никто не гарантирует, что 0.9f в памяти не хранится как 0.899999. Отсюда и проблемы с преобразованием.
"Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете". (с) Макконнелл, "Совершенный код".
Re[3]: Округление? Почему так?
От: Александр Кузнецов Россия  
Дата: 03.07.08 06:36
Оценка: 1 (1)
Здравствуйте, Аноним, Вы писали:

А>Где логика?)


Вот здесь:

double f1 = (10 * 0.9f); // 8
double f2 = (10 * 0.8f); // 8
double f3 = (10 * 0.7f); // 6
double f4 = (10 * 0.6f); // 6
double f5 = (10 * 0.5f); // 5
double f6 = (10 * 0.4f); // 4
double f7 = (10 * 0.3f); // 3
double f8 = (10 * 0.2f); // 2

Console.WriteLine("{0}-{1}", (int)f1, f1);
Console.WriteLine("{0}-{1}", (int)f2, f2);
Console.WriteLine("{0}-{1}", (int)f3, f3);
Console.WriteLine("{0}-{1}", (int)f4, f4);
Console.WriteLine("{0}-{1}", (int)f5, f5);
Console.WriteLine("{0}-{1}", (int)f6, f6);
Console.WriteLine("{0}-{1}", (int)f7, f7);
Console.WriteLine("{0}-{1}", (int)f8, f8);


Результаты:
8-8.99999976158142
8-8.00000011920929
6-6.99999988079071
6-6.00000023841858
5-5
4-4.00000005960464
3-3.00000011920929
2-2.00000002980232
"Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете". (с) Макконнелл, "Совершенный код".
Re[12]: Округление? Почему так?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 03.07.08 16:01
Оценка: 1 (1)
Здравствуйте, zbanned, Вы писали:

Z>Как уже сказали на 4 сообщения выше — ошибка из-за преобразования и я это сразу же понял после первого поста.

Z>Я уже два раза преобразовал и в двоичном виде, и в десятичном, все равно не нашел момента где у нас теряется эта маленькая величина. Если мы разложим 0,9 как aq^n при q = 2 то у нас может быть только два варианта:
Z>0,9 ~= 9*2^-3 = 9/8 = 1,125
Z>или
Z>0,9 ~= 9*2^-4 = 9/16 = 0,5625
Z>Ни один из них не дает нам ни первоначальный 0.9, ни сишный 0,8999999.
Какую-то ересь написал.

Z>Помогите правильно преобразовать 0,9 в формат с плавающей запятой, я был бы очень рад.


Мантисса вещественных чисел хранятся как разложение по 2^(-n).
0.9 — 2^(-1) = 0.9-0.5 = 0.4 в результат уходит 1
0.4 — 2^(-2) = 0.4 — 0.25 = 0.15 в результат уходит 1
0.15 — 2^(-3) = 0.15 — 0.125 = 0.025 в результат уходит 1
0.025 < 2^(-4) в результат уходит 0
0.025 < 2^(-5) в результат уходит 0
0.025 — 2^(-6) = 0.025 — 0.015625 = 0.009375 в результат уходит 1
...

Так можно продолжать долго, результат не станет нулевым. Тем не менее бит на мантиссу у нас ограниченное количество, поэтому возникают ошибки представления чисел с плавающей точкой и 0.9 на самом деле равно 0.89999999
Re[15]: Округление? Почему так?
От: GGoga  
Дата: 09.07.08 07:41
Оценка: +1
Здравствуйте, Niswn, Вы писали:

N>Да как и с целыми числами, только степень у двойки пойдет в отрицательную область.

N>^ — знак степени.
N>Для целых:
N>
N>1001 = 1*2^0 + 0*2^1 + 0*2^2 + 1*2^3 = 1 + 8 = 9
N>

N>Для дробных:
N>
N>0.111001 = 1*2^(-1) + 1*2^(-2) + 1*2^(-3) + 0*2^(-4) + 0*2^(-5) + 1*2^(-6) = 0.890625
N>0.11 = 1*2^(-1) + 1*2^(-2) = 0.75
N>



Огромное СПАСИБО!!!
Re[2]: Округление? Почему так?
От: Аноним  
Дата: 03.07.08 06:11
Оценка:
Где логика?)

int f1 = (int)(10* 0.9f); // 8
int f2 = (int)(10* 0.8f); // 8
int f3 = (int)(10* 0.7f); // 6
int f4 = (int)(10* 0.6f); // 6
int f5 = (int)(10* 0.5f); // 5
int f6 = (int)(10* 0.4f); // 4
int f7 = (int)(10* 0.3f); // 3
int f8 = (int)(10* 0.2f); // 2
Re[3]: Округление? Почему так?
От: _d_m_  
Дата: 03.07.08 06:34
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Где логика?)


Дьявол в деталях.

здесь
Автор: _d_m_
Дата: 19.05.08
Re[4]: Округление? Почему так?
От: zbanned  
Дата: 03.07.08 07:57
Оценка:
АК>Результаты:
АК>
АК>8-8.99999976158142
АК>8-8.00000011920929
АК>6-6.99999988079071
АК>6-6.00000023841858
АК>5-5
АК>4-4.00000005960464
АК>3-3.00000011920929
АК>2-2.00000002980232
АК>


Если заглянуть вглубь, то как представлена константа в формате IEEE c плавающей точкой?
Когда мы пишем 0.9 = то мантисса = 899999976158142 и запятая помещается после первого разряда?
Тогда почему бы мантиссу не представить как 9? Ведь все можно проще сделать? Откуда погрешность возникает?
Re[5]: Округление? Почему так?
От: _d_m_  
Дата: 03.07.08 08:04
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Тогда почему бы мантиссу не представить как 9? Ведь все можно проще сделать? Откуда погрешность возникает?


Потому что экспонента — степень двойки, а не 10-ки.
Re[6]: Округление? Почему так?
От: Овощ http://www.google.com
Дата: 03.07.08 10:15
Оценка:
Z>>Тогда почему бы мантиссу не представить как 9? Ведь все можно проще сделать? Откуда погрешность возникает?

Мантисса не может быть "представлена как 9", т.к. она хранится в нормализованном виде, т.е. для чисел с плавающей запятой (в десятичном представлении): 0.5 <= Мантисса < 1.0. И вот из-за неточного представления десятичных дробей в двоичном коде и появляется погрешность.
Re[7]: Округление? Почему так?
От: zbanned  
Дата: 03.07.08 10:57
Оценка:
Здравствуйте, Овощ, Вы писали:

Z>>>Тогда почему бы мантиссу не представить как 9? Ведь все можно проще сделать? Откуда погрешность возникает?


О>Мантисса не может быть "представлена как 9", т.к. она хранится в нормализованном виде, т.е. для чисел с плавающей запятой (в десятичном представлении): 0.5 <= Мантисса < 1.0. И вот из-за неточного представления десятичных дробей в двоичном коде и появляется погрешность.


Все равно не понял.
0.9 представим как a*(q^n)
а = мантисса = 9
q = система счисления = множитель = 10
n = показатель степени = -1
Итого: 0,9=9*10^-1
Откуда погрешность?
Если у нас число с плавающей запятой, то
— мантиссу 9 представим в двоичном виде как 1001
— множитель 10 = 1010
— степень -1 = 1111...11 (кол-во 1-ц в зависимости от разрядности)
Все это можно упаковать в 32 битное число с плавающей запятой без погрешности.

Поясните, где не прав?
Re[8]: Округление? Почему так?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 03.07.08 11:07
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Здравствуйте, Овощ, Вы писали:


Z>>>>Тогда почему бы мантиссу не представить как 9? Ведь все можно проще сделать? Откуда погрешность возникает?


О>>Мантисса не может быть "представлена как 9", т.к. она хранится в нормализованном виде, т.е. для чисел с плавающей запятой (в десятичном представлении): 0.5 <= Мантисса < 1.0. И вот из-за неточного представления десятичных дробей в двоичном коде и появляется погрешность.


Z>Все равно не понял.

Z>0.9 представим как a*(q^n)
Z>а = мантисса = 9
Z>q = система счисления = множитель = 10
Z>n = показатель степени = -1
Z>Итого: 0,9=9*10^-1
Z>Откуда погрешность?
Z>Если у нас число с плавающей запятой, то
Z>- мантиссу 9 представим в двоичном виде как 1001
Z>- множитель 10 = 1010
Z>- степень -1 = 1111...11 (кол-во 1-ц в зависимости от разрядности)
Z>Все это можно упаковать в 32 битное число с плавающей запятой без погрешности.

Z>Поясните, где не прав?


А ты распиши также число 9,346242 и поймешь в чем неправ.

На самом деле в компьютере q = 2
Re[9]: Округление? Почему так?
От: zbanned  
Дата: 03.07.08 11:27
Оценка:
G>А ты распиши также число 9,346242 и поймешь в чем неправ.
G>На самом деле в компьютере q = 2

Ок. Давай пока по проще. Хорошо, возмем q=2. Тогда получается большая погрешность:
0,9 ~= 9*2^-3 = 9/8 = 1,125
или
0,9 ~= 9*2^-4 = 9/16 = 0,5625

Откуда тогда берется 0,8999999?
Re[10]: Округление? Почему так?
От: Niswn  
Дата: 03.07.08 14:15
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Ок. Давай пока по проще. Хорошо, возмем q=2. Тогда получается большая погрешность:

Z>0,9 ~= 9*2^-3 = 9/8 = 1,125
Z>или
Z>0,9 ~= 9*2^-4 = 9/16 = 0,5625

Z>Откуда тогда берется 0,8999999?


Как уже было замечено эта погрешность происходит из-за преобразования чисел. Она заключается в том, что число 0.9 в компьютере хранится в двоичном виде, а не в десятичном, а вводишь ты его в десятичном. Попробуй преобразовать число 0.9 в двоичный вид, затем преобразовать обратно в десятичный, и ты увидишь, что это итоговое число будет близко к 0.9, но оно не будет ему равно, и величина этой погрешности зависит от количества знаков, которое занимает мантисса: чем больше разрядность мантиссы, тем меньше погрешность.
ICQ: 326084932
Re[11]: Округление? Почему так?
От: zbanned  
Дата: 03.07.08 15:14
Оценка:
Здравствуйте, Niswn, Вы писали:

N>Здравствуйте, zbanned, Вы писали:


Z>>Ок. Давай пока по проще. Хорошо, возмем q=2. Тогда получается большая погрешность:

Z>>0,9 ~= 9*2^-3 = 9/8 = 1,125
Z>>или
Z>>0,9 ~= 9*2^-4 = 9/16 = 0,5625

Z>>Откуда тогда берется 0,8999999?


N>Как уже было замечено эта погрешность происходит из-за преобразования чисел. Она заключается в том, что число 0.9 в компьютере хранится в двоичном виде, а не в десятичном, а вводишь ты его в десятичном. Попробуй преобразовать число 0.9 в двоичный вид, затем преобразовать обратно в десятичный, и ты увидишь, что это итоговое число будет близко к 0.9, но оно не будет ему равно, и величина этой погрешности зависит от количества знаков, которое занимает мантисса: чем больше разрядность мантиссы, тем меньше погрешность.


Как уже сказали на 4 сообщения выше — ошибка из-за преобразования и я это сразу же понял после первого поста.
Я уже два раза преобразовал и в двоичном виде, и в десятичном, все равно не нашел момента где у нас теряется эта маленькая величина. Если мы разложим 0,9 как aq^n при q = 2 то у нас может быть только два варианта:
0,9 ~= 9*2^-3 = 9/8 = 1,125
или
0,9 ~= 9*2^-4 = 9/16 = 0,5625
Ни один из них не дает нам ни первоначальный 0.9, ни сишный 0,8999999.

Помогите правильно преобразовать 0,9 в формат с плавающей запятой, я был бы очень рад.
Re[12]: Округление? Почему так?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 03.07.08 15:52
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Помогите правильно преобразовать 0,9 в формат с плавающей запятой, я был бы очень рад.


http://en.wikipedia.org/wiki/IEEE_754-1985
... << RSDN@Home 1.2.0 alpha 4 rev. 1095 on Windows Vista 6.0.6001.65536>>
AVK Blog
Re[13]: Округление? Почему так?
От: zbanned  
Дата: 03.07.08 17:24
Оценка:
N>
N>0.9*2=1.8 => 0.8*2=1.6 => 0.6*2=1.2 => 0.2*2=0.4 => 0.4*2=0.8 => 0.8*2=1.6 => 0.6 и т.д.
N>      1            1            1            0            0            1
N>


Народ, спасибо всем огромное за ответы! Все дошло до меня

Один вопрос остался: почему пошли именно таким путем деления? Почему бы не представить мантиссу 9 просто как 1001 (без деления)?
Т.е. можно же 32 битное число для плавающей запятой, просто разделить на две части на 16 и 16 бит и упаковать туда мантиссу со степенью, например так:
16 бит под мантиссу (знаковое 16-битное целое);
16 бит под степень (тоже знаковое 16-битное целое);

В итоге можно 0.9 представилось бы как
0000 0000 0000 1001 под мантиссу и
1111 1111 1111 1111 под степень в двоичном виде.

А число 9 представилось бы как:
0000 0000 0000 1001 под мантиссу и
0000 0000 0000 0000 под степень в двоичном виде.

И все считать по основанию 10ой системы. Для чего понадобилось таким хитрым способом переводить мантиссу делением?
Re[14]: Округление? Почему так?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 03.07.08 17:26
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Один вопрос остался: почему пошли именно таким путем деления? Почему бы не представить мантиссу 9 просто как 1001 (без деления)?


Аппаратная арифметика при чисто двоичном представлении проще. В 85 году, когда был выпущен IEEE 754, это было крайне важно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1095 on Windows Vista 6.0.6001.65536>>
AVK Blog
Re[14]: Округление? Почему так?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 03.07.08 18:21
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Один вопрос остался: почему пошли именно таким путем деления? Почему бы не представить мантиссу 9 просто как 1001 (без деления)?

Z>Т.е. можно же 32 битное число для плавающей запятой, просто разделить на две части на 16 и 16 бит и упаковать туда мантиссу со степенью, например так:
Z>16 бит под мантиссу (знаковое 16-битное целое);
Z>16 бит под степень (тоже знаковое 16-битное целое);

Z>В итоге можно 0.9 представилось бы как

Z>0000 0000 0000 1001 под мантиссу и
Z>1111 1111 1111 1111 под степень в двоичном виде.

Z>А число 9 представилось бы как:

Z>0000 0000 0000 1001 под мантиссу и
Z>0000 0000 0000 0000 под степень в двоичном виде.
И получили бы точность в 5 десятичных знаков. Сейчас бы еще больше плевались при таком раскладе.

Более того, такое кодирование избычно. Позволяет записать одно и тоже число несколькими способами, следовательно диапазон значений меньше.

Z>И все считать по основанию 10ой системы. Для чего понадобилось таким хитрым способом переводить мантиссу делением?

1)Однозначность хранимого числа
2)Оптимальность хранения
3)Быстродействие арифметических операций
Re[14]: Округление? Почему так?
От: Овощ http://www.google.com
Дата: 04.07.08 06:13
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Один вопрос остался: почему пошли именно таким путем деления? Почему бы не представить мантиссу 9 просто как 1001 (без деления)?

Z>Т.е. можно же 32 битное число для плавающей запятой, просто разделить на две части на 16 и 16 бит и упаковать туда мантиссу со степенью, например так:
Z>16 бит под мантиссу (знаковое 16-битное целое);
Z>16 бит под степень (тоже знаковое 16-битное целое);

Z>В итоге можно 0.9 представилось бы как

Z>0000 0000 0000 1001 под мантиссу и
Z>1111 1111 1111 1111 под степень в двоичном виде.

Z>А число 9 представилось бы как:

Z>0000 0000 0000 1001 под мантиссу и
Z>0000 0000 0000 0000 под степень в двоичном виде.

Z>И все считать по основанию 10ой системы. Для чего понадобилось таким хитрым способом переводить мантиссу делением?


Вот именно так и работает тип System.Decimal

A decimal number is a floating-point value that consists of a sign, a numeric value where each digit in the value ranges from 0 to 9, and a scaling factor that indicates the position of a floating decimal point that separates the integral and fractional parts of the numeric value.

The binary representation of a Decimal value consists of a 1-bit sign, a 96-bit integer number, and a scaling factor used to divide the 96-bit integer and specify what portion of it is a decimal fraction. The scaling factor is implicitly the number 10, raised to an exponent ranging from 0 to 28. Therefore, the binary representation of a Decimal value is of the form, ((-2^96 to 2^96) / 10^(0 to 28)), where -2^96-1 is equal to MinValue, and 2^96-1 is equal to MaxValue.

Re[13]: Округление? Почему так?
От: GGoga  
Дата: 08.07.08 08:39
Оценка:
Здравствуйте, Niswn, Вы писали:

N>Хорошо. Поясню на примере. ... нас учили немного по другому переводить число в двоичное, поэтому это может будет кому интересно (хотя нас этому учили на информатике в школе).

N>...

А как по такой схеме перевести его обратно?
Re[4]: Округление? Почему так?
От: PaulMinelly  
Дата: 09.07.08 20:36
Оценка:
АК>
АК>Console.WriteLine("{0}-{1}", (int)f1, f1);
АК>Console.WriteLine("{0}-{1}", (int)f2, f2);
АК>Console.WriteLine("{0}-{1}", (int)f3, f3);
АК>Console.WriteLine("{0}-{1}", (int)f4, f4);
АК>Console.WriteLine("{0}-{1}", (int)f5, f5);
АК>Console.WriteLine("{0}-{1}", (int)f6, f6);
АК>Console.WriteLine("{0}-{1}", (int)f7, f7);
АК>Console.WriteLine("{0}-{1}", (int)f8, f8);
АК>


Не совсем понял, на каком этапе проходит округление (потеря значащего куска)?
Ведь мы теряем значащие цифры в мантиссе когда представляем float число верно?
Т.е. мы представляем 0.9f и оно кодируется во флоат итерационным делением мантиссы на два как я понял. Все здорово, но почему тогда
Console.WriteLine("{0}", 0.9f); не выводит нам 8.99999976158142, а выводит 0.9? Почему нет потерь здесь при представлении float'a?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[5]: Округление? Почему так?
От: Ziaw Россия  
Дата: 10.07.08 05:25
Оценка:
Здравствуйте, PaulMinelly, Вы писали:

PM>Console.WriteLine("{0}", 0.9f); не выводит нам 8.99999976158142, а выводит 0.9? Почему нет потерь здесь при представлении float'a?


В примере все дело в конвертации в double,
Console.WriteLine("{0}", (double)0.9f);

выведет 0,899999976158142
... << RSDN@Home 1.2.0 alpha 4 rev. 1096>>
Re: Округление? Почему так?
От: jartur Россия http://jartur.l-square.net;
Дата: 10.07.08 07:42
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Почему так? Округление?


Z>C#:

Z>
Z>int f;
Z>f = (int)(10 * 0.9f); //returns 8
Z>f = Convert.ToInt16(10 * 0.9f); //returns 9
Z>


Вот здесь можно прочитать об этом очень подробно:
"What Every Computer Scientist Should Know About Floating-Point Arithmetic"
http://docs.sun.com/app/docs/doc/800-7895/6hos0aou4?a=view
蝸牛そろそろ登れ富士の山
Re[6]: Округление? Почему так?
От: PaulMinelly  
Дата: 10.07.08 08:36
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Здравствуйте, PaulMinelly, Вы писали:


PM>>Console.WriteLine("{0}", 0.9f); не выводит нам 8.99999976158142, а выводит 0.9? Почему нет потерь здесь при представлении float'a?


Z>В примере все дело в конвертации в double,

Z>
Z>Console.WriteLine("{0}", (double)0.9f);
Z>

Z>выведет 0,899999976158142

То есть это у дабла манитсса сохраняется делением по полам, а не у флоата? И есть константа какая-нибудь для double чтобы сразу писать вроде:
Console.WriteLine("{0}", 0.9d); // чтобы выводило 0,899999976158142
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[7]: Округление? Почему так?
От: Ziaw Россия  
Дата: 10.07.08 08:46
Оценка:
Здравствуйте, PaulMinelly, Вы писали:

PM>То есть это у дабла манитсса сохраняется делением по полам, а не у флоата? И есть константа какая-нибудь для double чтобы сразу писать вроде:

PM>Console.WriteLine("{0}", 0.9d); // чтобы выводило 0,899999976158142
0.9 без постфикса уже double, дополнительных указаний не требуется.
потеря происходит при конвертации 0.9f в double.
0.9 != (double)0.9f
... << RSDN@Home 1.2.0 alpha 4 rev. 0>>
Re[7]: Округление? Почему так?
От: PaulMinelly  
Дата: 10.07.08 08:51
Оценка:
Z>>В примере все дело в конвертации в double,
Z>>
Z>>Console.WriteLine("{0}", (double)0.9f);
Z>>

Z>>выведет 0,899999976158142

PM>То есть это у дабла манитсса сохраняется делением по полам, а не у флоата? И есть константа какая-нибудь для double чтобы сразу писать вроде:

PM>Console.WriteLine("{0}", 0.9d); // чтобы выводило 0,899999976158142

Нашел:
Console.WriteLine("{0}", 0.9D); //returns 0.9
все равно выводит 0.9
Не понятно на каком участке теряются числа. Т.е. почему при выводе 0.9 double — не происходит потеря, при выводе 0.9 float тоже не происходит, а при конвертации из флоата в дабл происходит?

Console.Write("{0}\n", 0.9D); // returns 0.9
Console.Write("{0}\n", 0.9f); // returns 0.9
Console.Write("{0}\n", (float)0.9D); // returns 0.9
Console.Write("{0}\n", (double)0.9f); // returns 0.899999976158142

Где происходит потеря? Все сообщения перечитал — не помогло.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[8]: Округление? Почему так?
От: PaulMinelly  
Дата: 10.07.08 08:51
Оценка:
PM>>То есть это у дабла манитсса сохраняется делением по полам, а не у флоата? И есть константа какая-нибудь для double чтобы сразу писать вроде:
PM>>Console.WriteLine("{0}", 0.9d); // чтобы выводило 0,899999976158142
Z>0.9 без постфикса уже double, дополнительных указаний не требуется.
Z>потеря происходит при конвертации 0.9f в double.
Z>0.9 != (double)0.9f

Почему так происходит?
Ведь
0.9 == 0.9f
0.9 == 0.9D
0.9 != (double)0.9f

Из-за того что считывается странным образом константа при конвертации? Ведь 0.9 == 0.9f и 0.9 == 0.9D, почему 0.9 != (double)0.9f?
Как это увязать с кодировкой мантиссы итерационным делением на два? Ведь в дабл 0.9 == 0.9D все нормально кодируется и во флоат тоже? Почему потеря только при конвертации происходит?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[7]: Округление? Почему так?
От: Овощ http://www.google.com
Дата: 10.07.08 09:07
Оценка:
Здравствуйте, PaulMinelly.

PM>То есть это у дабла манитсса сохраняется делением по полам, а не у флоата? И есть константа какая-нибудь для double чтобы сразу писать вроде:

PM>Console.WriteLine("{0}", 0.9d); // чтобы выводило 0,899999976158142

Неа, принципы представления числа для float'а и doubl'a — в общем одинаковы. Разница в том, что сам размер дабла больше (float — 4 байта, double — 8 байтов) и, соответственно, в дабле размерность в битах полей мантиссы и экспоненты больше. Однако в обоих этих типах число 0.9 не может быть представлено точно (хотя в типе Decimal оно может быть представлено точно).
Вот пример:

float f = 0.9f;
double d = 0.9d;
decimal m = 0.9m;

// вариант 1
Console.WriteLine(f);
Console.WriteLine(d);
Console.WriteLine(m);

// вариант 2
Console.WriteLine("{0:E20}", f);
Console.WriteLine("{0:E20}", d);
Console.WriteLine("{0:E40}", m);


На консоль выводится:
0.9
0.9
0.9

8.99999976000000000000E-001
9.00000000000000020000E-001
9.0000000000000000000000000000000000000000E-001


Я думаю, что такое поведение (разница между первым и вторым вариантом) можно объяснить тем, что в фунции WriteLine (вариант 1) формат отображения числа с плавающей точкой, принимаемый по умолчанию, подразумевает округление этого числа (до скольких-то значащих цифр) именно при выводе на консоль. Когда же мы во втором варианте явно указываем формат, то здесь отображаются все знаки в соответствии с форматом — видно, что ни float ни double точно десятичное число не представлют, хотя значение в double более точно, чем во float. Значение же в Decimal — в точности равно 0.9.
Re[9]: Округление? Почему так?
От: Овощ http://www.google.com
Дата: 10.07.08 09:47
Оценка:
Здравствуйте, PaulMinelly, Вы писали:

PM>>>То есть это у дабла манитсса сохраняется делением по полам, а не у флоата? И есть константа какая-нибудь для double чтобы сразу писать вроде:

PM>>>Console.WriteLine("{0}", 0.9d); // чтобы выводило 0,899999976158142
Z>>0.9 без постфикса уже double, дополнительных указаний не требуется.
Z>>потеря происходит при конвертации 0.9f в double.
Z>>0.9 != (double)0.9f

PM>Почему так происходит?

PM>Ведь
PM>0.9 == 0.9f
PM>0.9 == 0.9D
PM>0.9 != (double)0.9f

PM>Из-за того что считывается странным образом константа при конвертации? Ведь 0.9 == 0.9f и 0.9 == 0.9D, почему 0.9 != (double)0.9f?

PM>Как это увязать с кодировкой мантиссы итерационным делением на два? Ведь в дабл 0.9 == 0.9D все нормально кодируется и во флоат тоже? Почему потеря только при конвертации происходит?

Пример:
if (0.9 == 0.9f)
    Console.WriteLine("0.9 == 0.9f - true");
else
    Console.WriteLine("0.9 == 0.9f - false");

if (0.9 == 0.9d)
    Console.WriteLine("0.9 == 0.9d - true");
else
    Console.WriteLine("0.9 == 0.9d - false");

if (0.9 == (double)0.9f)
    Console.WriteLine("0.9 == (double)0.9f - true");
else
    Console.WriteLine("0.9 == (double)0.9f - false");



0.9 == 0.9f - false
0.9 == 0.9d - true
0.9 == (double)0.9f - false

Тут все понятно и логично.
При записи 0.9 (без суффикса) число считается в формате double. Разницу между 0.9f и 0.9d видно из моего предыдущего сообщения.

Еще интересно вот что:
Console.WriteLine("(double)0.9f = {0}", (double)0.9f);

выводит
(double)0.9f = 0.899999976158142

Почему нет округления (при выводе через WriteLine)? Возможно потому, что значащих цифр в double больше чем во float, и число 0.899999976158142 (полученное как приближенное представление во float числа 0.9) в формате double считается вполне себе "самостоятельным", а не приближенным значением 0.9, т.е. точности double-числа 0.899999976158142 не хватает чтобы считаться приближенным значением 0.9.

P.S. Все это лишь мои догадки и предположения. К сожалению, подтвердить ссылками на доки не могу.
Re[8]: Округление? Почему так?
От: PaulMinelly  
Дата: 10.07.08 15:12
Оценка:
О>
О>float f = 0.9f;
О>double d = 0.9d;
О>decimal m = 0.9m;

О>// вариант 1
О>Console.WriteLine(f);
О>Console.WriteLine(d);
О>Console.WriteLine(m);

О>// вариант 2
О>Console.WriteLine("{0:E20}", f);
О>Console.WriteLine("{0:E20}", d);
О>Console.WriteLine("{0:E40}", m);
О>


О>На консоль выводится:

О>
О>0.9
О>0.9
О>0.9

О>8.99999976000000000000E-001
О>9.00000000000000020000E-001
О>9.0000000000000000000000000000000000000000E-001
О>



О>Я думаю, что такое поведение (разница между первым и вторым вариантом) можно объяснить тем, что в фунции WriteLine (вариант 1) формат отображения числа с плавающей точкой, принимаемый по умолчанию, подразумевает округление этого числа (до скольких-то значащих цифр) именно при выводе на консоль. Когда же мы во втором варианте явно указываем формат, то здесь отображаются все знаки в соответствии с форматом — видно, что ни float ни double точно десятичное число не представлют, хотя значение в double более точно, чем во float. Значение же в Decimal — в точности равно 0.9.


Классный пример. Еще у кого есть 100% ответ? Выглядит как будто в double достаточно разрядов чтобы мантисса не казалась обрезанной из далека, а во float — не достаточно и мантисса даже с первого вгляда кажется плохой? Это 100% ответ?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[9]: Округление? Почему так?
От: Овощ http://www.google.com
Дата: 11.07.08 07:45
Оценка:
Здравствуйте, PaulMinelly, Вы писали:

PM>Классный пример. Еще у кого есть 100% ответ? Выглядит как будто в double достаточно разрядов чтобы мантисса не казалась обрезанной из далека, а во float — не достаточно и мантисса даже с первого вгляда кажется плохой? Это 100% ответ?



Console.WriteLine Method (Double)
The text representation of value is produced by calling the Double.ToString method.


Double.ToString Method
This version of the ToString method implicitly uses the general numeric format specifier ("G") and the NumberFormatInfo for the current culture.


Standard Numeric Format Strings
G or g, General

The number is converted to the most compact of either fixed-point or scientific notation, depending on the type of the number and whether a precision specifier is present. If the precision specifier is omitted or zero, the type of the number determines the default precision, as indicated by the following list.
* Byte or SByte: 3
* Int16 or UInt16: 5
* Int32 or UInt32: 10
* Int64 or UInt64: 19
* Single: 7
* Double: 15
* Decimal: 29

Fixed-point notation is used if the exponent that would result from expressing the number in scientific notation is greater than -5 and less than the precision specifier; otherwise, scientific notation is used. The result contains a decimal point if required and trailing zeroes are omitted. If the precision specifier is present and the number of significant digits in the result exceeds the specified precision, then the excess trailing digits are removed by rounding.

Re[10]: Округление? Почему так?
От: PaulMinelly  
Дата: 11.07.08 16:20
Оценка:
Здравствуйте, Овощ, Вы писали:

О>Здравствуйте, PaulMinelly, Вы писали:


PM>>Классный пример. Еще у кого есть 100% ответ? Выглядит как будто в double достаточно разрядов чтобы мантисса не казалась обрезанной из далека, а во float — не достаточно и мантисса даже с первого вгляда кажется плохой? Это 100% ответ?



О>

О>Console.WriteLine Method (Double)
О>The text representation of value is produced by calling the Double.ToString method.


О>

О>Double.ToString Method
О>This version of the ToString method implicitly uses the general numeric format specifier ("G") and the NumberFormatInfo for the current culture.


Открыл рефлектор,
вот как реализован в double
public override string ToString()
{
return Number.FormatDouble(this, null, NumberFormatInfo.CurrentInfo);
}
где в нем uses the general numeric format specifier ("G")? Карренирфо — вижу, G — нет.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Округление? Почему так?
От: kolja2003 Россия  
Дата: 11.07.08 16:48
Оценка:
Здравствуйте, zbanned, Вы писали:

Z>Почему так? Округление?


Z>C#:

Z>
Z>int f;
Z>f = (int)(10 * 0.9f); //returns 8
Z>f = Convert.ToInt16(10 * 0.9f); //returns 9
Z>



Прочел всю ветку. ИМХО проблема в том что никто не сказал автору ветки что мантисса всегда дробная часть числла то есть всегда меньше 1. Таким образом 0.9 в виде 9х10**(-1) нельзя представить только 0.9х10**0. Поэтому возникает конвертация 0.9 в двоичную мантиссу и следовательно погрешность.
Re[2]: Округление? Почему так?
От: PaulMinelly  
Дата: 12.07.08 02:51
Оценка:
K>Прочел всю ветку. ИМХО проблема в том что никто не сказал автору ветки что мантисса всегда дробная часть числла то есть всегда меньше 1. Таким образом 0.9 в виде 9х10**(-1) нельзя представить только 0.9х10**0. Поэтому возникает конвертация 0.9 в двоичную мантиссу и следовательно погрешность.

Почему тогда она не возникает просто при прямом выводе флоат, децимал и не возникает при конвертации из флоат в строку?
Почему?

Console.Write("{0}\n", 0.9D); // returns 0.9
Console.Write("{0}\n", 0.9f); // returns 0.9
Console.Write("{0}\n", (float)0.9D); // returns 0.9
Console.Write("{0}\n", (double)0.9f); // А вот тут возникает! returns 0.899999976158142

Чем последний случай такой особенный?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[13]: Округление? Почему так?
От: zbanned  
Дата: 13.07.08 10:58
Оценка:
N>
N>0.9*2=1.8 => 0.8*2=1.6 => 0.6*2=1.2 => 0.2*2=0.4 => 0.4*2=0.8 => 0.8*2=1.6 => 0.6 и т.д.
N>      1            1            1            0            0            1
N>


Люди, поясните, еще 4 вопроса чтобы кашу в голове убрать полностью:

1. float и double регулируются одним стандартом: IEEE 754. Каким стандартом регулируется decimal?
2. Как понятно из IEEE 754 (http://en.wikipedia.org/wiki/IEEE_754),
float мантисса представляется 23-битным числом.
у double мантисса представляется 52-битным числом.
Сколько на самом деле бит в мантиссе в C# во флоате?
3. Если считать что во floate 23 бита на мантиссу и если перевести 0.9 вручную во float методом который описал Niswn d в посте выше, то получится РОВНО:
899999976158142
Почему Visual Studio урезает результат и выводит всего лишь 9 первых знаков вместо всех 16?:
Console.Write("{0:E40}\n", (float)0.9d);
899999976000000000
Почему не выводит ..."158142" ведь 23-битов для этого достаточно или же во флоате мантисса не 23 битная?
4. Сделал программку для перевода дробных чисел во float и обратно. Конвертировал 0.8 и у меня получилось:
11001100110011001100110 что равно = 0,7999999523162840000
Почему Визуал-Студия выводит:
(float) 0.8 == как 0.8000000120
вместо
0,7999999523162840000
Я ошибся в разрядности мантиссы во флоате?
Re[3]: Округление? Почему так?
От: kolja2003 Россия  
Дата: 14.07.08 09:12
Оценка:
Здравствуйте, PaulMinelly, Вы писали:

K>>Прочел всю ветку. ИМХО проблема в том что никто не сказал автору ветки что мантисса всегда дробная часть числла то есть всегда меньше 1. Таким образом 0.9 в виде 9х10**(-1) нельзя представить только 0.9х10**0. Поэтому возникает конвертация 0.9 в двоичную мантиссу и следовательно погрешность.


PM>Почему тогда она не возникает просто при прямом выводе флоат, децимал и не возникает при конвертации из флоат в строку?

PM>Почему?

PM>Console.Write("{0}\n", 0.9D); // returns 0.9

PM>Console.Write("{0}\n", 0.9f); // returns 0.9
PM>Console.Write("{0}\n", (float)0.9D); // returns 0.9
PM>Console.Write("{0}\n", (double)0.9f); // А вот тут возникает! returns 0.899999976158142

PM>Чем последний случай такой особенный?


Последний случай особенный тем что там имеет место конвертация с удлиннением мантиссы от 4 байтового представления в 8 байтовое. Затем идет вывод значения и видимо конвертация происходит вот таким образом что результат 0.899999976158142. Это проблема конвертора Фреймворков а не ваша.
Re[14]: Округление? Почему так?
От: zbanned  
Дата: 17.07.08 15:26
Оценка:
Народ, ответьте хотя бы на первые 2 вопроса, плиз, кто знает, ну или на все.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.