ç动态的printf双,没有precision的损失和没有尾随零 [英] C dynamically printf double, no loss of precision and no trailing zeroes

查看:142
本文介绍了ç动态的printf双,没有precision的损失和没有尾随零的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是新的C和学习出一本书/关闭互联网。我试着写,我可以传递任何双击来并获得返回功能的 INT 中使用一个的printf(%*。LF... 语句使得 INT 返回既不会削弱precision也不产生尾随零。

我有一个工作的功能,但既然是写的可读性和所有评论了这是pretty大。

要总结的功能,我算需要多少部门10获得双击范围 10 -10 D> = 0 ,只取小数部分,并把它变成一个字符串有n位小数,其中 N = 15 - number_of_digits_left_of_decimal (我读的键入双击只能跟踪15位),检查字符串由右至左为尾随零,并保持计数,最后,返回一个 INT 的再presents的非零数字右边的数小数。

有没有更简单的方法?谢谢你。

  INT get_number_of_digits_after_decimal(双D)
{
  INT I = 0; / *有时你需要一个int * /
  INT PL = 0; / * precision左= 15 - sigfigs * /
  INT sigfigs = 1; / *在d位数的数量* /
  焦线[20]; / *用于查找小数位的最后一个非零数字右* /
  双温度; / *用于计算破坏性d的副本* /  / *找到位数小数点右侧* /
  温度= D;
  而(sigfigs< 15)
  {
    如果(温度℃,)
      温度* = -1;
    如果(温度小于10)
      打破;
    温度/ = 10;
    ++ sigfigs;
  }
  / *此时10 -10温度> = 0
  *递减温度单元1 1>温度> = 0 * /
  而(温度大于1)
  {
    --temp;
  }
  如果(临时== 0)
    返回(0);
  PL = 15 - sigfigs; / *如果n位小数离开,15-N的右* /
  开关(PL)
  {
  案例14:
    sprintf的(行,%.14lf,D);
    打破;
  案例13:
    sprintf的(行,%.13lf,D);
    打破;
  案例12:
    sprintf的(行,%.12lf,D);
    打破;
  案例11:
    sprintf的(行,%.11lf,D);
    打破;
  案例10:
    sprintf的(行,%.10lf,D);
    打破;
  案例9:
    sprintf的(行,%.9f,D);
    打破;
  案例8:
    sprintf的(行,%.8lf,D);
    打破;
  案例7:
    sprintf的(行,%.7lf,D);
    打破;
  情况6:
    sprintf的(行,%.6lf,D);
    打破;
  情况5:
    sprintf的(行,%.5lf,D);
    打破;
  情况4:
    sprintf的(行,%.4lf,D);
    打破;
  案例3:
    sprintf的(行,%.3lf,D);
    打破;
  案例2:
    sprintf的(行,%.2lf,D);
    打破;
  情况1:
    sprintf的(行,%.1lf,D);
    打破;
  情况下0:
    返回(0);
    打破;
  }
  I =(strlen的(线) - 1); / *最后一个有意义的数字字符* /
  而(1)/ *开始在字符串结尾,向左移动检查第一个非零* /
  {
    如果要是0年底*(行[I] =='0')/ * /
    {
       - 一世;
      --PL;
    }
    其他
    {
      打破;
    }
  }
  返回(PL);
}


解决方案

有可能是没有更简单的方法。这是一个相当复杂的问题。

您code未解决它的权利有以下几个原因:


  • 浮点运算是不是小数的大多数实际实现,它们是二进制。所以,当你乘以10一个浮点数或除以10,你可能会失去precision(这取决于具体的数字)。

  • 虽然标准 64位IEEE-754 浮点格式储量 53 位尾数,这相当于楼(log10的(2 ^ 53)) = 15 小数位数在此格式的有效数字最多可能需要一定的 1080 在完全相同打印时的小数部分,这是你似乎什么是十进制数字询问。

解决的方法之一是使用%在 A 格式类型说明符的snprintf(),这是要打印使用十六进制数字的尾数和1999年担保的C标准,这将打印所有显著的数字,如果浮点格式为基数-2(AKA基地-2或干脆二进制)的浮点值。所以,用这种可以获取数的尾数的所有二进制数字。从这里,你将能够找出数字多少小数都在小数部分。

现在,观察到:

1.00000 = 2 + 0 = 1.00000(二进制)结果
0.50000 = 2 1 = 0.10000结果
0.25000 = 2 -2 = 0.01000结果
0.12500 = 2 -3 = 0.00100结果
0.06250 = 2 -4 = 0.00010结果
0.03125 = 2 -5 = 0.00001

等。

您可以清楚地看到这里,在 I 的-th位置点的二进制重新$ P $权的二进制数字psentation生产的最后一个非零十进制数也的 I 的-th位置的点分十进制重新presentation的权利。

所以,如果你知道哪里有最显著非零位是一个二进制浮点数字,你可以计算出小数位数需要多少精确打印数的小数部分。

这是我的计划是这样做。

code:

  //文件:PrintFullFraction.c
//
//用gcc 4.6.2或更好的编译:
// GCC -Wall -std -Wextra C99 = -O2 PrintFullFraction.c -o PrintFullFraction.exe
#包括LT&;&limits.h中GT;
#包括LT&;&stdio.h中GT;
#包括LT&;&string.h中GT;
#包括LT&;&stdlib.h中GT;
#包括LT&;&math.h中GT;
#包括LT&;&FLOAT.H GT;
#包括LT&;&ASSERT.H GT;#如果FLT_RADIX!= 2
目前#ERROR仅支持FLT_RADIX = 2
#万一INT的fractionalDigits(双D)
{
  CHAR BUF [
           1 + //标志,' - '或'+'
           (sizeof的(D)* CHAR_BIT + 3)/ 4 + //尾数十六进制数字最多
           1 + //小数点。
           1 + //尾数-指数分离器,'P'
           1 + //尾数符号,' - '或'+'
           (sizeof的(D)* CHAR_BIT + 2)/ 3 + //指数小数位数最多
           1 //字符串结束,'\\ 0'
          ];
  INT N;
  字符*页,* P;
  INT E,lsbFound,lsbPos;  //转换成Ð+/- 0X h.hhhh p +/- DDD重presentation并检查错误
  如果((N = snprintf的(BUF,sizeof的(BUF),%+ A,d))的℃,||
      (符号)N> = sizeof的(BUF))
    返回-1;//输出({%S},BUF);  //确保转换并没有产生像男或INF
  //,而不是+/- 0X h.hhhh p +/- DDD
  如果(的strstr(BUF,0X)!= BUF + 1 ||
      (第=和strchr(BUF,'P'))== NULL)
    返回0;  //手动提取基2指数,检查溢出
  E = 0;
  p值= PP + 1 +(第[1] ==' - '||第[1] ==+); //先跳过指数符号
  为(;!* P ='\\ 0'; p ++)
  {
    如果(e基INT_MAX / 10)
      返回-2;
    E * = 10;
    如果(e基INT_MAX - (* P - '0'))
      返回-2;
    E + = * P - '0';
  }
  如果(第[1] ==' - ')//应用标志指数
    E = -e;//的printf([%S |%D]。BUF,E);  //找到最显著非零位的位置
  lsbFound = lsbPos = 0;
  为(P = PP - 1;!* P ='X'; P--)
  {
    如果(* P =='。')
      继续;
    如果(!lsbFound)
    {
      INT hdigit =(* P> ='A')? (* P - 'A'+ 10):(* P - '0'); //假设ASCII字符
      如果(hdigit)
      {
        静态const int的lsbPosInNibble [16] = {0,4,3,4,2,4,3,4,1,4,3,4,2,4,3,4};
        lsbFound = 1;
        lsbPos = -lsbPosInNibble [hdigit]
      }
    }
    其他
    {
      lsbPos - = 4;
    }
  }
  lsbPos + = 4;  如果(!lsbFound)
    返回0; // d为0(整数)  //调整至少显著非零位的位置
  //由基地-2指数(只需添加它们),检查
  //为溢出  如果(lsbPos> = 0&放大器;&安培; e基= 0)
    返回0; // lsbPos + e基= 0,d为整数  如果(lsbPos℃,与放大器;急症℃下)
    如果(lsbPos< INT_MIN - E)
      返回-2; // d不整,需要太多的小数位数  如果((lsbPos + = E)> = 0)
    返回0; // d为整数  如果(lsbPos == INT_MIN和放大器;&安培; -INT_MAX = INT_MIN!)
    返回-2; // d不整,需要太多的小数位数  返回-lsbPos;
}常量双TESTDATA [] =
{
  0,
  1,// 2 ^ 0
  0.5,// 2 ^ -1
  0.25,// 2 ^ -2
  0.125,
  0.0625 // ...
  0.03125,
  0.015625,
  0.0078125,// 2 ^ -7
  1.0 / 256,// 2 ^ -8
  1.0 / 256/256,// 2 ^ -16
  1.0 / 256/256/256,// 2 ^ -24
  1.0 / 256/256/256/256,// 2 ^ -32
  1.0 / 256/256/256/256/256/256/256/256,// 2 ^ -64
  3.14159265358979323846264338327950288419716939937510582097494459,
  0.1,
  无穷,
#IFDEF NAN
  NAN,
#万一
  DBL_MIN
};INT主要(无效)
{
  无符号我;
  对于(I = 0; I&下;的sizeof(TESTDATA)/的sizeof(TESTDATA [0]);我+ +)
  {
    INT位=的fractionalDigits(TESTDATA [I]);
    断言(数字&GT = 0);
    的printf(%F%E%*˚F\\ n,TESTDATA [I],TESTDATA [I],数字TESTDATA [I]);
  }
  返回0;
}

输出( ideone ):

  0.000000 0.000000e + 00 0
1.000000 1.000000e + 00 1
0.500000 5.000000e-01 0.5
0.250000 2.500000e-01 0.25
0.125000 1.250000e-01 0.125
0.062500 6.250000e-02 0.0625
0.031250 3.125000e-02 0.03125
0.015625 1.562500e-02 0.015625
0.007812 7.812500e-03 0.0078125
0.003906 3.906250e-03 0.00390625
0.000015 1.525879e-05 0.0000152587890625
0.000000 5.960464e-08 0.000000059604644775390625
0.000000 2.328306e-10 0.00000000023283064365386962890625
0.000000 5.421011e-20 0.0000000000000000000542101086242752217003726400434970855712890625
3.141593 3.141593e + 00 3.141592653589793115997963468544185161590576171875
0.100000 1.000000e-01 0.1000000000000000055511151231257827021181583404541015625
INF INF INF
南楠楠
0.000000 2.225074e-308 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201383090232717332404064219215980462331830553327416887204434813918195854283159012511020564067339731035811005152434161553460108856012385377718821130777993532002330479610147442583636071921565046942503734208375250806650616658158948720491179968591639648500635908770118304874799780887753749949451580451605050915399856582470818645113537935804992115981085766051992433352114352390148795699609591288891602992641511063466313393663477586513029371762047325631781485664350872122828637642044846811407613911477062801689853244110024161447421618567166150540154285084716752901903161322778896729707373123334086988983175067838846926092773977972858659654941091369095406136467568702398678315290680984617210924625396728515625

您可以看到, π 0.1 只有真正达到 15 十进制数字和数字显示什么号码自己都四舍五入到,因为这些数字其余的不能再$ p $二进制浮点格式完全相同psented。

您还可以看到, DBL_MIN ,最小的正归双击价值,有 1022 的小数部分,这些数字有 715 显著数字。

这种解决方案可能出现的问题:


  • 您的编译器的的printf()函数不支持%A 或不正确打印在被要求的所有数字precision(这是很可能的)。

  • 您的计算机使用非二进制浮点格式(这是非常罕见的)。

I'm new to C and learning out of a book / off the internet. I'm trying to write a function that I can pass any double to and get returned an int to be used in a printf("%.*lf" ... statement such that the int returned will neither diminish precision nor produce trailing zeros.

I have a working function but it is pretty big since it is written for readability and all commented up.

To summarize the function, I count how many divisions by 10 it takes to get the double in the range 10 > d >= 0, take only the fractional part and put it into a string with n decimal places where n = 15 - number_of_digits_left_of_decimal (I read that type double can only keep track of 15 digits), check the string from right to left for trailing zeroes and keep count, and, finally, return an int that represents the number of non-zero digits right of the decimal.

Is there an easier way? Thanks.

int get_number_of_digits_after_decimal(double d)
{
  int i = 0;      /* sometimes you need an int */
  int pl = 0;     /* precision left = 15 - sigfigs */
  int sigfigs = 1; /* the number of digits in d */
  char line[20];  /* used to find last non-zero digit right of the decimal place */
  double temp;    /* a copy of d used for destructive calculations */

  /* find digits to right of decimal */
  temp = d;
  while(sigfigs < 15)
  {
    if(temp < 0)
      temp *= -1;
    if(temp < 10)
      break;
    temp /= 10;
    ++sigfigs;
  }
  /* at this point 10 > temp >= 0
  * decrement temp unitl 1 > temp >=0 */
  while(temp > 1)
  {
    --temp;
  }
  if(temp == 0)
    return(0);
  pl = 15 - sigfigs;   /* if n digits left of decimal, 15-n to right */
  switch(pl)
  {
  case 14:
    sprintf(line, "%.14lf", d);
    break;
  case 13:
    sprintf(line, "%.13lf", d);
    break;
  case 12:
    sprintf(line, "%.12lf", d);
    break;
  case 11:
    sprintf(line, "%.11lf", d);
    break;
  case 10:
    sprintf(line, "%.10lf", d);
    break;
  case 9:
    sprintf(line, "%.9f", d);
    break;
  case 8:
    sprintf(line, "%.8lf", d);
    break;
  case 7:
    sprintf(line, "%.7lf", d);
    break;
  case 6:
    sprintf(line, "%.6lf", d);
    break;
  case 5:
    sprintf(line, "%.5lf", d);
    break;
  case 4:
    sprintf(line, "%.4lf", d);
    break;
  case 3:
    sprintf(line, "%.3lf", d);
    break;
  case 2:
    sprintf(line, "%.2lf", d);
    break;
  case 1:
    sprintf(line, "%.1lf", d);
    break;
  case 0:
    return(0);
    break;
  }
  i = (strlen(line) - 1); /* last meaningful digit char */
  while(1) /* start at end of string, move left checking for first non-zero */
  {
    if(line[i] == '0') /* if 0 at end */
    {
      --i;
      --pl;
    }
    else
    {
      break;
    }
  }
  return(pl);
}

解决方案

There's probably no easier way. It's a quite involved problem.

Your code isn't solving it right for several reasons:

  • Most practical implementations of floating-point arithmetic aren't decimal, they are binary. So, when you multiply a floating-point number by 10 or divide it by 10, you may lose precision (this depends on the number).
  • Even though the standard 64-bit IEEE-754 floating-point format reserves 53 bits for the mantissa, which is equivalent to floor(log10(2 ^ 53)) = 15 decimal digits, a valid number in this format may need up to some 1080 decimal digits in the fractional part when printed exactly, which is what you appear to be asking about.

One way of solving this is to use the %a format type specifier in snprintf(), which is going to print the floating-point value using hexadecimal digits for the mantissa and the C standard from 1999 guarantees that this will print all significant digits if the floating-point format is radix-2 (AKA base-2 or simply binary). So, with this you can obtain all the binary digits of the mantissa of the number. And from here you will be able to figure out how many decimal digits are in the fractional part.

Now, observe that:

1.00000 = 2+0 = 1.00000 (binary)
0.50000 = 2-1 = 0.10000
0.25000 = 2-2 = 0.01000
0.12500 = 2-3 = 0.00100
0.06250 = 2-4 = 0.00010
0.03125 = 2-5 = 0.00001

and so on.

You can clearly see here that a binary digit at i-th position to the right of the point in the binary representation produces the last non-zero decimal digit also in i-th position to the right of the point in the decimal representation.

So, if you know where the least significant non-zero bit is in a binary floating-point number, you can figure out how many decimal digits are needed to print the fractional part of the number exactly.

And this is what my program is doing.

Code:

// file: PrintFullFraction.c
//
// compile with gcc 4.6.2 or better:
//   gcc -Wall -Wextra -std=c99 -O2 PrintFullFraction.c -o PrintFullFraction.exe
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <assert.h>

#if FLT_RADIX != 2
#error currently supported only FLT_RADIX = 2
#endif

int FractionalDigits(double d)
{
  char buf[
           1 + // sign, '-' or '+'
           (sizeof(d) * CHAR_BIT + 3) / 4 + // mantissa hex digits max
           1 + // decimal point, '.'
           1 + // mantissa-exponent separator, 'p'
           1 + // mantissa sign, '-' or '+'
           (sizeof(d) * CHAR_BIT + 2) / 3 + // exponent decimal digits max
           1 // string terminator, '\0'
          ];
  int n;
  char *pp, *p;
  int e, lsbFound, lsbPos;

  // convert d into "+/- 0x h.hhhh p +/- ddd" representation and check for errors
  if ((n = snprintf(buf, sizeof(buf), "%+a", d)) < 0 ||
      (unsigned)n >= sizeof(buf))
    return -1;

//printf("{%s}", buf);

  // make sure the conversion didn't produce something like "nan" or "inf"
  // instead of "+/- 0x h.hhhh p +/- ddd"
  if (strstr(buf, "0x") != buf + 1 ||
      (pp = strchr(buf, 'p')) == NULL)
    return 0;

  // extract the base-2 exponent manually, checking for overflows
  e = 0;
  p = pp + 1 + (pp[1] == '-' || pp[1] == '+'); // skip the exponent sign at first
  for (; *p != '\0'; p++)
  {
    if (e > INT_MAX / 10)
      return -2;
    e *= 10;
    if (e > INT_MAX - (*p - '0'))
      return -2;
    e += *p - '0';
  }
  if (pp[1] == '-') // apply the sign to the exponent
    e = -e;

//printf("[%s|%d]", buf, e);

  // find the position of the least significant non-zero bit
  lsbFound = lsbPos = 0;
  for (p = pp - 1; *p != 'x'; p--)
  {
    if (*p == '.')
      continue;
    if (!lsbFound)
    {
      int hdigit = (*p >= 'a') ? (*p - 'a' + 10) : (*p - '0'); // assuming ASCII chars
      if (hdigit)
      {
        static const int lsbPosInNibble[16] = { 0,4,3,4,  2,4,3,4, 1,4,3,4, 2,4,3,4 };
        lsbFound = 1;
        lsbPos = -lsbPosInNibble[hdigit];
      }
    }
    else
    {
      lsbPos -= 4;
    }
  }
  lsbPos += 4;

  if (!lsbFound)
    return 0; // d is 0 (integer)

  // adjust the least significant non-zero bit position
  // by the base-2 exponent (just add them), checking
  // for overflows

  if (lsbPos >= 0 && e >= 0)
    return 0; // lsbPos + e >= 0, d is integer

  if (lsbPos < 0 && e < 0)
    if (lsbPos < INT_MIN - e)
      return -2; // d isn't integer and needs too many fractional digits

  if ((lsbPos += e) >= 0)
    return 0; // d is integer

  if (lsbPos == INT_MIN && -INT_MAX != INT_MIN)
    return -2; // d isn't integer and needs too many fractional digits

  return -lsbPos;
}

const double testData[] =
{
  0,
  1, // 2 ^ 0
  0.5, // 2 ^ -1
  0.25, // 2 ^ -2
  0.125,
  0.0625, // ...
  0.03125,
  0.015625,
  0.0078125, // 2 ^ -7
  1.0/256, // 2 ^ -8
  1.0/256/256, // 2 ^ -16
  1.0/256/256/256, // 2 ^ -24
  1.0/256/256/256/256, // 2 ^ -32
  1.0/256/256/256/256/256/256/256/256, // 2 ^ -64
  3.14159265358979323846264338327950288419716939937510582097494459,
  0.1,
  INFINITY,
#ifdef NAN
  NAN,
#endif
  DBL_MIN
};

int main(void)
{
  unsigned i;
  for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++)
  {
    int digits = FractionalDigits(testData[i]);
    assert(digits >= 0);
    printf("%f %e %.*f\n", testData[i], testData[i], digits, testData[i]);
  }
  return 0;
}

Output (ideone):

0.000000 0.000000e+00 0
1.000000 1.000000e+00 1
0.500000 5.000000e-01 0.5
0.250000 2.500000e-01 0.25
0.125000 1.250000e-01 0.125
0.062500 6.250000e-02 0.0625
0.031250 3.125000e-02 0.03125
0.015625 1.562500e-02 0.015625
0.007812 7.812500e-03 0.0078125
0.003906 3.906250e-03 0.00390625
0.000015 1.525879e-05 0.0000152587890625
0.000000 5.960464e-08 0.000000059604644775390625
0.000000 2.328306e-10 0.00000000023283064365386962890625
0.000000 5.421011e-20 0.0000000000000000000542101086242752217003726400434970855712890625
3.141593 3.141593e+00 3.141592653589793115997963468544185161590576171875
0.100000 1.000000e-01 0.1000000000000000055511151231257827021181583404541015625
inf inf inf
nan nan nan
0.000000 2.225074e-308 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201383090232717332404064219215980462331830553327416887204434813918195854283159012511020564067339731035811005152434161553460108856012385377718821130777993532002330479610147442583636071921565046942503734208375250806650616658158948720491179968591639648500635908770118304874799780887753749949451580451605050915399856582470818645113537935804992115981085766051992433352114352390148795699609591288891602992641511063466313393663477586513029371762047325631781485664350872122828637642044846811407613911477062801689853244110024161447421618567166150540154285084716752901903161322778896729707373123334086988983175067838846926092773977972858659654941091369095406136467568702398678315290680984617210924625396728515625

You can see that π and 0.1 are only true up to 15 decimal digits and the rest of the digits show what the numbers got really rounded to since these numbers cannot be represented exactly in a binary floating-point format.

You can also see that DBL_MIN, the smallest positive normalized double value, has 1022 digits in the fractional part and of those there are 715 significant digits.

Possible issues with this solution:

  • Your compiler's printf() functions do not support %a or do not correctly print all digits requested by the precision (this is quite possible).
  • Your computer uses non-binary floating-point formats (this is extremely rare).

这篇关于ç动态的printf双,没有precision的损失和没有尾随零的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