PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

前置说明

  本文作为本人csdn blog的主站的备份。(BlogID=088)

  本文发布于 2019-09-02 17:19:42,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=088)

环境说明

  ubuntu 18.04

  Linux 4.15.0-54-generic #58-Ubuntu SMP Mon Jun 24 10:55:24 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

起因


  有些时候,在测试深度学习的模型的时候,特别是模型出问题,或者其他乱七八糟的原因,你会发现某些层的某些特征会出现INF和NAN(特别是转换模型)。说实话,这两个一个是无穷大,一个是不是数字,我们都知道这个的意思,但是怎么出现的,可能都忘了。

  我如果没有记错的话,数在计算机中的表示是来至于《计算机组成原理》,其中介绍了很多很多的有趣的原理。但是我出校后,这些理论知识很少和实际联系起来,趁着这次机会,我准备结合我们平常写的代码,理一理计算机中数的这个问题。

符号


  在计算机里面,除了指令,就是数值或者符号(统一的说就是数值,因为数值和符号有映射关系,这就是符号编码),我也不知道这样说对不对。

  本文主要是说的是数值。对于符号来说,符号涉及到编码问题,有兴趣的可以去了解了解,我们常见的编码就是ascii,utf-8, unicode,gbk,big5等等,这些编码涉及到符号显示的问题。

数值


  数值大致有两类表示法,一类是定点数,一类是浮点数。

  计算机中,对于整数来说,就是定点数表示法(这里对于整数的说法我也不太确定对不对,相关资料也没有查到明确的说法,但是我理解的是整数就是定点数表示法中,小数点在最右边),对于实数来说,就是浮点数表示法。

整数

  在计算机中,在说明整数的定点数表示之前,还得说几个书上的理论:

  • 原码:原码就是十进制转换为二进制。
  • 反码:原码取反。
  • 补码:反码+1。(注意:如果是符号数,反码和补码运算不影响符号位。)

  在计算机中,正整数的补码反码原码一样,负整数的补码是原码取反加1。

  对于整数来说,可以分为有符号还是无符号的整数。

  无符号数,是正整数,所以在计算机中是补码表示,且就是其十进制转换为二进制。

  有符号整数,如果是正整数,计算机中补码表示,且就是其十进制转换为二进制(补码原码一样),如果是负整数,在计算机中表示,且就是其十进制转换为二进制,取补码。

实数

  在计算机中,在说明浮点数表示法之前,还得说两个理论:

移码:N-M转换为二进制,M为偏移数。

  二进制浮点数:整数部分直接转换为二进制(除2逆序取余),小数部分逼近求和(乘2正序取整)。(更详细的百度随便找个教程即可,我这里只是简单写一下)

  规范化二进制浮点数:小数点前只有一个1。

  IEEE 754:IEEE根据一些历史因素,定制的大部分通用的浮点数表示方法。以单精度浮点数为例,31位表示符号位,23-30位表示exponent(偏移数是M,指数为E.),0-22表示base(底数为B),表示的浮点数为:B*(2^(E-M))

  IEEE 754有很多特殊值,也有一些溢出规则和约等于规则,一般来说,除非你要做科学运算,平常你是遇不到的,简单了解一下即可。

  IEEE 754规定的特殊值:

  注意:其实浮点数还有其他的一些异常计算及表示,详细的请查看ieee 754 chapter7

实例分析


  上面扯了半天,大家都看烦了,其实都是一些书本上的知识整理。

  下面是实例源文件。

