FloatToString
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
返回值:为转换后的字符串长度。空间不足或出错时返回 0。 1.3 要求
用 C 或 C++语言实现,可以使用系统库,但不得使用任何其它辅助函数或类; 不得使用系统库中已提供的相关转换函数或类; 以十进制的科学计数法表示,形式为:-3.354e-44,表示-3.354*(10^-44)。尾数的区间为 (10,-10); 当 pBuf 的空间不足时,可适当截短尾数的精度。当 pBuf 的空间不够存放符号或阶数时, 返回 0;
1. 问题重述
1.1 目标 写一个函数,实现将 float 型变量转换为字符串的功能。
1.2 格式 函数头定义如下。
int ftostr( char *pBuf, int nSize, float fNum )
参数表:pBuf 用来存放转换后的字符串;nSize 为用户指定的 pBuf 的大小(以字节为单 位);fNum 是待转换的浮点数。
������ = 1 +
23
������������ 2������
1
������ =1
下面讨论������的纯小数部分最多有多少位。显然,当������23 为 1 时������的小数位数最多,即只要 知道1/223有多少位小数即可。级数的第 1 项为 0.5,第 2 项为 0.25,第 3 项为 0.125。设第
星火燎原
4/7
memset( pMantissa, 0, 24 * sizeof( int ) ); pMantissa[0] = 1; int aOrder[24] = { 0 }; // 存放2的级数的数组 aOrder[0] = 1; for ( int i = 0; i < 23; ++i ) {
值。那么公式(1)中的级数为等比数列,有:
������
=
1
+
1 2
[1 − 1−
1 2
1 2
23
] <2
易知:1 < ������ < 2。综上所述,������的最大位数为23 + 1 = 24(结论 1)。
3. 算法实现
3.1 二进制小数转为十进制科学计数法 用一个含 24 个元素的整型数组 1 来存放������,每一个元素存放一位。现在只要按照公式(1),
unsigned char cBit = pData[ 2 - ( i + 1 ) / 8 ]; cBit <<= ( i + 1 ) % 8; // 将左边的位清零 cBit >>= 7U; // 右移至最低位 ArrayDivBy2( aOrder, 24 ); if ( cBit ) {
ArrayAddition( pMantissa, aOrder, 24 ); } } }
pArray1[nSize] += dr.quot + pArray2[nSize]; pArray1[nSize] = ( dr = div( pArray1[nSize], 10 ) ).rem; } return dr.quot; }
要得到������������必须先确定������������所在的字节。在实际情况中,float 是以逆字节序存储的。从内存 中的低字节到高字节的存储的顺序如图 2 所示。
现在得到了浮点数中尾数的十进制形式,但必须乘以 2 的指数次方才能得到真正的原数。 指数位于第 4 个字节的第 7 位到第 1 位和第 3 个字节的第 8 位。按照 IEEE745 标准,float 中的指数是原指数加 127 所得。下面是获取指数的代码:
int ExtractExponent( float fNum ) {
} return dr.rem; }
两个由数组表示的大数相加,从最低位开始计算。对应位相加后除以 10,将余数附给 该位,把商加到下一位。下面是具体的代码:
int ArrayAddition( int *pArray1, int *pArray2, int nSize ) {
div_t dr = { 0 }; // 商和余数 for ( --nSize; nSize >= 0; --nSize ) {
Crea mdog
星火燎原
1/7
C++实现定点算法将浮点数转为字符串
作者:Creamdog
注意 :此论文非正式发表,本人不对因文章内容带来的任何后果负责。因时间问题,还有很 多后续修改工作暂未完成,请见谅! 摘要:将浮点数转为字符串的具体算法在网上少有涉及,一般都采用浮点法,即通过浮点运 算确定需要的每一位上的数字。本文介绍的方法是定点法,即对 IEEE 745[1]的浮点数编码规 范进行硬解码。这种方法效率不高,但是精度确很好,而且思路明 晰,可为浮点数计算原理 的研究工作提供参考。本文仅用 C++语言针对 float 类型的变量做了阐释,其它类型的浮点 数如 double,只是在各部分存储的长度上有差异,因此不做额外讨论。 关键词:浮点数; C++; IEEE745; 转换; 算法
Crea mdog
星火燎原
2/7
Leabharlann Baidu
图 1:float 类型变量的编码结构
根据 IEEE745 标准规定,float 的尾数必须为规格化的二进制纯小数。规格化就是指通过
移动小数点(改变指数)的方法,使小数成为 1.x 的形式。由于小数点左边的 1 是冗余的,
因此在转换过程中舍去。设������������为 float 中的第 i 位尾数,那么转化为 10 进制小数的公式为:
=
������
+
2× 2
10������
+
2
×
10������ −1
+
5
×
10������ −2
由此可以得知,级数的第 23 项������23的小数点后位数为 23 位,也就是������的纯小数部分最
多 23 位。而������的整数部分不会超过 2,证明:对于(1, 23)内所有的������,������������都是 1 时,������取最大
图 2:float 类型变量的内存结构
获得一个浮点数四个字节的数据,可以简单地通过强制类型转换实现。如果字节序和������都 从 0 开始计算,那么确定字节的公式为:2 − ������ + 1 8 ,计算������������在该字节中的位数的公式 为: ������ + 1 ������������������ 8。
获得指数后可以利用前面的结果计算真正的十进制原数了。如果指数小于等于 0,就将 十进制尾数除以 2,一共除指数次。跟据结论 1 可知,每次除以 2 后尾数就会向后延伸一位。 指数的绝对值最高为 128,因此必须预留一个128 + 24 = 152个字节的数组。将原尾数数组 按头对齐拷贝到大数组中,然后循环执行除 2 的函数即可。计算结束后,从第 0 个元素开始 数起,首次出现非 0 数的元素的位置,就是以 10 为底的科学技术法的指数。
nStart = 128; memcpy( &pArray[nStart], aMantissa, sizeof(int) * 24 ); for ( int i = 0; i < nExponent; ++i ) {
ArrayMulti2( pArray, 152 ); } } else { // 指数小于0,循环除以2 memcpy( pArray, aMantissa, sizeof(int) * 24 ); for ( int i = 0; i < abs( nExponent ); ++i ) {
������项第最后两个数字为 25,那么第������项可表示为
������������ = ������ + 2 × 10������ + 5 × 10������−1 其中������ + 2 × 10������可以被 2 整除,则有:
������������ +1
=
������������ 2
如果指数大于 0,就将十进制尾数乘以 2,一共乘指数次。和除法类似,每次乘 2 后尾 数就会向前延伸一位。乘法是从后向前进位,因此要按尾对齐拷贝。计算结束后,从第 0 个元素开始数起,首次出现非 0 数的元素的位置,减去计算结束前的位置,就是以 10 为底 的科学技术法的指数。大数乘法的实现代码如下:
int ArrayMulti2( int *pArray, int nSize ) { // 视数组为一个大整数,将该数乘以2
Crea mdog
星火燎原
5/7
div_t dr = { 0 }; // 商和余数 for ( int i = nSize - 1; i >= 0; --i ) {
pArray[i] *= 2; pArray[i] += dr.quot; // 加上前一个元素的余数 pArray[i] = ( dr = div( pArray[i], 10 ) ).rem; // 除以2 } return dr.quot; // 返回未能进位的商 }
2. 基本理论
目前大多数编程环境中,float 类型浮点数采用科学计数法表达。但它又不同于平时常 用的以 10 为底数的科学计数法,它是以 2 为底数,表示方式为:
−1 ������ × ������ × 2������ 其中 S 为符号,正为 0,负为 1;T 为尾数,以二进制的纯小数方式表达;E 为指数。 在 float 中符号,指数和尾数分开存储,字长为 32 位,占用连续的四个字节,存储结构如图 1 所示。
得到对于(1, 23)内所有的1/2������,分别乘以������������并累加到数组中即可。 计算1/2������需要再申请一个有 24 个元素的整型数组 2,清零后第一个元素设为 1。用该数
组连续除以 2,运算������次就可以得到1/2������。每次除以 2 的方法是:从第一个元素开始循环,当 前元素除以 2 的商向下取整后赋值给自己,余数乘以 10 加到下一个元素中,然后执行下一 次循环。下面是具体的代码:
结合以上的函数和算法,容易实现获取 float 变量中十进制小数的算法。具体代码如下:
void ExtractMantissa( float fNum, int *pMantissa ) {
unsigned char *pData = (unsigned char*)&fNum;
Crea mdog
unsigned char *pData = (unsigned char*)&fNum; unsigned char cExp = pData[3]; cExp <<= 1; //除掉附号位 cExp |= ( pData[2] >> 7 ); // 与下个字节的首位合并,组成阶码 cExp -= 0x7F; // 减127 return *(char*)&cExp2; }
综上所述,将浮点数转为以 10 为底的科学计数法的十进制数组的代码为:
int GetDecNum( float fNum, int *pArray ) {
int aMantissa[24]; ExtractMantissa( fNum, aMantissa ); int nExponent = ExtractExponent( fNum ); memset( pArray, 0, sizeof(int) * 152 ); int nStart = 0; if ( nExponent > 0 ) { // 指数大于0,循环乘2
int ArrayDivBy2( int *pArray, int nSize ) { // 视数组为一个大整数,将该数除以2
div_t dr = { 0 }; // 商和余数 for ( int i = 0; i < nSize; ++i )
Crea mdog
星火燎原
3/7
{ pArray[i] += dr.rem * 10; // 加上前一个元素的余数 pArray[i] = ( dr = div( pArray[i], 2 ) ).quot;// 除以2
ArrayDivBy2( pArray, 25 + i ); } } int nFirst = 0; // 记录第一个非0数的位置 for ( ; pArray[nFirst] == 0 && nFirst <= 128; ++nFirst ); int nExp10 = nStart - nFirst; // 计算以10为底的指数 if ( nExp10 != 0 || nExponent > 0 ) { // 向左移位,舍去头部的0 for ( int i = 0; i < 24; ++i ) {
用 C 或 C++语言实现,可以使用系统库,但不得使用任何其它辅助函数或类; 不得使用系统库中已提供的相关转换函数或类; 以十进制的科学计数法表示,形式为:-3.354e-44,表示-3.354*(10^-44)。尾数的区间为 (10,-10); 当 pBuf 的空间不足时,可适当截短尾数的精度。当 pBuf 的空间不够存放符号或阶数时, 返回 0;
1. 问题重述
1.1 目标 写一个函数,实现将 float 型变量转换为字符串的功能。
1.2 格式 函数头定义如下。
int ftostr( char *pBuf, int nSize, float fNum )
参数表:pBuf 用来存放转换后的字符串;nSize 为用户指定的 pBuf 的大小(以字节为单 位);fNum 是待转换的浮点数。
������ = 1 +
23
������������ 2������
1
������ =1
下面讨论������的纯小数部分最多有多少位。显然,当������23 为 1 时������的小数位数最多,即只要 知道1/223有多少位小数即可。级数的第 1 项为 0.5,第 2 项为 0.25,第 3 项为 0.125。设第
星火燎原
4/7
memset( pMantissa, 0, 24 * sizeof( int ) ); pMantissa[0] = 1; int aOrder[24] = { 0 }; // 存放2的级数的数组 aOrder[0] = 1; for ( int i = 0; i < 23; ++i ) {
值。那么公式(1)中的级数为等比数列,有:
������
=
1
+
1 2
[1 − 1−
1 2
1 2
23
] <2
易知:1 < ������ < 2。综上所述,������的最大位数为23 + 1 = 24(结论 1)。
3. 算法实现
3.1 二进制小数转为十进制科学计数法 用一个含 24 个元素的整型数组 1 来存放������,每一个元素存放一位。现在只要按照公式(1),
unsigned char cBit = pData[ 2 - ( i + 1 ) / 8 ]; cBit <<= ( i + 1 ) % 8; // 将左边的位清零 cBit >>= 7U; // 右移至最低位 ArrayDivBy2( aOrder, 24 ); if ( cBit ) {
ArrayAddition( pMantissa, aOrder, 24 ); } } }
pArray1[nSize] += dr.quot + pArray2[nSize]; pArray1[nSize] = ( dr = div( pArray1[nSize], 10 ) ).rem; } return dr.quot; }
要得到������������必须先确定������������所在的字节。在实际情况中,float 是以逆字节序存储的。从内存 中的低字节到高字节的存储的顺序如图 2 所示。
现在得到了浮点数中尾数的十进制形式,但必须乘以 2 的指数次方才能得到真正的原数。 指数位于第 4 个字节的第 7 位到第 1 位和第 3 个字节的第 8 位。按照 IEEE745 标准,float 中的指数是原指数加 127 所得。下面是获取指数的代码:
int ExtractExponent( float fNum ) {
} return dr.rem; }
两个由数组表示的大数相加,从最低位开始计算。对应位相加后除以 10,将余数附给 该位,把商加到下一位。下面是具体的代码:
int ArrayAddition( int *pArray1, int *pArray2, int nSize ) {
div_t dr = { 0 }; // 商和余数 for ( --nSize; nSize >= 0; --nSize ) {
Crea mdog
星火燎原
1/7
C++实现定点算法将浮点数转为字符串
作者:Creamdog
注意 :此论文非正式发表,本人不对因文章内容带来的任何后果负责。因时间问题,还有很 多后续修改工作暂未完成,请见谅! 摘要:将浮点数转为字符串的具体算法在网上少有涉及,一般都采用浮点法,即通过浮点运 算确定需要的每一位上的数字。本文介绍的方法是定点法,即对 IEEE 745[1]的浮点数编码规 范进行硬解码。这种方法效率不高,但是精度确很好,而且思路明 晰,可为浮点数计算原理 的研究工作提供参考。本文仅用 C++语言针对 float 类型的变量做了阐释,其它类型的浮点 数如 double,只是在各部分存储的长度上有差异,因此不做额外讨论。 关键词:浮点数; C++; IEEE745; 转换; 算法
Crea mdog
星火燎原
2/7
Leabharlann Baidu
图 1:float 类型变量的编码结构
根据 IEEE745 标准规定,float 的尾数必须为规格化的二进制纯小数。规格化就是指通过
移动小数点(改变指数)的方法,使小数成为 1.x 的形式。由于小数点左边的 1 是冗余的,
因此在转换过程中舍去。设������������为 float 中的第 i 位尾数,那么转化为 10 进制小数的公式为:
=
������
+
2× 2
10������
+
2
×
10������ −1
+
5
×
10������ −2
由此可以得知,级数的第 23 项������23的小数点后位数为 23 位,也就是������的纯小数部分最
多 23 位。而������的整数部分不会超过 2,证明:对于(1, 23)内所有的������,������������都是 1 时,������取最大
图 2:float 类型变量的内存结构
获得一个浮点数四个字节的数据,可以简单地通过强制类型转换实现。如果字节序和������都 从 0 开始计算,那么确定字节的公式为:2 − ������ + 1 8 ,计算������������在该字节中的位数的公式 为: ������ + 1 ������������������ 8。
获得指数后可以利用前面的结果计算真正的十进制原数了。如果指数小于等于 0,就将 十进制尾数除以 2,一共除指数次。跟据结论 1 可知,每次除以 2 后尾数就会向后延伸一位。 指数的绝对值最高为 128,因此必须预留一个128 + 24 = 152个字节的数组。将原尾数数组 按头对齐拷贝到大数组中,然后循环执行除 2 的函数即可。计算结束后,从第 0 个元素开始 数起,首次出现非 0 数的元素的位置,就是以 10 为底的科学技术法的指数。
nStart = 128; memcpy( &pArray[nStart], aMantissa, sizeof(int) * 24 ); for ( int i = 0; i < nExponent; ++i ) {
ArrayMulti2( pArray, 152 ); } } else { // 指数小于0,循环除以2 memcpy( pArray, aMantissa, sizeof(int) * 24 ); for ( int i = 0; i < abs( nExponent ); ++i ) {
������项第最后两个数字为 25,那么第������项可表示为
������������ = ������ + 2 × 10������ + 5 × 10������−1 其中������ + 2 × 10������可以被 2 整除,则有:
������������ +1
=
������������ 2
如果指数大于 0,就将十进制尾数乘以 2,一共乘指数次。和除法类似,每次乘 2 后尾 数就会向前延伸一位。乘法是从后向前进位,因此要按尾对齐拷贝。计算结束后,从第 0 个元素开始数起,首次出现非 0 数的元素的位置,减去计算结束前的位置,就是以 10 为底 的科学技术法的指数。大数乘法的实现代码如下:
int ArrayMulti2( int *pArray, int nSize ) { // 视数组为一个大整数,将该数乘以2
Crea mdog
星火燎原
5/7
div_t dr = { 0 }; // 商和余数 for ( int i = nSize - 1; i >= 0; --i ) {
pArray[i] *= 2; pArray[i] += dr.quot; // 加上前一个元素的余数 pArray[i] = ( dr = div( pArray[i], 10 ) ).rem; // 除以2 } return dr.quot; // 返回未能进位的商 }
2. 基本理论
目前大多数编程环境中,float 类型浮点数采用科学计数法表达。但它又不同于平时常 用的以 10 为底数的科学计数法,它是以 2 为底数,表示方式为:
−1 ������ × ������ × 2������ 其中 S 为符号,正为 0,负为 1;T 为尾数,以二进制的纯小数方式表达;E 为指数。 在 float 中符号,指数和尾数分开存储,字长为 32 位,占用连续的四个字节,存储结构如图 1 所示。
得到对于(1, 23)内所有的1/2������,分别乘以������������并累加到数组中即可。 计算1/2������需要再申请一个有 24 个元素的整型数组 2,清零后第一个元素设为 1。用该数
组连续除以 2,运算������次就可以得到1/2������。每次除以 2 的方法是:从第一个元素开始循环,当 前元素除以 2 的商向下取整后赋值给自己,余数乘以 10 加到下一个元素中,然后执行下一 次循环。下面是具体的代码:
结合以上的函数和算法,容易实现获取 float 变量中十进制小数的算法。具体代码如下:
void ExtractMantissa( float fNum, int *pMantissa ) {
unsigned char *pData = (unsigned char*)&fNum;
Crea mdog
unsigned char *pData = (unsigned char*)&fNum; unsigned char cExp = pData[3]; cExp <<= 1; //除掉附号位 cExp |= ( pData[2] >> 7 ); // 与下个字节的首位合并,组成阶码 cExp -= 0x7F; // 减127 return *(char*)&cExp2; }
综上所述,将浮点数转为以 10 为底的科学计数法的十进制数组的代码为:
int GetDecNum( float fNum, int *pArray ) {
int aMantissa[24]; ExtractMantissa( fNum, aMantissa ); int nExponent = ExtractExponent( fNum ); memset( pArray, 0, sizeof(int) * 152 ); int nStart = 0; if ( nExponent > 0 ) { // 指数大于0,循环乘2
int ArrayDivBy2( int *pArray, int nSize ) { // 视数组为一个大整数,将该数除以2
div_t dr = { 0 }; // 商和余数 for ( int i = 0; i < nSize; ++i )
Crea mdog
星火燎原
3/7
{ pArray[i] += dr.rem * 10; // 加上前一个元素的余数 pArray[i] = ( dr = div( pArray[i], 2 ) ).quot;// 除以2
ArrayDivBy2( pArray, 25 + i ); } } int nFirst = 0; // 记录第一个非0数的位置 for ( ; pArray[nFirst] == 0 && nFirst <= 128; ++nFirst ); int nExp10 = nStart - nFirst; // 计算以10为底的指数 if ( nExp10 != 0 || nExponent > 0 ) { // 向左移位,舍去头部的0 for ( int i = 0; i < 24; ++i ) {