简体   繁体   English

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

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

I'm trying to recreate printf and I'm currently trying to find a way to handle the conversion specifiers that deal with floats. 我正在尝试重新创建printf,并且当前正在尝试找到一种方法来处理处理浮点数的转换说明符。 More specifically: I'm trying to round doubles at a specific decimal place. 更具体地说:我正在尝试将双精度数四舍五入到特定的小数位。 Now I have the following code: 现在,我有以下代码:

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);
}

Which works for relatively small numbers (I haven't quite figured out whether printf compensates for truncation and rounding errors caused by it but that's another story). 这适用于相对较小的数字(我还没有弄清楚printf是否可以补偿由它引起的截断和舍入错误,但这是另一回事了)。 However, if I try a large number like 但是,如果我尝试大量

-154584942443242549.213565124235

I get -922337203685.4775391 as output, whereas printf itself gives me -154584942443242560.0000000 (precision for both outputs is 7). 我得到-922337203685.4775391作为输出,而printf本身给了我-154584942443242560.0000000 (两个输出的精度都是7)。

Both aren't exactly the output I was expecting but I'm wondering if you can help me figure out how I can make my idea for rounding work with larger numbers. 两者都不完全是我所期望的输出,但是我想知道您是否可以帮助我弄清楚如何使我的想法大数取整。

My question is basically twofold: 我的问题基本上是双重的:

  1. What exactly is happening in this case, both with my code and printf itself, that causes this output? 在这种情况下,我的代码和printf本身到底发生了什么,导致输出? (I'm pretty new to programming, sorry if it's a dumb question) (我对编程非常陌生,如果这是一个愚蠢的问题,请对不起)
  2. Do you guys have any tips on how to make my code capable of handling these bigger numbers? 你们对如何使我的代码能够处理这些更大的数字有任何提示吗?

PS I know there are libraries and such to do the rounding but I'm looking for a reinventing-the-wheel type of answer here, just FYI! PS:我知道有一些库可以进行四舍五入,但是我在这里寻找一种全新的答案,仅供参考。

You can't round to a particular decimal precision with binary floating point arithmetic. 使用二进制浮点算法无法舍入到特定的十进制精度。 It's not just possible. 这不仅是可能的。 At small magnitudes, the errors are small enough that you can still get the right answer, but in general it doesn't work. 在很小的程度上,误差足够小,您仍然可以获得正确的答案,但是总的来说它是行不通的。

The only way to round a floating point number as decimal is to do all the arithmetic in decimal. 将浮点数舍入为十进制的唯一方法是使用十进制进行所有算术运算。 Basically you start with the mantissa, converting it to decimal like an integer, then scale it by powers of 2 (the exponent) using decimal arithmetic. 基本上,您从尾数开始,将其像整数一样转换为十进制,然后使用十进制算术按2的幂(指数)进行缩放。 The amount of (decimal) precision you need to keep at each step is roughly (just a bit over) the final decimal precision you want. 您需要在每个步骤上保持的(十进制)精度大约(想要的)最终的十进制精度。 If you want an exact result, though, it's on the order of the base-2 exponent range (ie very large). 但是,如果您想获得精确的结果,则该结果约为以2为底的指数范围(即非常大)。

Typically rather than using base 10, implementations will use a base that's some large power of 10, since it's equivalent to work with but much faster. 通常,实现将使用10的幂次方,而不是使用10的底数,因为它等效于使用10,但是速度要快得多。 1000000000 is a nice base because it fits in 32 bits and lets you treat your decimal representation as an array of 32-bit ints (comparable to how BCD lets you treat decimal representations as arrays of 4-bit nibbles). 1000000000是一个不错的基础,因为它可以容纳32位,并允许您将十进制表示形式视为32位整数数组(与BCD允许您将十进制表示形式视为4位半字节数组一样)。

My implementation in musl is dense but demonstrates this approach near-optimally and may be informative. 我在musl中的实现非常密集,但几乎可以最佳地演示此方法,并且可能会提供参考。

  1. What exactly is happening in this case, both with my code and printf itself, that causes this output? 在这种情况下,我的代码和printf本身到底发生了什么,导致输出?

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

  1. Do you guys have any tips on how to make my code capable of handling these bigger numbers? 你们对如何使我的代码能够处理这些更大的数字有任何提示吗?

Set aside various int types to do rounding/truncation. 预留各种int类型进行舍入/截断。 Use FP routines like round() , nearby() , etc. 使用FP例程,例如round() ,near nearby()等。

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;
}

As well mentioned in this answer , floating point numbers have binary characteristics as well as finite precision. 就像在这个答案中提到的那样,浮点数具有二进制特征以及有限的精度。 Using only double will extend the range of acceptable behavior. 仅使用double会扩展可接受行为的范围。 With extreme precision , the value computed with this code be close yet potentially only near the desired result. 以极高的precision ,用此代码计算的值接近,但可能仅接近期望的结果。

Using temporary wider math will extend the acceptable range. 使用临时更广泛的数学将扩展可接受的范围。

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是否可以补偿由它引起的截断和舍入错误,但这是另一回事了。

See Printf width specifier to maintain precision of floating-point value to print FP with enough precision. 请参阅Printf宽度说明符,以保持浮点值的精度,以足够的精度打印FP。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM