Явное преобразование из одного в десятичный приводит к разному представлению бит

Если я преобразую одиночный s в десятичный d, я заметил, что его битовое представление отличается от представления десятичной дроби, созданного напрямую.

Например:

Single s = 0.01f;
Decimal d = 0.01m;

int[] bitsSingle = Decimal.GetBits((decimal)s)
int[] bitsDecimal = Decimal.GetBits(d)

Возвращает (средние элементы удалены для краткости):

bitsSingle:
[0] = 10
[3] = 196608

bitsDecimal:
[0] = 1
[3] = 131072

Оба эти числа являются десятичными числами, которые оба (кажущиеся) точно отображают 0,01:

enter image description here

Глядя на спецификацию, нет света, кроме, возможно:

§4.1.7. В отличие от типа данных с плавающей точкой и двойными данными десятичная дробная числа, такие как 0,1, могут быть представлены точно в десятичной представление.

Предполагая, что это как-то повлияло на одиночный, неспособный точно представлять 0.01 перед преобразованием, поэтому:

  • Почему это неточно к моменту завершения преобразования?
  • Почему у нас есть два способа представления 0.01 в одном и том же типе данных?

Ответ 1

TL; DR

Оба десятичных знака точно представляют 0,1. Просто формат decimal, точно такой же, как плавающая точка IEEE, допускает несколько поразрядных значений, которые представляют точно такое же число.

Объяснение

Это не значит, что single не может точно представлять 0,1. Согласно документации GetBits:

Двоичное представление числа decimal состоит из 1-битного знака, 96-битного целочисленного числа и коэффициента масштабирования, используемого для разделения целочисленное число и укажите, какая часть его является десятичной дробью. Масштабный коэффициент неявно равен числу 10, поднятому до показателя от 0 до 28.

Возвращаемое значение представляет собой четырехэлементный массив из 32-разрядных целых чисел.

Первый, второй и третий элементы возвращаемого массива содержат низкий, средний и высокий 32 бит 96-битного целочисленного числа.

Четвертый элемент возвращаемого массива содержит масштабный коэффициент и знак. Он состоит из следующих частей:

Биты от 0 до 15, нижнее слово, не используются и должны быть равны нулю.

Биты с 16 по 23 должны содержать показатель между 0 и 28, который указывает мощность 10 для деления целочисленного числа.

Биты с 24 по 30 не используются и должны быть равны нулю.

Бит 31 содержит знак: 0 означает положительный, а 1 означает отрицательный.

Обратите внимание, что представление битов различает отрицательные и положительный ноль. Эти значения считаются равными во всех операции.

Четвертое целое число decimal в вашем примере - 0x00030000 для bitsSingle и 0x00020000 для bitsDecimal. В двоичном виде это сопоставляется с:

bitsSingle     00000000 00000011 00000000 00000000
               |\-----/ \------/ \---------------/
               |   |       |             |
        sign <-+ unused exponent       unused
               |   |       |             |
               |/-----\ /------\ /---------------\
bitsDecimal    00000000 00000010 00000000 00000000

NOTE: exponent represents multiplication by negative power of 10

Следовательно, в первом случае 96-битное целое делится на дополнительный коэффициент 10 по сравнению со вторыми битами 16-23, выдает значение 3 вместо 2. Но это компенсируется 96-битным целым числом сама по себе, которая в первом случае также в 10 раз больше, чем во втором (очевидно из значений первых элементов).

Таким образом, разницу в наблюдаемых значениях можно объяснить просто тем фактом, что преобразование из single использует тонкую логику для получения внутреннего представления по сравнению с "прямым" конструктором.