| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Общие вопросы > Представление дробного числа в памяти |
| Автор: oper54 25.12.2006, 11:57 |
| Доборое время суток... Вот интересно стало как вы глядят числа на низком уровне... 1. Как выглядит отрицательное число(по битам) 2. Как выглядит число с плавающей запятой(по битам) |
| Автор: jonie 25.12.2006, 13:46 |
| почитай стандарт представления чисел с плавающей точкой....скажем в MSDN все написано даже... да и поиск тоже даст результаты. на вскидку могу вспомнить: (проверить стоит) signed integer - старший бит - знаковый(1=="-",0=="+") все остальное - число. в беззнаковом этот бит отдается под число. float - 8 бит экспонента, 23 - мантисса, старший бит опять же знак double - 11 бит экспонента, 52 - мантисса, старший бит знаковый. ........IEEE читай ....... ну или там хотяб (особо обращаем внимание на хранение экпоненты)...: http://www.intuit.ru/department/se/pbmsu/2/2.html |
| Автор: takedo 25.12.2006, 13:51 |
| В конце концов посмотри по битам сам через маску: Приведу пример для самого простого типа, а дальше по аналогии: char data; bool bits[8]; bits[0] = data&0x80; bits[1] = data&0x40; ... Ну и так далее. Единственное, что может быть наоборот старший и младший биты, но для твоей задачи это неважно. |
| Автор: W4FhLF 25.12.2006, 14:23 | ||||||||||||||||||
Что-то в последнее время стала популярна эта тема, отпарвлять читать маны интела смысла нет, поэтому я сейчас постараюсь довольно доступно разъяснить эту тему.
Отрицательное целое или с плавающей точкой? Целые числа делятся на отрицательные и положительные условно, в памяти никакого деления на положительные и отрицательные нет, допустим int(в 32х разрядной системе) допускает значения от -2147483648(0x80000000) до 2147483647(0x7FFFFFFF) или от -2^31 до 2^31 - 1. Отсюда следует, что например -1 запишется в памяти как 0xFFFFFFFF, -2 как 0xFFFFFFFE. Давай убедимся в этом на практике и продизассемблируем такую программу:
Прежде всего нас интересуют первые три операции присвоения, компилятор их скомпилирует в машинный код, а переведя этот машинный код на язык ассемблера(продизассемблировав его) мы увидим такие эквивалентые, с точки зрения процессора, команды:
Операция MOV DWORD PTR DS:[402008],FFFFFFFE на языке ассемблера дословно значит: присвоить ячейке памяти по смещению 0x402008 значение размером 4 байта(DWORD) равное 0xFFFFFFFE. Каждую строку я закоментировал. В первой операции присвоения используется OR только потому, что она занимает меньше места, чем MOV, но она эквивалентна ей в данном случае. Отсюда можно вывести простую закономерность, фактическое представление любого отрицательного целого числа в памяти можно получить по формуле: RealValue = 0xFFFFFFFF - b + 1 Где b - это абсолютное значение отрицательного числа. Для удобства в процессоры была включена специальная команда для этого - NEG. Выдержка из книги Юрова:
Команда эта так же используется компилятором при иневертировании положительного знака целых чисел:
Такой вот код компилятор преобразует в следующее машинное выражение:
Как видишь, сначала происходит присвоение положительного числа, а после изменение знака специальной командой процессора.
Тут всё сложнее. Дело в том, что работой с flat point числами занимается арифметический сопроцессор - FPU. Он имеет свои команды, свои регистры(при этом основными являются регистры R0-R7, состовляющие основу модели сопроцессора - стек сопроцессора. Размерность каждого из этих регистров 80 бит, обрати внимание на это). Набор функций и форматы данных с которыми работает сопроцессор описывает стандарт IEEE 754 (http://babbage.cs.qc.edu/IEEE-754/References.xhtml). При этом формат данных с плавающей запятой делится на 3 группы: Короткий Длинный Расширенный Отличаются они между собой прежде всего разрядностью, первый занимает в памяти 4 байта, второй 8 байт, а третий 10. В общем случае вещественное число или число с плавающей точкой имеет следующую структуру в памяти: [знак][экспонента][мантисса] От размера экспоненты и мантиссы как раз зависит формат данных. В С/С++ мы привыкли использовать два формата: float и double. Первый является коротким форматом представления вещественного числа(под экспоненту отводится 8 бит, под мантиссу 24), второй длинным(под экспоненты отводится 11 байт, под мантиссу 53). Под знак во всех форматах отводится 1 бит. Теперь переходим к практике, я буду разбирать всё на примере float, с остальными форматами всё естественно аналогично только с учётом размера составных частей. Скомпилируем такой пример и посмотрим как с ним работает процессор:
Смотрим во что превращается команда присвоения:
Ага, ну вот, то о чём я и говорил. Сопроцессор использует свой набор команд, поэтому непосвящённым они кажутся совсем непривычными. При этом в них нет ничего необычного. Первая команда загружет нв верхушку стека сопроцессора значение из памяти, а вторая кладёт значение с вершины по адресу 0x402000(очевидно, что это смещение в памяти нашей переменной a). При этом если мы посмотрим по адресу 0x403094 мы не увидим ничего даже отдалённо напоминающего -7.770000, вместо этого мы видим следующее значение по этому адресу: 0xC0F8A3D7. Вот это и есть представление числа -7.770000 в памяти, таким его видит процессор. Основываясь на вышесказанном, конвертируем это число в последовательность битов и получаем: 11000000111110001010001111010111 Раскладывая его на составляющие получаем: 1 - знак. (0 - число положительное; 1 - отрицательное) 10000001 - Экспонента. Показывает сколько порядков надо взять у мантиссы, чтобы получить целую часть числа. 11110001010001111010111 - Мантисса. Начнём постепенно строить модель нашего числа. Нам известно, что число отрицательное, теперь нужно перевести экспоненту в нормальный вид. Берём её значение, оно равно 129 и чтобы получить кол-во битов целой части отнимаем от этого числа 127, получаем 2(на самом деле устройство экспоненты это довольно обширная тема, я в подробности вдаваться не стал). Берём у мантиссы 2 бита в счёт целой части: 11.110001010001111010111 Но 11b это не 7, скажите вы, и будете правы. На самом деле перед этим я должен был сказать, что при преобразовании в число с плавающей точкой в мантиссу уходит всё до последнего бита, равного 1. Этот бит всегда есть и поэтому его нет смысла хранить в памяти, он добавляется в "уме". Вы должны были обратить на это внимание, когда я описывал формат и сказал, что размер мантиссы у float - 24 бита, а когда мы разделили на составляющие наше число получили 23 бита, перед этими 23 битами стоит ещё один. Учитывая это получаем: 111.110001010001111010111 Итак, целая часть 111b, а это как раз 7 в десятичной системе. Осталось привести к нормальному виду дробную часть: 110001010001111010111, но и тут наши приключения не заканчиваются DrobValue = Drob(1) * 2^(-1) + Drob(2) * 2^(-2) + Drob(3) * 2^(-3) + Drob(4) * 2^(-4) ... Drob(n) * 2^(-n) Где n кол-во бит в оставшейся мантиссе. Или, если применить к нашему случаю, получаем: DrobValue = (1 * 2^(-1)) + (1 * 2^(-2)) + (0 * 2^(-3)) + (0 * 2^(-4)) ... 1 * 2^(-21) Если проссумировать всё, как написано выше, получим: 0,769999980926514 И вот тут открывается ещё одна тайна Связывая в одно всё вышесказанное, значение 0xC0F8A3D7 превращается в отрицательное число с плавающей точкой, где целая часть равна 7, а дробная 769999980926514: -7.769999980926514 Ну вот, надо же... Целая "статья" получилась. На полноту описание конечно же не претендует, я и половины особенностей форматов FPU не рассмотрел, однако постарался изложить всё просто и доступно, подврепив примерами. Учите господа ассемблер и тогда для Вас всё будет прозрачным и понятным |
| Автор: oper54 16.1.2007, 21:19 |
| спасибо, исчерпывающий ответ... предлагаю, запостить эту статью в FAQ |
| Автор: GIK 17.1.2007, 16:40 | ||
Помница на уроках информатики это рассуждали так: Все нули становятся единицами, все единицы становятся нулями и прибавляется 1 (как число) Например число 1 выглядит так (в одном байте) 00000001 Число -1 будет таким 11111111 т.е. изменение 1 на 0 и наоборот + один Так вроде легче понять начинающему Поправте если не прав, а то я в это дело давно не влазил, хотя нужно знать хорошо |
| Автор: SerpentVV 17.1.2007, 20:47 | ||||
Да, это называется "дополнительный код". От количества разрядов не зависит. Например, для 8 разрядов (битов) на примере +1 +1 = 00000001 инвертируем 11111110 прибавляем 1 в младший разряд 11111111 = -1 Проверяем +1 -1 = 0 1|1111111 единички переноса |00000001 |11111111 ---------------- 00000000 = 0 два числа не переводятся: 0 и самое маленькое отрицательное число... для 8 битов это 10000000 = -128 Числа +128 нет, есть +127 = 01111111 = +127 |