c#中,在小数后面加f表示float,d表示double,m表示decimal,不加默认是double
var a = 1.0f; //float var b = 1.0d; // double var c = 1.0m; // decimal
一直没搞懂为什么需要这么多类型,下面看一下它们能表示的范围以及内部存储,其中float和double属于浮点型,decimal是MS推出的另一种类型
范围和精度
字节数 | 范围 | 小数点 | |
float | 4 | -2128 ~ +2128,也即-3.40E38 ~ +3.40E38 | 223 = 8388608,也就是7 |
double | 8 | -21024 ~ +21024,也即-1.79E308 ~ +1.79E308 | 252 = 4503599627370496也就是16 |
decimal | 16 | -(296 - 1) to 296 - 1 | 28 |
内存存储
- float(32位):
float的指数部分有8bit(2^8),由于是有符号型,所以得到对应的指数范围-128~128。由于float的指数部分对应的指数范围为-128~128,所以取值范围为:
-2128到2128,约等于-3.4E38 — +3.4E38浮点数的计算有一篇文章写的非常好,写出了浮点型的内部存储详细规则以及为什么会有精度损失
- double(64位):
double的指数部分有11bit(2^11),由于是有符号型,所以得到对应的指数范围-1024~1024。
- decimal(128位):
decimal 内部使用 4 个 32-bit 的 System.Int32 来存储,占用 128 bits = 16 bytes。这 128 bits 分配如下:
96 bits 表示从 0 至 296 - 1 的整数,分布在 3 个 32-bit 的 System.Int32 中。
剩下的 1 个 32-bit 的 System.Int32 包括符号位和比例因子。
第 31 bit 是符号位,0 表示正数,1 表示负数。
第 16 至 23 bit 表示比例因子,必须包含一个 0 至 28 之间的指数,指示 10 的幂,即小数点的位置,也就是小数点右边有几位数字。
其实表示 0 至 28 之间的指数只需 5 bits 就够了,而上面的第 16 至 23 bit 共 8 bits = 1 byte。也就是说剩下的 3 bits (第 21 至 23 bit) 一定是零。
其余 bits (0 - 15 bit 和 24 - 30 bit)不被使用,必须为零。
比较
由上面2个表可以看出来,浮点型和decimal是完全不同的存储方式
对于浮点型,
首先我们要搞清楚下面两个问题: (1) 十进制整数如何转化为二进制数 算法很简单。举个例子,11表示成二进制数: 11/2=5 余 1 5/2=2 余 1 2/2=1 余 0 1/2=0 余 1 0结束 11二进制表示为(从下往上):1011 (2) 十进制小数如何转化为二进制数 算法是乘以2直到没有了小数为止。举个例子,0.9表示成二进制数 0.9*2=1.8 取整数部分 1 0.8(1.8的小数部分)*2=1.6 取整数部分 1 0.6*2=1.2 取整数部分 1 0.2*2=0.4 取整数部分 0 0.4*2=0.8 取整数部分 0 0.8*2=1.6 取整数部分 1 0.6*2=1.2 取整数部分 0 ......... 0.9二进制表示为(从上往下): 1100100100100......
上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了”减不尽”的精度丢失问题。
而对于decimal,整数部分和小数部分都放在后面12个字节内,不存在精度丢失的问题,小数点后面位数保存在另外4个字节的16-23个bit,只要不超出28,就不会有精度损失。下面的代码由字符串转换成decimal,然后获取到了具体的小数位数。
所以假如在有效位数里面,decimal是不会有精度损失的,财务计算以及位数比较多的科学计算推荐decimal
相互转换
这边只考虑精度损失的问题,精度比较大的转换成小的有可能会造成精度损失。
float -> double, decimal不会造成精度损失
double-> float可能造成精度损失,double->decimal不会造成精度损失
decimal->float, double可能会造成精度损失
decimal和浮点型的相互转换或者加减乘除操作,必须加强制转换