C / C ++计算小数位数?

时间:2009-07-05 01:30:00

标签: c++ c decimal

假设用户输入的是十进制数,例如。 5. 2155 (有4位小数)。它可以自由存储(int,double)等。

是否有聪明(或非常简单)的方法来查明该数字的小数位数? (有点像问题,如何通过屏蔽最后一位来找到数字是偶数还是奇数)。

15 个答案:

答案 0 :(得分:18)

我知道的两种方式,不幸的是不是很聪明,但这更像是对环境的限制而不是我: - )

第一个是将sprintf数字转换为带有"%.50f"格式字符串的大缓冲区,去掉尾随的零,然后计算小数点后的字符。这将受printf家庭本身的限制。或者您可以使用该字符串作为用户输入(而不是sprintf浮点值),以避免浮点问题。

第二个是减去整数部分然后迭代乘以10并再次减去整数部分直到你得到零。这受到浮点数的计算机表示限制的限制 - 在每个阶段你可能会得到一个无法准确表示的数字的问题(所以.2155实际上可能是.215499999998)。像下面这样的东西(未经测试,除了在我脑海中,与COMX-35相当):

count = 0
num = abs(num)
num = num - int(num)
while num != 0:
    num = num * 10
    count = count + 1
    num = num - int(num)

如果您知道您将获得的数字类型(例如,它们都是小数点后的0到4位数),您可以使用标准浮点“技巧”来正确执行。例如,而不是:

while num != 0:

使用

while abs(num) >= 0.0000001:

答案 1 :(得分:6)

一旦将数字从用户表示(字符串,OCR-ed gif文件等)转换为浮点数,您就不必处理相同的数字。所以严格的,不是非常有用的答案是“不”。

如果(案例A )您可以避免从字符串表示转换数字,问题就变得容易了,您只需要计算小数点后的数字并减去尾随零的数量

如果你不能这样做(案例B ),那么你需要假设最大小数位数,将数字转换回字符串表示形式并将其四舍五入到这个最大数字。 round-to-even method。例如,如果用户提供的1.1表示为1.09999999999999(假设),将其转换回字符串,则猜测“1.09999999999999”。将这个数字四舍五入到比如四个小数点可以得到“1.1000”。现在回到案例A

答案 2 :(得分:4)

脱离我的头顶:

从小数部分开始:.2155

重复乘以10并丢弃数字的整数部分直到你得到零。步数将是小数位数。 e.g:

.2155 * 10 = 2.155
.155 * 10 = 1.55
.55 * 10 = 5.5
.5 * 10 = 5.0

4步= 4位小数

答案 3 :(得分:2)

