试图以双精度和给定精度(舍入)重新创建printf的行为,并且对处理大数有疑问

时间:2019-07-04 14:49:13

标签: c floating-point printf rounding

我正在尝试重新创建printf,并且当前正在尝试找到一种方法来处理处理浮点数的转换说明符。更具体地说:我正在尝试将双精度数四舍五入到特定的小数位。现在,我有以下代码:

double  ft_round(double value, int precision)
{
    long long int power;
    long long int result;

    power = ft_power(10, precision);
    result = (long long int) (value * power);
    return ((double)result / power);
}

哪个适用于相对较小的数字(我还不太清楚printf是否可以补偿由它引起的截断和舍入错误,但这是另一回事了)。但是,如果我尝试大量

-154584942443242549.213565124235

我得到-922337203685.4775391作为输出,而printf本身给了我 -154584942443242560.0000000(两个输出的精度均为7)。

这都不是我所期望的输出,但是我想知道您是否可以帮助我弄清楚如何使我的想法对更大的数字取整。

我的问题基本上是双重的:

  1. 在这种情况下,我的代码和printf本身到底发生了什么,导致输出? (我对编程非常陌生,如果这是一个愚蠢的问题,请对不起)
  2. 你们对如何使我的代码能够处理这些更大的数字有任何提示吗?

P.S。我知道有一些库可以进行四舍五入,但是我在这里寻找一种全新的答案,仅供参考!

2 个答案:

答案 0 :(得分:2)

使用二进制浮点算法不能舍入到特定的十进制精度。这不仅是可能的。在很小的范围内,误差足够小,您仍然可以获得正确的答案,但是总的来说是行不通的。

将浮点数舍入为十进制的唯一方法是用十进制进行所有算术运算。基本上,您从尾数开始,将其像整数一样转换为十进制,然后使用十进制算术按2的幂(指数)进行缩放。您需要在每个步骤上保持的(十进制)精度大约(想要的)最终的十进制精度。不过,如果您想获得准确的结果,则该结果应以2为底的指数范围(即非常大)。

通常,实现而不是使用10为基数,实现会使用10的幂数,因为它等效于使用10,但是速度要快得多。 1000000000是一个不错的基础,因为它可以容纳32位,并且可以让您将十进制表示形式视为32位整数数组(与BCD允许您将十进制表示形式视为4位半字节数组相比)。

My implementation in musl非常密集,但是几乎可以最佳地演示这种方法,并且可能会提供参考。

答案 1 :(得分:0)

  1. 在这种情况下,我的代码和printf本身到底发生了什么,导致输出?

溢出。 ft_power(10, precision)超过LLONG_MAX和/或value * power > LLONG_MAX

  1. 你们对如何使我的代码能够处理这些更大的数字有任何提示吗?

预留各种int类型来进行舍入/截断。使用round()nearby()等FP例程。

double ft_round(double value, int precision) {
    // Use a re-coded `ft_power()` that computes/returns `double`
    double pwr = ft_power(10, precision);
    return round(value * pwr)/pwr;
}

正如该answer中所提到的,浮点数具有二进制特性和有限的精度。仅使用double将扩大可接受行为的范围。使用极端precision时,用此代码计算的值接近,但可能仅接近所需结果。

使用暂时的更广泛的数学将扩展可接受的范围。

double ft_round(double value, int precision) {
    double pwr = ft_power(10, precision);
    return (double) (roundl((long double) value * pwr)/pwr);
}

  

我还不太清楚printf是否可以补偿由它引起的截断和舍入错误,但这是另一回事了

请参见Printf width specifier to maintain precision of floating-point value,以足够的精度打印FP。