从Single到Decimal的显式转换导致不同的位表示

m.e*_*son 12 c# floating-point types decimal primitive-types

如果我将单个 s转换为十进制 d,我注意到它的位表示与直接创建的小数表示不同.

例如:

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

int[] bitsSingle = Decimal.GetBits((decimal)s)
int[] bitsDecimal = Decimal.GetBits(d)
Run Code Online (Sandbox Code Playgroud)

返回(为简洁起见,删除了中间元素):

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

bitsDecimal:
[0] = 1
[3] = 131072
Run Code Online (Sandbox Code Playgroud)

这两个都是十进制数,两者(看起来)都准确地表示0.01:

在此输入图像描述

看看这个规范除了可能之外没有任何亮点:

§4.1.7与float和double数据类型相反,0.1等小数小数可以用十进制表示精确表示.

这表明该以某种方式受到不能够在转换前准确地表示0.01,因此:

  • 转换完成后为什么这不准确?
  • 为什么我们似乎有两种方法在同一数据类型中表示0.01?

Jon*_*Jon 7

TL; DR

两位小数都精确地代表0.1.只是decimal格式,允许多个按位不同的值表示完全相同的数字.

说明

这并不是说single不能精确地代表0.1.根据以下文件GetBits:

数字的二进制表示Decimal由1位符号,96位整数和用于除以整数的比例因子组成,并指定它的哪个部分是小数.缩放因子隐含地为数字10,增加到范围从0到28的指数.

返回值是32位有符号整数的四元素数组.

返回数组的第一,第二和第三个元素包含96位整数的低,中和高32位.

返回数组的第四个元素包含比例因子和符号.它由以下部分组成:

低位字0到15位未使用,必须为零.

位16到23必须包含0到28之间的指数,表示除以整数的10的幂.

第24至30位未使用,必须为零.

位31包含符号:0表示正数,1表示负数.

注意,位表示区分负零和正零.在所有操作中,这些值被视为相等.

decimal示例中每个的第四个整数是0x00030000for bitsSingle0x00020000for bitsDecimal.在二进制中,这映射到:

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

NOTE: exponent represents multiplication by negative power of 10
Run Code Online (Sandbox Code Playgroud)

因此,在第一种情况下,96位整数除以10的附加因子,而第二位16到23给出值3而不是2.但是它被96位整数本身抵消,在第一种情况下也比第二种情况大10倍(显而易见的是第一种元素的值).

因此,观察值的差异可以简单地归因于这样的事实:single与"直"构造函数相比,转换使用微妙不同的逻辑来导出内部表示.