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

Trying to recreate printf's behaviour with doubles and given precisions (rounding) and have a question about handling big numbers

我正在尝试重新创建 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 的幂(指数)缩放它。您需要在每个步骤中保持的(小数)精度量大致(略高于)您想要的最终小数精度。但是,如果你想要一个精确的结果,它是在以 2 为底的指数范围内的数量级(即非常大)。

通常情况下,而不是使用基数 10,实现将使用一个基数,它是 10 的一些大幂,因为它等效于 工作但更快。 1000000000 是一个很好的基数,因为它适合 32 位并允许您将十进制表示形式视为 32 位整数数组(与 BCD 让您将十进制表示形式视为 4 位半字节数组的方式相比)。

My implementation in musl 很密集,但展示了这种方法 near-optimally 并且可能提供信息。

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

溢出。 ft_power(10, precision) 超过 LLONG_MAX and/or value * power > LLONG_MAX.

  1. 你们有什么技巧可以让我的代码能够处理这些更大的数字吗?

留出各种int类型来做rounding/truncation。使用 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;
}

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

使用临时更宽的数学运算将扩大可接受的范围。

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

I haven't quite figured out whether printf compensates for truncation and rounding errors caused by it but that's another story

参见 Printf width specifier to maintain precision of floating-point value 以足够精确地打印 FP。