Преобразование десятичного числа в двоичное число с плавающей точкой

Модератор: Модераторы разделов

BratSinot
Сообщения: 812
ОС: Slackware64

Преобразование десятичного числа в двоичное число с плавающей точкой

Сообщение BratSinot »

Доброго времени суток!

Вообщем я испытываю некоторые трудности при переводе числа с точкой в двоичный формат. Есть функция с двумя аргументами целого типа, скажем a и b. Аргумент a принимает значение целой части числа, а аргумент b дробной. А теперь в чем трудность, перевести дробную часть b в двоичный формат. На бумаге то оно понятно как будет (пример для числа 0,15):

Код: Выделить всё

0.15 = 0*2^-1 + 0*2^-2 + 1*2^-3 + 0*2^-4 + 0*2^-5 + 1*2^-6 (и т.д.)

Либо так:

Код: Выделить всё

0.15*2 = (0).3
0.3*2 = (0).6
0.6*2 = (1).12
0.12*2 = (0).24
0.24*2 = (0).48
0.48*2 = (0).96
0.96*2 = (1).92
и т.д.

А как это воплотить в программу что-то не очень доходит. Может кто идейку или статейку подкинет?
P.S. Сразу тогда скажу зачем оно нужно. Нужно оно для преобразования числа в формат IEEE 754.
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Преобразование десятичного числа в двоичное число с плавающей точкой

Сообщение drBatty »

BratSinot писал(а):
15.06.2012 22:55
А теперь в чем трудность, перевести дробную часть b в двоичный формат. На бумаге то оно понятно как будет (пример для числа 0,15):

в точности так же, как и целое. Только потом надо поделить на M. ну например надо перевести 0.56, это 56/100, умножаем 56 на 256, и делим на 100, получаем 143, или 10001111, теперь делим на 256, получаем 0.10001111

ну конечно погрешность, ибо .55859375 == .10001111 != 0.56

и да, быстрее вместо деления сразу умножить на 256*256/100 == 655.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
BratSinot
Сообщения: 812
ОС: Slackware64

Re: Преобразование десятичного числа в двоичное число с плавающей точкой

Сообщение BratSinot »

drBatty писал(а):
15.06.2012 23:20
BratSinot писал(а):
15.06.2012 22:55
А теперь в чем трудность, перевести дробную часть b в двоичный формат. На бумаге то оно понятно как будет (пример для числа 0,15):

в точности так же, как и целое. Только потом надо поделить на M. ну например надо перевести 0.56, это 56/100, умножаем 56 на 256, и делим на 100, получаем 143, или 10001111, теперь делим на 256, получаем 0.10001111

ну конечно погрешность, ибо .55859375 == .10001111 != 0.56

и да, быстрее вместо деления сразу умножить на 256*256/100 == 655.

Огромное вам Спасибо!!
Спасибо сказали:
BratSinot
Сообщения: 812
ОС: Slackware64

Re: Преобразование десятичного числа в двоичное число с плавающей точкой

Сообщение BratSinot »

Хм... Осталось только придумать как сделать это максимально точно. Потому-что как я делаю в лоб, то например число 2.4 я получаю как 2.3999998569488525390625, когда его можно представить как 2.400000095367431640625.

Хотя не, такой способ влоб не прокатит. На single-precision еще можно, т.к. там мантиса небольшая, а на double-precision мантиса уже больше, не говоря о quad-precision. Вообщем придется идти обходными путями...
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Преобразование десятичного числа в двоичное число с плавающей точкой

Сообщение drBatty »

BratSinot писал(а):
16.06.2012 18:08
Хм... Осталось только придумать как сделать это максимально точно.

эх... Вы в школе не учились? Округляйте. В двоичной системе правила примитивные: если старший из отбрасываемых битов == 0, ничего не делаете. Иначе +1. Конечно это не 1, а 1/M, но это будет уже потом, когда поделите. Важно понимать, что это НЕ улучшает точность, это просто смещает математическое ожидание ошибки. Если просто отбрасывать лишнее, тогда вы _всегда_ будете уходить в минус, что плохо - например складывая 100500 раз числа, вы получите результат намного меньше истинного, если числа случайные, то математическое ожидание ошибки будет в 100500 больше, чем величина погрешности на одном шаге. Т.е. ошибки накапливаются.
BratSinot писал(а):
16.06.2012 18:08
Хм... Осталось только придумать как сделать это максимально точно. Потому-что как я делаю в лоб, то например число 2.4 я получаю как 2.3999998569488525390625, когда его можно представить как 2.400000095367431640625.