#include <cstring>
int main(int argc , char * argv[]){ //integer
char A = 0xF1;//A = -15; size(A) = 1;mem=[1]111 0001(complement); mem=[1]000 1111(true form)
short B = 0xF111;//B= -3823; size(B) = 2; mem = [1]111 0001 / 0001 0001 (complement); mem=[1]000 1110 / 1110 1111(true form)
int C = 0xF1111111;//C = -250539759; size(C) = 4; mem = [1]111 0001 / 0001 0001 / 0001 0001 / 0001 0001 (complement); mem=[1]000 1110 / 1110 1110 / 1110 1110 / 1110 1111 (true form)
long D = 0xF1111111;//D = -250539759(size(D)=4), 4044427537(size(D)=8); size(D) = 4; mem = [1]111 0001 / 0001 0001 / 0001 0001 / 0001 0001 (complement); mem=[1]000 1110 / 1110 1110 / 1110 1110 / 1110 1111 (true form)(Sizeof(D) may be 4 or 8, it decided by compiler)
long long E = 0xF111111111111111;//E = -1076060070966390511; size(E) = 8; mem = [1]111 0001 / 0001 0001 / 0001 0001 / 0001 0001 / 0001 0001 / 0001 0001 / 0001 0001 / 0001 0001 (complement); mem=[1]000 1110 / 1110 1110 / 1110 1110 / 1110 1110 / 1110 1110 / 1110 1110 / 1110 1110 / 1110 1111 (true form) //overflow int
int C1 = 0xF1111111FF;//drop highest byte(0xF1), it decided by compiler //overflow long long
long long E1 = 0xF111111111111111FF;//drop highest byte(0xF1), it decided by compiler //float, IEEE 754.
/*
single-precision float
bits: [31] is signed bit, (30~23) is exponent, {22~0} is base double-precision float
bits: [63] is signed bit, (62~52) is exponent, {51~0} is base
*/
float F = -10;//size(F)=4, mem=[1](100 0001 / 0){010 0000 / 0000 0000 / 0000 0000}, [] is signed bit. () is exponent, {} is normalized base.(single-precision)
double G = 10;//size(G)=8, mem=[0](100 0000 / 0010) {0100 / 0000 0000 / 0000 0000 / 0000 0000 / 0000 0000 / 0000 0000 / 0000 0000}, [] is signed bit. () is exponent, {} is normalized base.(double-precision) int tmp_buf = 0x00800001;
float F_normalized_min = 0;
memcpy(&F_normalized_min, &tmp_buf, sizeof(F_normalized_min));//size(F)=4
float F_normalized_zero = F_normalized_min - 1; //-1 float F_normalized_max = 0;
tmp_buf = 0x7F7FFFFF;
memcpy(&F_normalized_max, &tmp_buf, sizeof(F_normalized_max));//size(F)=4
float F_normalized_infinity = F_normalized_max * F_normalized_max; //inf float F_normalized_nan = F_normalized_infinity / F_normalized_infinity;//nan //some fun value
float F_fun_01 = 0.1;
float F_fun_02 = 0.2;
float F_fun_03 = 0.3;
float F_fun_04 = 0.4;
float F_fun_05 = 0.5;
float F_fun_06 = 0.6;
float F_fun_07 = 0.7;
float F_fun_08 = 0.8;
float F_fun_09 = 0.9; float F_denormalized_min = 0;
tmp_buf = 0x00000001;
memcpy(&F_denormalized_min, &tmp_buf, sizeof(F_denormalized_min));//size(F)=4
float F_denormalized_max = 0;
tmp_buf = 0x007FFFFF;
memcpy(&F_denormalized_max, &tmp_buf, sizeof(F_denormalized_max));//size(F)=4 return 0;
}
整数分析

  有符号负整数:

(注意,x86,小端)

  无符号整数、有符号正整数,就是直接10进制转换为二进制。

浮点数分析

  规范化浮点数:(数值:规范化浮点数最小正数)

(数值:规范化浮点数最大正数)

(数值:非规范化浮点数最小正数)

(数值:非规范化浮点数最大正数)

浮点数异常计算:

(数值:NAN)

(数值:INF)

一些有趣的浮点数


  看了上边后,计算机关于浮点数的的存储其实是很离散的(很不靠谱),也就是说,很多浮点数计算机根本表示不出来(计算机只能够存储,实数数轴上极少部分的数),为什么呢?如果你要是了解了上面关于10进制浮点数转2进制浮点数,那么你可能已经猜到了原因。

  下面我以0.1位例,分析一下,为啥会出现这样的问题。

  0.1在计算机中表示为:

mem=[0](011 1101/ 1){100 1100/ 1100 1100/ 1100 1100}, [] is signed bit. () is exponent, {} is normalized base.(single-precision)

E=123
M=127
B=1.100 1100/ 1100 1100/ 1100 1100 F_fun_01 = B*2(^-4) = 0.0001100 1100/ 1100 1100/ 1100 1100 = 2^(-4) + 2^(-5) + 2^(-8) + 2^(-9) + 2^(-12) + 2^(-13) + 2^(-16) + 2^(-17) + 2^(-20) + 2^(-21) + 2^(-24) + 2^(-25)

  正是因为在内存中表示的是这样的,所以这里打印出来的值看到不是0.1,而是0.1+。那么大家可能会疑惑,如果0.1都有误差,那计算的时候,不是炸了吗?其实不然,还记得c语言中一句话吗?float的精度为小数点后6位,为啥是6位,而不是10位,8位呢?其实原因就是来至于这里,计算机中,某些小数位后虽然还有值,但是不是有效的,但是这些值影响数值的舍进(类似与四舍五入的约等于,建议大致了解,知道有这个事情即可)。

后记


  总结

  计算机里面,数的表示,就浮点数最难,但是只需要了解了大致的原理,你就会觉得非常简单。

  其实对于计算机来说,数值的表示很弱的,离表示整个数轴差的远。

  在计算机里面,数值有很多边界条件,比如溢出、异常运算、异常值,只是我们平常很少遇到,所以遗忘了。

  同时也可以说明,其实计算机仅仅是个机器,只会冰冷的加载指令和数,并执行,只是我们的前辈们为我们做了很多事情,隔离了很多细节和底层,让我们觉得这些东西可有可无,极大的便利人们使用计算机。这样有好处也有坏处。

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

