【一天一个C++小知识】006. 浮点数在计算机内部的表示与转换

  编写程序时,两个浮点数不能直接比较大小(无论单精度float双精度double),这是因为浮点数在计算机内部不能精确的表示。

  首先明确定点数和浮点数:定点数就是小数点位置固定不动的数,在计算机中,我们假设小数点在最前面,例如,001表示二进制小数0.001;10表示二进制小数0.10。这样任何小于1且不小于0的小数都可以用定点数来表示。浮点数就是小数点位置不固定的数,这样就需要一定的方法来表示浮点数。

  再看一下十进制小数和二进制小数的互相转换:

  • 十进制小数转二进制小数:小数部分乘以2,如果如果大于1,则记该位为1,并将乘积减去1,如果小于1,则记该位为0,乘积继续乘以2。重复下去直到乘积正好为1,最后一位取1,从前到后写出记下的位的值。比如十进制的0.8125转换为二进制,首先0.8125x2=1.6250,记该位为1,乘积减去1,;继续乘2,0.625x2=1.250,记该位为1,乘积减1,得0.250;继续乘2,乘积为0.500,小于1,记该位为0;继续乘2,乘积为1.000,等于1,记该位为1,最终结果0.1101
  • 二进制小数0.1101转化为十进制,1×2−1+1×2−2+0×2−31\times 2^{-1}+1\times 2^{-2}+0\times2^{-3}1×21+1×22+0×23+1×2−4=0.8125+1\times2^{-4}=0.8125+1×24=0.8125

  在计算机内部存储时,浮点数主要分为三部分来存储:符号位、阶码部分(也有叫指数)和尾数部分【32位单精度float符号位占1bit,阶码占8bit,尾数部分占23bit;64位双精度double符号位占1bit,阶码占11bit,尾数部分占52bit】。
在这里插入图片描述
  其中阶码存储的是实际指数值的移码,尾数部分存储的是规约化后的尾数的补码。移码就是将实际的阶码值加上一个固定的数值而得到的数,在float型数据中规定该固定值/偏置量为127,阶码有正负所以8为二进制表示范围-128—127,double中规定该固定值/偏置量为1023,表示范围-1024—1023。

  令nnn表示阶码部分的长度,这个数就是e=2n−1−1e=2^{n-1}-1e=2n11。比如若真实的阶码为2,float类型数据则加上127后为129,阶码形式为1000 0010。这样做的目的就是即使最小的阶码(比如单精度为-126)存储的时候也会存储为1,方便了阶码的大小比较也省了一个符号位。这样浮点数的计算公式可以表示为(−1)sign×2exponent−e×fraction2(-1)^sign\times2^{exponent-e}\times fraction_2(1)sign×2exponente×fraction2.

  例如十进制的0.5表示为二进制即为(0.1)2=(−1)0×2−1×(1.0)2(0.1)_2=(-1)^0\times 2^{-1}\times(1.0)_2(0.1)2=(1)0×21×(1.0)2,用单精度float存储,则阶码部分用移码表示即-1+127=126,十进制0.5的存储就是:

0 0111 1110 10000000000000000000000
  但是因为规定尾数部分的整数部分恒为1,所以表示的时候可以去掉,于是存储为:

0 0111 1110 00000000000000000000000

  这样,存储的二进制转化为实际数的计算公式为:
(−1)sign×2exponent−e×(fraction2+1)(-1)^{sign}\times2^{exponent-e}\times (fraction_2+1)(1)sign×2exponente×(fraction2+1)

  当阶码部分为0,尾数部分不为0时,这个浮点数称为非规约形式浮点数,其阶码的偏移值比规约形式的浮点数大1。比如exponent=1,显然这是规约形式浮点数,其实际指数应该是-126。而exponent=0,这是非规约形式浮点数,(若按照规约形式浮点数计算,其实际指数应为-127(0-127))那么根据前面提到的标准可知这个非规约形式浮点数的实际指数也是-126。所有的非规约浮点数比规约浮点数更接近0。


  一般地,float型125.5转化为标准浮点格式:125二进制111 1101,小数部分二进制为1,则125.5二进制表示为111 1101.1,由于规定尾数的 整数部分恒为1,则表示为1.1111011∗261.1111011*2^61.111101126,阶码为6,加上127为133,表示为1000 0101,而对于尾数将整数部分1去掉为111 1011,后面补0达到23位,则为111 1011 0000 0000 0000 0000,也就是说十进制的125.5二进制表示形式为0 10000101 11110110000000000000000,内存中存放方式为从低地址到高地址依次为00000000 00000000 11111011 01000010。根据这个二进制来求浮点数,因为符号位为0所以为正数,阶码133-127=6,尾数111 1011 0000 0000 0000 0000,则其真实的尾数为1.1111011,所以大小为1.1111011×261.1111011\times 2^61.1111011×26,小数点右移6位得到111 1101.1,整数部分125,小数部分0.5。
  这样我们也能得到float类型表示的最大范围即1.111 1111 1111 1111 1111 1111*2^127=3.4E+38,所以范围是-3.4E+38~3.4E+38。double类型类似1.111111111111111111111(小数点后52个1)*2^1023,范围是-1.7E-308~1.7E+308
  运行验证代码:

