我可以相信 sprintf() 可以消除浮点不精确吗?
Can I trust sprintf() to eliminate floating point imprecision?
我有以下 C 代码来确定双精度值的小数位。它使用 sprintf()
的 %f
来结束一个没有浮点不精确的字符串。是否存在 sprintf()
没有正确消除浮点不精确的风险,或者该函数是否可以安全使用?
#include <stdio.h>
#include <stdbool.h>
#define MAX_STRING 256
int getDecimalPlaces(double n)
{
char string[MAX_STRING];
int numberOfDecimalPlaces = 0;
int i = sprintf(string, "%f", n);
bool countZero = false;
for (int j = i - 1; j > 0; j--)
{
if (string[j] == '.')
break;
if (!countZero && string[j] != 48)
countZero = true;
if (countZero)
numberOfDecimalPlaces++;
}
return numberOfDecimalPlaces;
}
我调用数字函数来对它们执行数学运算(最不常见的乘法)。我需要此操作的整数,以便我搜索小数位数最多的数字。然后我将每个数字乘以 10 ^(大多数小数位)。处理的数字是用户输入的,属于线性方程组。在大多数情况下,2 位小数就足够了。
数字本身不必 非常 精确,但它们的小数位 与用户输入的内容相匹配.
您的函数只为任何有限数生成 6 位小数,因此它不能 return 大于 6
的小数位数,returned 的小数位数可以小得多超出预期:如果值不是整数但足够接近,函数将 return 0
,例如:getDecimalPlaces(1.0000004)
或 getDecimalPlaces(1.9999996)
.
%f
将数字格式化为固定小数位数,您可以使用 precision 字段指定小数位数,例如 %.10f
。默认为 6
.
如果要明确表示值 n
所需的小数位数,应使用 %320.18g
并处理可能为 [=20= 之间的值生成的指数部分] 和 1
.
但是请注意,double
类型在内部使用二进制表示浮点值。除非该值是 2 的负幂的精确倍数,否则表示形式是其十进制表示形式的近似值。
例如1.5
是精确表示,没有近似值,但是0.1
不是,就像1/3不能用小数表示一样。
0.1
的内部表示是近似值,但是根据sprintf()
的实现,将这个近似值转换为十进制可以产生0.1000000000000000000013552527156068805425093160010874271392822265625
或者只是0.1
,因为两者都会转换回相同的值。
sprintf
在您的函数中使用编码不安全:对于大于 1e250 的非常大的数字,snprintf
将产生超过 256 个字符并写入数组末尾.使用 snprinf
.
更安全
如果您的目标是将值转换为最多 6 位小数且没有指数的十进制形式的值,您可以更改您可以使用此修改版本:
#include <stdio.h>
int convertValue(char *dest, size_t size, double d, int maxplaces) {
int len = snprintf(dest, "%.*f", maxplaces, d);
while (n > 0 && string[len - 1] == '0')
string[--len] = '[=10=]';
if (len > 0 && string[len - 1] == '.')
string[--len] = '[=10=]';
return len;
}
编辑: 为了您的目的,您想将数字转换为整数。确保使用 round(d * power_of_10)
以确保正确转换。如果没有 round()
,您可能会得到不正确的转换,例如 0.7 * 10
转换为 6
而不是 7
。
我有以下 C 代码来确定双精度值的小数位。它使用 sprintf()
的 %f
来结束一个没有浮点不精确的字符串。是否存在 sprintf()
没有正确消除浮点不精确的风险,或者该函数是否可以安全使用?
#include <stdio.h>
#include <stdbool.h>
#define MAX_STRING 256
int getDecimalPlaces(double n)
{
char string[MAX_STRING];
int numberOfDecimalPlaces = 0;
int i = sprintf(string, "%f", n);
bool countZero = false;
for (int j = i - 1; j > 0; j--)
{
if (string[j] == '.')
break;
if (!countZero && string[j] != 48)
countZero = true;
if (countZero)
numberOfDecimalPlaces++;
}
return numberOfDecimalPlaces;
}
我调用数字函数来对它们执行数学运算(最不常见的乘法)。我需要此操作的整数,以便我搜索小数位数最多的数字。然后我将每个数字乘以 10 ^(大多数小数位)。处理的数字是用户输入的,属于线性方程组。在大多数情况下,2 位小数就足够了。
数字本身不必 非常 精确,但它们的小数位 与用户输入的内容相匹配.
您的函数只为任何有限数生成 6 位小数,因此它不能 return 大于 6
的小数位数,returned 的小数位数可以小得多超出预期:如果值不是整数但足够接近,函数将 return 0
,例如:getDecimalPlaces(1.0000004)
或 getDecimalPlaces(1.9999996)
.
%f
将数字格式化为固定小数位数,您可以使用 precision 字段指定小数位数,例如 %.10f
。默认为 6
.
如果要明确表示值 n
所需的小数位数,应使用 %320.18g
并处理可能为 [=20= 之间的值生成的指数部分] 和 1
.
但是请注意,double
类型在内部使用二进制表示浮点值。除非该值是 2 的负幂的精确倍数,否则表示形式是其十进制表示形式的近似值。
例如1.5
是精确表示,没有近似值,但是0.1
不是,就像1/3不能用小数表示一样。
0.1
的内部表示是近似值,但是根据sprintf()
的实现,将这个近似值转换为十进制可以产生0.1000000000000000000013552527156068805425093160010874271392822265625
或者只是0.1
,因为两者都会转换回相同的值。
sprintf
在您的函数中使用编码不安全:对于大于 1e250 的非常大的数字,snprintf
将产生超过 256 个字符并写入数组末尾.使用 snprinf
.
如果您的目标是将值转换为最多 6 位小数且没有指数的十进制形式的值,您可以更改您可以使用此修改版本:
#include <stdio.h>
int convertValue(char *dest, size_t size, double d, int maxplaces) {
int len = snprintf(dest, "%.*f", maxplaces, d);
while (n > 0 && string[len - 1] == '0')
string[--len] = '[=10=]';
if (len > 0 && string[len - 1] == '.')
string[--len] = '[=10=]';
return len;
}
编辑: 为了您的目的,您想将数字转换为整数。确保使用 round(d * power_of_10)
以确保正确转换。如果没有 round()
,您可能会得到不正确的转换,例如 0.7 * 10
转换为 6
而不是 7
。