数与计算机 (编码、原码、反码、补码、移码、IEEE 754、定点数、浮点数)的更多相关文章

  1. python之计算机硬件基本认知_数据单位_进制间转换_数的原码反码补码

    一:计算机硬件基本认知 cpu:   中央处理器.   相当于人的大脑.运算中心,控制中心. 内存:  临时存储数据. 优点:读取速度快,缺点:容量小,造价高,断电即消失. 硬盘:  长期存储数据. ...

  2. 原码 & 反码 & 补码 & 详解

    本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希 ...

  3. C语言原码反码补码与位运算.

      目录:     一.机器数和真值     二.原码,反码和补码的基础概念     三.为什么要使用原码,反码和补码     四.原码,补码,反码再深入     五.数据溢出测试     六.位运算 ...

  4. Java 原码 反码 补码

    本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希 ...

  5. 「C语言」原码反码补码与位运算

    尽管能查到各种文献,亲自归纳出自己的体系还是更能加深对该知识的理解.     本篇文章便是在结合百度百科有关原码.反码.补码和位运算的介绍并深度借鉴了张子秋和Liquor相关文章后整理而出.   目录 ...

  6. 位移&二进制转换&原码&反码&补码

    << 左移 按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零. 格式 需要移位的数字 << 移位的次数 计算过程 1. 按二进制形式把所有的数字向左 ...

  7. C语言基础(4)-原码,反码,补码及sizeof关键字

    1. 原码 +7的原码是0000 0111 -7的原码是1000 0111 +0的原码是0000 0000 -0的原码是1000 0000 2. 反码 一个数如果值为正,那么反码和原码相同. 一个数如 ...

  8. JAVA:二进制(原码 反码 补码),位运算,移位运算,约瑟夫问题(5)

    一.二进制,位运算,移位运算 1.二进制 对于原码, 反码, 补码而言, 需要注意以下几点: (1).Java中没有无符号数, 换言之, Java中的数都是有符号的; (2).二进制的最高位是符号位, ...

  9. Java学习第五篇:二进制(原码 反码 补码),位运算,移位运算,约瑟夫问题

    一.二进制,位运算,移位运算 1.二进制 对于原码, 反码, 补码而言, 需要注意以下几点: (1).Java中没有无符号数, 换言之, Java中的数都是有符号的; (2).二进制的最高位是符号位, ...

  10. java原码反码补码以及位运算

    原码, 反码, 补码的基础概念和计算方法. 对于一个数, 计算机要使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的编码方式. 1. 原码 原码就是符号位加上真值的绝对值, 即 ...

随机推荐

  1. yapi 启动后,老是自动关闭的问题。

    解决方法只需要2步: 1.在命令后面加 & 符号,放到后台执行,最终的命令为: node /usr/local/yapi/yapi/vendors/server/app.js & 2. ...

  2. Visual Studio部署matplotlib绘图库的C++版本

      本文介绍在Visual Studio软件中配置.编译C++环境下matplotlibcpp库的详细方法.   matplotlibcpp库是一个C++环境下的绘图工具,其通过调用Python接口, ...

  3. 【Unity3D】地面网格特效

    1 前言 ​ 本文实现了地面网格特效,包含以下两种模式: 实时模式:网格线宽度和间距随相机的高度实时变化: 分段模式:将相机高度分段,网格线宽度和间距在每段中对应一个值. ​ 本文完整资源见→Unit ...

  4. col命令

    col命令 在很多UNIX说明文件里,都有RLF控制字符,当我们把说明文件的内容输出成纯文本文件时,控制字符会变成乱码,col命令则能有效滤除这些控制字符. 语法 col [options] 参数 - ...

  5. Java设计模式-单例模式Singleton

    介绍 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法). 比如 Hibernate 的 SessionF ...

  6. postgresql常见开发技巧

    1.数据类型 名字 描述 bigint 有符号 8 字节整数 bigserial 自增八字节整数 bit [ (n) ] 定长位串 bit varying [ (n) ] 变长位串 boolean 逻 ...

  7. Innodb存储引擎之锁

    目录 一.概述 二.lock 与 latch 三.Innodb存储引擎中的锁 锁 一致性非锁定读 一致性锁定读 自增长与锁 外键与锁 四.锁的算法 锁的算法 Phantom Problem 幻读问题 ...

  8. Taurus.MVC WebMVC 入门开发教程3:数据绑定Model

    前言: 在这篇 Taurus.MVC WebMVC 入门开发教程的第三篇文章中, 我们将重点介绍如何进行数据绑定操作,还会学习如何使用 ${属性名称} CMS 语法来绑定页面上的元素与 Model 中 ...

  9. 【LeetCode回溯算法#02】组合总和III

    组合总和III 力扣题目链接(opens new window) 找出所有相加之和为 n 的 k 个数的组合.组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字. 说明: 所有数字 ...

  10. 在Study.BlazorOne项目中引入Study.Trade模块的实体的表结构

    # 1.修改EntityFrameworkCore项目下的BlazorOneDbContext文件,增加一行代码即可 增加Study.Trade.EntityFrameworkCore中的这个方法: ...