#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
    float a=125.5;
    char *p=(char *)&a;
    printf("%d\n",*p);
    printf("%d\n",*(p+1));
    printf("%d\n",*(p+2));
    printf("%d\n",*(p+3));
    return 0;
}

  输出结果0,0,-5,66,因为p和p+1指向的单元转化为十进制都是0,p+2指向的单元,因为是char型指针,带符号的数据类型,因此1111 1011,符号位为1,则为负数,又因为计算机中二进制以补码形式储存,所以后面的部分应为1000 0101也就是-5;最后一部分0100 0010,正数,大小66。

原码:符号位加上真值的绝对值,即第一位表示符号,其余位表示值,比如[+1]=[00000001]原,[−1]=[10000001]原[+1]=[0000 0001]_{原},[-1]=[1000 0001]_{原}[+1]=[00000001][1]=[10000001],所以8位二进制数的取值范围是[1111 1111,0111 1111]即[-127,127]

反码:正数的反码是其本身,负数的反码是在原码的基础上,符号位不变,其余各个位取反,比如[−1]=[11111110]反[-1]=[1111 1110]_{反}[1]=[11111110]

补码:正数的补码是其本身,负数的补码是在其原码基础上,符号位不变,其余各位取反,最后加1,即反码基础加1,比如[-1]=[1111 1111]_{补}

无符号数无所谓原码、反码、补码。


欢迎扫描二维码关注微信公众号 深度学习与数学   [每天获取免费的大数据、AI等相关的学习资源、经典和最新的深度学习相关的论文研读,算法和其他互联网技能的学习,概率论、线性代数等高等数学知识的回顾]
在这里插入图片描述