в данном случае есть смысл _ухудшить_ точность на 1 бит. Если вам доступен вычислитель на 31 бит (32 минус 1 бит для знака), то сделайте точность 30 бит, а 31й используйте для округления.
BratSinot писал(а):
16.06.2012 18:08
Хотя не, такой способ влоб не прокатит. На single-precision еще можно, т.к. там мантиса небольшая, а на double-precision мантиса уже больше, не говоря о quad-precision. Вообщем придется идти обходными путями...

нет никакой разницы, какая у вас мантисса. Разница в том, сколько вам нужно _десятичных_ цифр точности. Например если вам нужно 6 цифр, то вам понадобится множитель в 21 бит, а именно 2097152. Причём не важно, 12345.6 это или 1.23456. Для double вам очевидно потребуется множитель 18014398509481984 (2^54), и очевидно, что технически проще взять множитель 2^63 (в 64х битных системах этот тип нативный, а в 32х битных легко реализуется через 4 умножения 32х битных половинок). Это обеспечивает требуемую вами точность в 15 десятичных цифр. Больше в ваш double просто не влезет (точнее, никак не повлияют на конечный результат).

И да, никогда не сравнивайте на точное равенство дробные числа! Они неравны ВСЕГДА. Единственное исключение - если вы используете double для хранения целых. Да и то... Впрочем Intel обещает, что до 64х бит их FPU считают точно (только сложение и умножение естественно). Потому результат 2.3999998569488525390625 ничем не хуже 2.400000095367431640625. Ну за исключением накопления погрешности.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
BratSinot
Сообщения: 812
ОС: Slackware64

Re: Преобразование десятичного числа в двоичное число с плавающей точкой

Сообщение BratSinot »

drBatty писал(а):
18.06.2012 02:39
BratSinot писал(а):
16.06.2012 18:08
Хотя не, такой способ влоб не прокатит. На single-precision еще можно, т.к. там мантиса небольшая, а на double-precision мантиса уже больше, не говоря о quad-precision. Вообщем придется идти обходными путями...

нет никакой разницы, какая у вас мантисса. Разница в том, сколько вам нужно _десятичных_ цифр точности. Например если вам нужно 6 цифр, то вам понадобится множитель в 21 бит, а именно 2097152. Причём не важно, 12345.6 это или 1.23456. Для double вам очевидно потребуется множитель 18014398509481984 (2^54), и очевидно, что технически проще взять множитель 2^63 (в 64х битных системах этот тип нативный, а в 32х битных легко реализуется через 4 умножения 32х битных половинок). Это обеспечивает требуемую вами точность в 15 десятичных цифр. Больше в ваш double просто не влезет (точнее, никак не повлияют на конечный результат).

Ну я просто делаю так:

Код: Выделить всё

b=(b<<52)/pow10(num(b)); // b -- дробная часть, num() -- количество цифр в числе

и, понятное, дело оно легко за пределы выходит.
А, ну вообще можно делить не на 10^x, а на 5^x, умножать не на 2^52, а на 2^(52-x), что увеличит размеры числа с которым можно работать, хотя не совсем достаточно.

drBatty писал(а):
18.06.2012 02:39
И да, никогда не сравнивайте на точное равенство дробные числа! Они неравны ВСЕГДА. Единственное исключение - если вы используете double для хранения целых. Да и то... Впрочем Intel обещает, что до 64х бит их FPU считают точно (только сложение и умножение естественно). Потому результат 2.3999998569488525390625 ничем не хуже 2.400000095367431640625. Ну за исключением накопления погрешности.

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

Черт, OpenCL не поддерживает битовые поля....

А вообще, у меня такое ощущение, что моя кривая функция числа определенного диапаона конвертирует лучше, чем gcc :laugh:
Спасибо сказали: