如何将十进制科学计数法float/double转换为普通字符串
How to convert decimal scientific notation float/double to normal string
我知道当数字不是很大时有很多问题可以解决这个问题,但是当涉及到大数字时(虽然仍在 float/double 范围内),输出将失去精度:
#include <stdio.h>
#include <iostream>
#include <sstream>
int main() {
double d = -1e307, d2 = 1e307;
std::cout << std::fixed << d << std::endl;
printf("d2 is %lf\n", d2);
return 0;
}
此代码将得到如下输出:
-9999999999999999860310597602564577717002641838126363875249660735883565852672743849064846414228960666786379280392654615393353172850252103336275952370615397010730691664689375178569039851073146339641623266071126720011020169553304018596457812688561947201171488461172921822139066929851282122002676667750021070848.000000
d2 is 9999999999999999860310597602564577717002641838126363875249660735883565852672743849064846414228960666786379280392654615393353172850252103336275952370615397010730691664689375178569039851073146339641623266071126720011020169553304018596457812688561947201171488461172921822139066929851282122002676667750021070848.000000
但这显然是不正确的!如何进行真正的转换?!
问题是双精度浮点数是用 IEEE-754 二进制格式表示的,使用 64 位,其精度约为 16 位十进制数字。因此,您看到的整数值是以 IEE-754 格式表示的整数,最接近 1e307
(resp. -1e307
)。
如果您知道自己需要另一种表示,则必须自己手动构建。最简单的方法(恕我直言)是使用默认格式在 stringstream
上输出它,然后解析它:
- 如果里面有
e
,后面是指数,前面是尾数,格式1.234...就是小数点前一位,小数点按指数移动即可,根据需要在数字前面(负指数)或数字后面(正指数)添加尽可能多的 0
- 如果里面没有
e
,则输出已经是固定格式
或者,您可以尝试找到一个 大十进制库 ,它会在内部使用精确的十进制数表示法(或者如果找不到任何满足您要求的,则自己编写).
实际上这是我自己处理的,这是我的代码,该函数将任意浮点字符串转换为普通字符串。
此函数的灵感来自 this question。
// DW: remove e
std::string trim_real(std::string str) {
// DW: all to lowercase
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
// DW: find "e" and "."
size_t pe = str.find_first_of("e");
std::string se = str.substr(pe + 1, str.size() - (pe + 1));
int de = std::stoi(se);
// DW: get string without "e"
std::string sc = str.substr(0, pe);
std::cout << "sc is " << sc << std::endl;
// DW: remove uneccessary 0
size_t pf0 = sc.find_first_not_of("0");
size_t pl0 = sc.find_last_not_of("0");
std::string sc_trim = sc.substr(pf0, pl0 - pf0 + 1);
std::cout << "sc_trim is " << sc_trim << std::endl;
// DW: move "." according to e
size_t pp = sc_trim.find_first_of(".");
if (pp == std::string::npos) {
// DW: this means there is no "."
pp = 0;
}
int pp_tobe = pp + de;
sc_trim.erase(pp, 1);
if (de > 0) {
// DW: should move "." to the right
if (pp_tobe < sc_trim.size()) {
sc_trim.insert(pp_tobe, ".");
} else {
sc_trim += std::string(pp_tobe - sc_trim.size() + 1, '0');
sc_trim += ".0";
}
} else {
// DW: should move "." to the left
if (pp_tobe >= 0) {
sc_trim.insert(pp_tobe, ".");
} else {
sc_trim = "." + std::string(0 - pp_tobe, '0') + sc_trim;
}
}
if (sc_trim == ".") {
// DW: this means sc_trim is actually 0 now
sc_trim = "0.0";
}
return sc_trim;
}
我知道当数字不是很大时有很多问题可以解决这个问题,但是当涉及到大数字时(虽然仍在 float/double 范围内),输出将失去精度:
#include <stdio.h>
#include <iostream>
#include <sstream>
int main() {
double d = -1e307, d2 = 1e307;
std::cout << std::fixed << d << std::endl;
printf("d2 is %lf\n", d2);
return 0;
}
此代码将得到如下输出:
-9999999999999999860310597602564577717002641838126363875249660735883565852672743849064846414228960666786379280392654615393353172850252103336275952370615397010730691664689375178569039851073146339641623266071126720011020169553304018596457812688561947201171488461172921822139066929851282122002676667750021070848.000000
d2 is 9999999999999999860310597602564577717002641838126363875249660735883565852672743849064846414228960666786379280392654615393353172850252103336275952370615397010730691664689375178569039851073146339641623266071126720011020169553304018596457812688561947201171488461172921822139066929851282122002676667750021070848.000000
但这显然是不正确的!如何进行真正的转换?!
问题是双精度浮点数是用 IEEE-754 二进制格式表示的,使用 64 位,其精度约为 16 位十进制数字。因此,您看到的整数值是以 IEE-754 格式表示的整数,最接近 1e307
(resp. -1e307
)。
如果您知道自己需要另一种表示,则必须自己手动构建。最简单的方法(恕我直言)是使用默认格式在 stringstream
上输出它,然后解析它:
- 如果里面有
e
,后面是指数,前面是尾数,格式1.234...就是小数点前一位,小数点按指数移动即可,根据需要在数字前面(负指数)或数字后面(正指数)添加尽可能多的 0 - 如果里面没有
e
,则输出已经是固定格式
或者,您可以尝试找到一个 大十进制库 ,它会在内部使用精确的十进制数表示法(或者如果找不到任何满足您要求的,则自己编写).
实际上这是我自己处理的,这是我的代码,该函数将任意浮点字符串转换为普通字符串。
此函数的灵感来自 this question。
// DW: remove e
std::string trim_real(std::string str) {
// DW: all to lowercase
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
// DW: find "e" and "."
size_t pe = str.find_first_of("e");
std::string se = str.substr(pe + 1, str.size() - (pe + 1));
int de = std::stoi(se);
// DW: get string without "e"
std::string sc = str.substr(0, pe);
std::cout << "sc is " << sc << std::endl;
// DW: remove uneccessary 0
size_t pf0 = sc.find_first_not_of("0");
size_t pl0 = sc.find_last_not_of("0");
std::string sc_trim = sc.substr(pf0, pl0 - pf0 + 1);
std::cout << "sc_trim is " << sc_trim << std::endl;
// DW: move "." according to e
size_t pp = sc_trim.find_first_of(".");
if (pp == std::string::npos) {
// DW: this means there is no "."
pp = 0;
}
int pp_tobe = pp + de;
sc_trim.erase(pp, 1);
if (de > 0) {
// DW: should move "." to the right
if (pp_tobe < sc_trim.size()) {
sc_trim.insert(pp_tobe, ".");
} else {
sc_trim += std::string(pp_tobe - sc_trim.size() + 1, '0');
sc_trim += ".0";
}
} else {
// DW: should move "." to the left
if (pp_tobe >= 0) {
sc_trim.insert(pp_tobe, ".");
} else {
sc_trim = "." + std::string(0 - pp_tobe, '0') + sc_trim;
}
}
if (sc_trim == ".") {
// DW: this means sc_trim is actually 0 now
sc_trim = "0.0";
}
return sc_trim;
}