在 C++ 中,整数的表示原理主要涉及计算机如何在内存中存储和处理整数数据。以下是简洁而全面的解释,涵盖整数表示的核心原理:
C++ 中的整数类型(如
int
、short
、long
、long long
等)在内存中以 二进制形式
存储。计算机使用固定数量的位(bit)来表示一个整数,位数由整数类型决定,例如:
int
通常占 32 位(4 字节),但在某些系统上可能是 16
位。short
通常占 16 位(2 字节)。long
和 long long
通常占 32 位或 64
位,具体取决于编译器和系统架构。每个位可以是 0 或 1,整数的值通过这些位的组合来表示。
C++ 支持 有符号(signed)和 无符号(unsigned)整数类型,它们的表示方式不同:
unsigned int
):所有位都用于表示非负数值。范围从 0 到,其中 (n) 是位数。例如,32 位无符号整数的范围是 0 到
(即 0 到 4,294,967,295)。
int
):使用最高位(符号位)表示正负,0 表示正数,1
表示负数。其余位表示数值大小。有符号整数通常采用 二进制补码(Two’s Complement)表示法,这是 C++ 标准中常用的编码方式。补码的优点是简化了加减法运算。补码表示法的特点如下:
正数:直接以二进制形式存储,符号位为
0。例如,十进制的 5 在 8 位补码中是 00000101
。
负数:负数通过对其绝对值的补码计算得到。计算步骤:
例如,十进制的 -5 在 8 位补码中:
00000101
。11111010
。11111011
。因此,-5 表示为 11111011
。
范围:对于 (n) 位有符号整数,范围是
到
。例如,32 位 int
的范围是 (-2,147,483,648) 到
(2,147,483,647)。
整数在内存中以 字节(8 位)为单位存储,多字节整数的存储方式涉及 字节序(Endianness):
0x12345678
存储为 12 34 56 78
。0x12345678
存储为 78 56 34 12
。现代 x86 和 x86-64 架构通常使用小端序,而某些嵌入式系统可能使用大端序。C++ 程序员通常无需直接处理字节序,除非涉及跨平台数据传输。
unsigned int
的最大值是,加 1 后变为 0。
int
最大值加 1
可能变为负数,但不能依赖这种行为。C++ 提供了多种整数类型,位数和范围因编译器和平台而异:
short
:至少 16 位。int
:通常 32 位(但可能是 16 位,视平台而定)。long
:至少 32 位。long long
:至少 64 位。unsigned short
、unsigned int
、unsigned long
、unsigned long long
。<cstdint>
提供了
int8_t
、uint8_t
、int16_t
、uint16_t
等,确保跨平台一致性。以下代码展示如何查看整数的二进制表示:
#include <iostream>
#include <bitset>
int main() {
int x = 5;
int y = -5;
std::cout << "5 in binary: " << std::bitset<32>(x) << std::endl;
std::cout << "-5 in binary: " << std::bitset<32>(y) << std::endl;
return 0;
}
输出(假设 32 位 int):
5 in binary: 00000000000000000000000000000101
-5 in binary: 11111111111111111111111111111011
<cstdint>
的固定宽度类型可提高代码可移植性。&
、|
、^
、~
、<<、>>
),可直接操作整数的二进制位。在 C++ 中,float
是一种浮点数类型,用于表示带有小数部分的实数。其表示原理基于
IEEE 754
标准(通常为单精度浮点数)。以下是简洁而全面的解释,涵盖
float
的表示原理:
C++ 中的 float
通常占用 32 位(4
字节),按照 IEEE 754 单精度浮点数标准分为三个部分:
内存布局如下:
| 符号位 (1 位) | 指数 (8 位) | 尾数 (23 位) |
float
的值通过以下公式计算:
例如,十进制数 6.5 的二进制表示为: -
二进制:110.1
(即
)。 - 符号位:0(正数)。 - 指数:实际指数 2,存储值为 (2 + 127 =
129),二进制为 10000001
。 - 尾数:101
补齐为
23 位,101000...0
。 - 最终 32
位表示:0 10000001 10100000000000000000000
。
IEEE 754 标准定义了一些特殊值:
或
float
的绝对值范围大约是到
float
提供约 6-7
位十进制精度,因为尾数只有 23
位。超出精度的部分会被截断,导致精度损失。与整数类似,float
的多字节存储涉及
字节序(Endianness): -
大端序:高位字节存储在低地址。 -
小端序:低位字节存储在低地址(x86 架构常用)。
字节序通常对程序员透明,但在跨平台数据传输时需注意。
a == b
)可能不可靠,建议使用误差范围比较(如
abs(a - b) < epsilon
)。以下代码展示如何查看 float
的二进制表示:
#include <iostream>
#include <bitset>
int main() {
float f = 6.5f;
unsigned int* ptr = reinterpret_cast<unsigned int*>(&f);
std::cout << "6.5 in binary: " << std::bitset<32>(*ptr) << std::endl;
return 0;
}
输出(假设小端序,32 位):
6.5 in binary: 01000000110100000000000000000000
解释:
10000001
(129,实际指数 (129 - 127 = 2))。10100000000000000000000
。float
必须遵循
IEEE 754,但现代编译器(如 GCC、Clang、MSVC)通常遵守此标准。double
类型(通常 64 位),提供更高精度(52 位尾数,11 位指数,偏移
1023)。在 C++ 中,double
和 float
都是浮点数类型,均基于 IEEE 754 标准
表示浮点数,因此在表示原理上有许多相同之处,但也有关键的区别,主要体现在存储大小、精度和范围上。以下是简洁而全面的对比,分析
double
和 float
的表示原理的
相同之处 与 不同之处。
(对于规格化数)。
内存结构:
[符号位 | 指数 | 尾数]
。特殊值表示:
字节序:
浮点运算特性:
==
比较)。abs(a - b) < epsilon
)。特性 | float | double |
---|---|---|
存储大小 | 32 位(4 字节) | 64 位(8 字节) |
符号位 | 1 位 | 1 位 |
指数位数 | 8 位 | 11 位 |
尾数位数 | 23 位 | 52 位 |
指数偏移 | 127 | 1023 |
精度 | 约 6-7 位十进制精度 | 约 15-16 位十进制精度 |
内存布局 | 1 位符号 8 位指数 23 位尾数 | 1 位符号 11 位指数 52 位尾数 |
float范围
到
double范围
到
float
:32 位,其中 1 位符号、8 位指数、23 位尾数。double
:64 位,其中 1 位符号、11 位指数、52
位尾数。double
的尾数位数更多,提供更高精度;指数位数更多,支持更大范围。float
:指数存储值 (e = E + 127),实际指数 (E = e -
127)。double
:指数存储值 (e = E + 1023),实际指数 (E = e -
1023)。double
的指数范围更大(-1022 到
+1023 vs -126 到 +127),支持表示极小或极大的数。float
:范围较小,精度较低,适合对内存敏感但精度要求不高的场景。double
:范围更大,精度更高,适合需要高精度计算的场景(如科学计算)。float
中可能有较大舍入误差,而 double
误差更小。float
:占用内存少,运算速度通常更快,适合嵌入式系统或
GPU 计算。double
:占用内存多,运算速度稍慢,但精度更高,广泛用于科学计算和工程应用。以下代码展示 float
和 double
的二进制表示差异:
#include <iostream>
#include <bitset>
int main() {
float f = 6.5f;
double d = 6.5;
unsigned int* f_ptr = reinterpret_cast<unsigned int*>(&f);
unsigned long long* d_ptr = reinterpret_cast<unsigned long long*>(&d);
std::cout << "float 6.5: " << std::bitset<32>(*f_ptr) << std::endl;
std::cout << "double 6.5: " << std::bitset<64>(*d_ptr) << std::endl;
return 0;
}
输出(假设小端序):
float 6.5: 01000000110100000000000000000000
double 6.5: 0100000000011010000000000000000000000000000000000000000000000000
解释:
float
:符号位 0,指数
10000001
(129,实际指数 2),尾数
101000...0
(23 位)。double
:符号位 0,指数
10000000001
(1025,实际指数 2),尾数
101000...0
(52 位)。float
时需注意精度损失,可能不适合高精度需求;double
是默认选择,适合大多数科学计算。float
;在精度优先场景,选择 double
。long double
(通常
80 位或更高,依赖平台),提供更高精度,但非所有平台都遵循 IEEE
754。如bitcoin中用64位整数来表示Satoshi
1 Bitcoin (BTC) = 100,000,000 Satoshi
则0.00000001个比特币也就是表示为 1个Statoshi,在计算用int64表示就是存放的整数1,而不是存储浮点数。
#include <string>
#include <iomanip>
#include <sstream>
// 将 Satoshi (int64_t) 转换为 Bitcoin (double),并返回格式化的字符串
std::string satoshiToBitcoin(int64_t satoshi) {
// 1 BTC = 100,000,000 Satoshi
constexpr double SATOSHI_PER_BTC = 100000000.0;
// 转换为 Bitcoin 值
double bitcoin = static_cast<double>(satoshi) / SATOSHI_PER_BTC;
// 使用 stringstream 格式化输出,保留 8 位小数
std::ostringstream oss;
<< std::fixed << std::setprecision(8) << bitcoin;
oss
// 返回格式化字符串,移除末尾多余的零
std::string result = oss.str();
.erase(result.find_last_not_of('0') + 1);
resultif (result.back() == '.') {
.pop_back(); // 移除末尾的小数点
result}
return result;
}
float转int64定点
float floatVal = 1.2;
double doubleVal = floatVal;
(int64_t)(doubleVal * (1LL << 32));
让我们逐步分析这段 C++ 代码,解释
float floatVal = 1.2; double doubleVal = floatVal; (int64_t)(doubleVal * (1LL << 32));
的执行过程和结果。这涉及浮点数表示、类型转换和位运算。
代码分解
定义和初始化 float
变量:
float floatVal = 1.2;
floatVal
是一个单精度浮点数(32 位,IEEE 754
标准),初始化为十进制值 1.2
。float
的精度限制(约 6-7
位十进制精度),1.2
可能无法精确表示。将 float
转换为
double
:
double doubleVal = floatVal;
doubleVal
是一个双精度浮点数(64 位,IEEE 754
标准),通过隐式转换从 floatVal
初始化。floatVal
的值,但由于 double
有更高精度(约 15-16 位十进制精度),不会引入额外误差。计算
(int64_t)(doubleVal * (1LL << 32))
:
(1LL << 32)
:1LL
是一个 64
位有符号整数(long long
),左移 32 位,得到doubleVal * (1LL << 32)
:将
doubleVal
乘以 (2^{32}),将浮点数放大到整数范围。(int64_t)(...)
:将乘法结果(double
类型)转换为 int64_t
,通过截断小数部分得到整数。步骤 1:floatVal = 1.2
的表示 - 十进制
1.2
的二进制表示是无限循环的:1.0011001100110011...
(即
float
)中:
,存储指数 (e = E + 127 = 127)(二进制:01111111
)。 -
尾数:小数部分
00110011001100110011001...
,截取前 23
位:00110011001100110011001
。 -
内存布局:0 01111111 00110011001100110011001
。 -
十六进制:0x3F99999A
。 -
实际存储值:由于尾数截断,floatVal
≈
1.2000000476837158203125
(略大于 1.2,因为
float
精度有限)。
doubleVal = floatVal
floatVal
的值(约
1.2000000476837158203125
)被复制到
doubleVal
。double
(64 位,11 位指数,52
位尾数)时,doubleVal
精确存储 floatVal
的值,不会引入额外误差。doubleVal
的值仍然是
1.2000000476837158203125
,因为 float
的精度决定了初始值的误差。(int64_t)(doubleVal * (1LL << 32))
(1LL << 32)
:
1LL
是 64 位整数 1
,左移 32 位得到doubleVal * (1LL << 32)
:
doubleVal
≈
1.2000000476837158203125
。double
,包含整数部分
5,159,780,352
和小数部分
0.000002384185791015625
。int64_t
:
(int64_t)
截断小数部分,只保留整数部分。5,159,780,352
(十进制)。(int64_t)(doubleVal * (1LL << 32))
的值是
5,159,780,352。1.2
被放大 2^32 倍后,理论上应为,但由于 float
精度限制,floatVal
实际值略大于 1.2,导致结果略大。
以下代码验证上述计算:
#include <iostream>
#include <iomanip>
int main() {
float floatVal = 1.2f;
double doubleVal = floatVal;
int64_t result = (int64_t)(doubleVal * (1LL << 32));
std::cout << "floatVal: " << std::fixed << std::setprecision(20) << floatVal << std::endl;
std::cout << "doubleVal: " << std::fixed << std::setprecision(20) << doubleVal << std::endl;
std::cout << "Result: " << result << std::endl;
return 0;
}
输出:
floatVal: 1.20000004768371582031
doubleVal: 1.20000004768371582031
Result: 5159780352
1.2
在 float
中无法精确表示,实际值为
1.2000000476837158203125
,导致放大后的结果偏离理论值。1.2
,可直接初始化
double doubleVal = 1.2;
(double
精度更高,误差更小),结果会更接近理论值
5,153,960,755
。floatVal = 1.2
的 IEEE 754
表示为 0 01111111 00110011001100110011001
(约
1.2000000476837158203125
)。doubleVal
继承
floatVal
的值,保持相同精度。(int64_t)(doubleVal * (1LL << 32))
放大值并截断小数部分,结果为 5,159,780,352
。float
精度限制导致结果略偏离理论值。若需更高精度,建议直接使用
double
初始化。