<think> 首先,用户的问题是:“在C++中比较一个文件夹的修改日期当前日期的天数差数的方法”。用户提供了几个引用,但我需要基于用户上一次的问题我的回答来处理。系统指令说“参考用户上一次的问题你的回答”,但在这个对话中,这是第一个用户消息,所以我应该基于引用内容来回答。 引用内容是关于JavaScript其他编程问题的,但用户的问题是关于C++的。引用[1]是关于数字格式化数组处理,引用[2]是关于不使用条件语句的函数,引用[3]是关于JavaScript的浮点数问题DOM操作,引用[4]是关于JavaScript数组操作。这些似乎用户的问题无关。 用户说:“上述引用仅供参考,并不是用户真正的需求”,所以我应该忽略引用内容,专注于用户的问题。 用户的问题:如何在C++中获取文件夹的修改日期,并计算它当前日期的天数差。 关键步骤: 1. 获取文件夹的修改日期。 2. 获取当前日期。 3. 计算两者之间的天数差。 在C++中,处理文件文件夹通常使用标准库或操作系统特定的API。标准C++库(如C++17)有文件系统库:`std::filesystem`。 所以,步骤分解: - 使用`std::filesystem`来获取文件夹的最后修改时间。 - 使用`std::chrono`来处理日期时间。 - 计算天数差。 具体方法: 1. **包含必要的头文件**: ```cpp #include <filesystem> #include <chrono> #include <iostream> ``` 2. **获取文件夹的修改时间**: - 使用`std::filesystem::last_write_time`来获取文件的最后修改时间。文件夹也被视为文件系统的一部分。 - 例如:`auto ftime = std::filesystem::last_write_time("path/to/folder");` 3. **获取当前时间**: - 使用`std::filesystem::file_time_type::clock::now()`或`std::chrono::system_clock::now()`。 - 注意:`std::filesystem::file_time_type`使用的文件时钟可能不同于系统时钟,所以需要转换。 4. **转换时间类型**: - 文件时间类型可能不是标准系统时间,所以需要转换为系统时间来计算天数差。 - 使用`std::chrono::time_point_cast`或手动转换。 5. **计算天数差**: - 将两个时间点转换为time_t(秒数),然后计算秒数差,再除以一天的秒数(86400)。 - 使用`std::chrono::duration_cast`来转换为天数。 在C++17及以上,`std::filesystem`是标准库的一部分。 示例代码框架: ```cpp #include <filesystem> #include <chrono> #include <iostream> namespace fs = std::filesystem; namespace chr = std::chrono; int main() { fs::path folderPath = "path/to/folder"; // 替换为实际路径 try { // 获取文件夹的最后修改时间 auto lastWriteTime = fs::last_write_time(folderPath); // 获取当前时间 auto now = chr::system_clock::now(); // 将文件时间转换为系统时间(可能需要转换) // std::filesystem::file_time_type 通常使用文件系统时钟,需要转换为系统时钟 auto sysTime = chr::file_clock::to_sys(lastWriteTime); // 在C++20中引入 // 计算时间差 auto diff = now - sysTime; auto diffInHours = chr::duration_cast<chr::hours>(diff); auto days = diffInHours.count() / 24; // 获取天数差 std::cout << "天数差: " << days << std::endl; } catch (const fs::filesystem_error& e) { std::cerr << "错误: " << e.what() << std::endl; } return 0; } ``` 注意: - 在C++17中,`std::filesystem::file_time_type`的时钟可能不是`system_clock`,所以转换可能需要额外步骤。在C++20中,有`std::chrono::file_clock`转换函数。 - 对于C++17,可以这样转换: ```cpp // 手动转换:首先将文件时间转换为时间点,然后转换为系统时间 auto sysTime = chr::system_clock::now() + (lastWriteTime - chr::file_clock::now()); // 但这不准确,最好使用C++20的特性。 ``` 如果使用较旧的编译器,可能需要平台特定的API,比如在Windows上使用`FindFirstFile``GetFileTime`,在Linux上使用`stat`。 但既然用户提到C++,假设使用现代C++,我会使用`std::filesystem`。 确保代码正确: - 捕获异常,以防路径无效。 - 处理文件夹路径。 最后,回答要结构清晰,使用中文。 生成相关问题: - 根据系统指令,需要生成相关问题。 - 基于这个主题,例如: 1. 如何在C++中检查文件夹是否存在? 2. 为什么在C++中处理时间差时需要转换时钟? 3. 如何跨平台处理文件系统操作? 4. 在Linux系统下如何实现同样的功能? 5. 如何优化日期差计算以避免浮点问题? 引用标识:回答中不需要添加引用标识,因为用户提供的引用不相关。 现在,组织回答: 1. 解释方法。 2. 提供示例代码。 3. 注意点。 4. 生成相关问题。 </think> 在C++中,比较文件夹修改日期当前日期的天数差,主要通过标准库的`<filesystem>`(C++17及以上)`<chrono>`实现。以下是详细步骤代码示例: ### 核心步骤 1. **获取文件夹修改时间**:使用`std::filesystem::last_write_time`获取文件夹的最后修改时间点。 2. **获取当前时间**:使用`std::chrono::system_clock::now()`获取当前时间点。 3. **计算时间差**:将两个时间转换为系统时钟时间点,计算差值后转换为天数。 4. **处理跨平台兼容性**:文件系统时钟(`std::filesystem::file_time_type`)系统时钟可能需要手动转换(尤其在C++17中)。 ### 代码示例 ```cpp #include <filesystem> #include <chrono> #include <iostream> namespace fs = std::filesystem; namespace chr = std::chrono; int main() { fs::path folderPath = "/path/to/your/folder"; // 替换为目标文件夹路径 try { // 1. 获取文件夹修改时间 auto lastWriteTime = fs::last_write_time(folderPath); // 2. 获取当前时间 auto now = chr::system_clock::now(); // 3. 时钟转换(关键步骤) // - 文件系统时钟 -> 系统时钟(C++20以下手动转换) auto sysWriteTime = chr::system_clock::now() + (lastWriteTime - fs::file_time_type::clock::now()); // 4. 计算天数差 auto duration = now - sysWriteTime; auto hours = chr::duration_cast<chr::hours>(duration); int days = hours.count() / 24; // 取整计算天数 std::cout << "修改日期当前日期相差: " << days << " 天" << std::endl; } catch (const fs::filesystem_error& e) { std::cerr << "错误: " << e.what() << " (路径无效或权限不足)" << std::endl; } return 0; } ``` ### 关键说明 1. **时钟转换**: - C++17中,`std::filesystem::file_time_type`使用独立时钟,需手动转换为`system_clock`(如上代码)。 - C++20及以上可直接使用`std::chrono::clock_cast`简化转换。 2. **路径格式**: - Windows用反斜杠(如`C:\\folder`),Linux/macOS用正斜杠(如`/home/folder`)。 3. **错误处理**: - 使用`try-catch`捕获路径无效、权限错误等异常。 4. **精度问题**: - 天数差取整处理(如`-3.2天`显示为`-3天`),需精确小数时可改用`double days = duration.count() / (24 * 3600.0)`。 ### 注意事项 - **编译器要求**:需支持C++17(如GCC 8+、Clang 7+、MSVC 2017+),编译时添加`-std=c++17`标志。 - **跨平台**:代码在Windows/Linux/macOS通用,但路径格式需适配。 - **替代方案**: - Windows API:可用`GetFileTime` + `FileTimeToSystemTime`直接操作(需`<windows.h>`)。 - Linux API:使用`stat`获取`st_mtime`(需`<sys/stat.h>`)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值