你是什么意思“自由存储(int”?一旦存储在int中,它剩下零小数,显然。一个double以二进制形式存储,因此与“decimal”没有明显或简单的关系。为什么在将输入发送到最终的数字变量目的地之前,不要将输入保持为字符串,只要足够长以计算小数位数吗?

答案 4 :(得分:2)

这样的事情也可能有效:

float i = 5.2154;
std::string s;
std::string t;
std::stringstream out;
out << i;
s = out.str();

t = s.substr(s.find(".")+1);
cout<<"number of decimal places: " << t.length();

答案 5 :(得分:1)

使用科学记数法格式(以避免舍入错误):

#include <stdio.h>
#include <string.h>

/* Counting the number of decimals
 *
 * 1. Use Scientific Notation format
 * 2. Convert it to a string
 * 3. Tokenize it on the exp sign, discard the base part
 * 4. convert the second token back to number
*/

int main(){

   int counts;
   char *sign;
   char str[15];
   char *base;
   char *exp10;
   float real = 0.00001;

   sprintf (str, "%E",  real);
   sign= ( strpbrk ( str, "+"))? "+" : "-";

   base = strtok (str, sign);
   exp10 = strtok (NULL, sign);

   counts=atoi(exp10);

   printf("[%d]\n", counts);

   return 0;
}

[5]

答案 6 :(得分:1)

战斗结束后的几年,但我已经用三条线做出了自己的解决方案:

<button type="button" class="btn btn-default" data-toggle="tooltip" 
 data-placement="left" title="Tooltip on left">Tooltip on left</button>

当然,你必须先测试它是否真的是浮动的 (例如string number = "543.014"; size_t dotFound; stoi(number, &dotFound)); string(number).substr(dotFound).size()

答案 7 :(得分:1)

由于目标是要快,所以这是对andrei alexandrescu's improvement的改进。他的版本已经比幼稚的方式快(每位数除以10)。下面的版本是恒定时间的,至少在所有尺寸的x86-64和ARM上都更快,但是占用的二进制代码数量是后者的两倍,因此它对缓存的友好程度不高。

我的PR on facebook folly上的该版本与亚历山大版本的基准。

unsigned上工作,而不在signed上工作。

inline uint32_t digits10(uint64_t v) {
  return  1
        + (std::uint32_t)(v>=10)
        + (std::uint32_t)(v>=100)
        + (std::uint32_t)(v>=1000)
        + (std::uint32_t)(v>=10000)
        + (std::uint32_t)(v>=100000)
        + (std::uint32_t)(v>=1000000)
        + (std::uint32_t)(v>=10000000)
        + (std::uint32_t)(v>=100000000)
        + (std::uint32_t)(v>=1000000000)
        + (std::uint32_t)(v>=10000000000ull)
        + (std::uint32_t)(v>=100000000000ull)
        + (std::uint32_t)(v>=1000000000000ull)
        + (std::uint32_t)(v>=10000000000000ull)
        + (std::uint32_t)(v>=100000000000000ull)
        + (std::uint32_t)(v>=1000000000000000ull)
        + (std::uint32_t)(v>=10000000000000000ull)
        + (std::uint32_t)(v>=100000000000000000ull)
        + (std::uint32_t)(v>=1000000000000000000ull)
        + (std::uint32_t)(v>=10000000000000000000ull);
}

答案 8 :(得分:0)

我建议将值作为字符串读取,搜索小数点,并将其前后的文本解析为整数。没有浮点或舍入错误。

答案 9 :(得分:0)

char* fractpart(double f)
 {
    int intary={1,2,3,4,5,6,7,8,9,0};
    char charary={'1','2','3','4','5','6','7','8','9','0'};
    int count=0,x,y;
    f=f-(int)f;
           while(f<=1)
           {
                     f=f*10;
                     for(y=0;y<10;y++)
                         {
                                if((int)f==intary[y])
                                 {
                                           chrstr[count]=charary[y];
                                           break;
                                 }
                         }
    f=f-(int)f;
    if(f<=0.01 || count==4)
   break;
   if(f<0)
   f=-f;
   count++;
    }
     return(chrstr);
    }

答案 10 :(得分:0)

这是完整的程序

 #include <iostream.h>
 #include <conio.h>
 #include <string.h>
 #include <math.h>

     char charary[10]={'1','2','3','4','5','6','7','8','9','0'};
     int intary[10]={1,2,3,4,5,6,7,8,9,0};

     char* intpart(double);
     char* fractpart(double);

      int main()
      {
      clrscr();
      int count = 0;
      double d = 0;
      char intstr[10], fractstr[10];
     cout<<"Enter a number";
     cin>>d;
     strcpy(intstr,intpart(d));
     strcpy(fractstr,fractpart(d));
     cout<<intstr<<'.'<<fractstr;
     getche();
    return(0);
   }

    char* intpart(double f)
    {
       char retstr[10];
       int x,y,z,count1=0;
       x=(int)f;
            while(x>=1)
            {
               z=x%10;
               for(y=0;y<10;y++)
                {
                 if(z==intary[y])
                 {
                 chrstr[count1]=charary[y];
                 break;
                 }
              }
             x=x/10;
            count1++;
            }
           for(x=0,y=strlen(chrstr)-1;y>=0;y--,x++)
            retstr[x]=chrstr[y];
            retstr[x]='\0';
            return(retstr);
     }

       char* fractpart(double f)
      {
      int count=0,x,y;
      f=f-(int)f;
        while(f<=1)
        {
          f=f*10;
             for(y=0;y<10;y++)
             {
                   if((int)f==intary[y])
                   {
                        chrstr[count]=charary[y];
                        break;
                   }
            }
             f=f-(int)f;
             if(f<=0.01 || count==4)
             break;
             if(f<0)
               f=-f;
            count++;
         }
         return(chrstr); 
 }

答案 11 :(得分:0)

这是适用于floatdouble类型的健壮的C ++ 11实现:

#include <type_traits>
#include <cstdlib>
#include <cmath>

template <typename T>
std::enable_if_t<(std::is_floating_point<T>::value), std::size_t>
decimal_places(T v)
{
    std::size_t count = 0;

    v = std::abs(v);

    auto c = v - std::floor(v);

    T factor = 10;

    while (c > 0 && count < std::numeric_limits<T>::max_digits10)
    {
        c = v * factor;

        c = c - std::floor(c);

        factor *= 10;

        count++;
    }

    return count;
}

它会在每次迭代时丢弃该值,而是跟踪10乘方的幂,以避免出现四舍五入的问题。

答案 12 :(得分:0)

{'access_token': '1234',
 'expires_in': 28800,
 'refresh_token': '5678',
 'scope': ['heartrate',
  'profile',
  'settings',
  'nutrition',
  'location',
  'weight',
  'activity',
  'sleep',
  'social'],
 'token_type': 'Bearer',
 'user_id': 'abcd',
 'expires_at': 1611455442.4566112}

答案 13 :(得分:0)

根据其他人写的内容,这对我来说效果很好。此解决方案确实处理无法以二进制精确表示数字的情况。

正如其他人所建议的,while 循环的条件指示了精确的行为。我的更新使用机器 epsilon 值来测试任何循环的余数是否可以用数字格式表示。测试不应与 0 或硬编码值(如 0.000001)进行比较。

template<class T, std::enable_if_t<std::is_floating_point_v<T>, T>* = nullptr>
unsigned int NumDecimalPlaces(T val)
{
    unsigned int decimalPlaces = 0;
    val = std::abs(val);
    val = val - std::round(val);
    while (
        val - std::numeric_limits<T>::epsilon() > std::numeric_limits<T>::epsilon() && 
        decimalPlaces <= std::numeric_limits<T>::digits10)
    {
        std::cout << val << ", ";

        val = val * 10;
        ++decimalPlaces;
        val = val - std::round(val);
    }
    return val;
}

举个例子,如果输入值为 2.1,正确的解是 1。但是,如果使用双精度,这里发布的一些其他答案会输出 16,因为 2.1 不能用双精度精确表示。

答案 14 :(得分:-1)

一种方法是以字符串形式读取数字。找到小数点后子字符串的长度,即该人输入的小数位数。使用

将此字符串转换为float

atof(string.c_str());

另一方面;处理浮点运算以将它们存储在具有有限精度的特殊对象中时,总是一个好主意。例如,您可以将浮点存储在名为“Decimal”的特殊类型的对象中,其中整数部分和数字的小数部分都是整数。这样你就具有有限的精度。这样做的缺点是你必须写出算术运算的方法(+, - ,*,/等),但你可以很容易地用C ++覆盖运算符。我知道这偏离了你原来的问题,但最好以有限的形式存储你的小数。通过这种方式,您还可以回答您的问题,即该数字的小数位数。