假设来自用户的输入是十进制数,例如。5. 2155(有 4 个十进制数字)。它可以自由存储(int,double)等。
是否有任何聪明(或非常简单)的方法来找出数字有多少位小数?(有点像你如何通过屏蔽最后一位来发现一个数字是偶数还是奇数的问题)。
我知道的两种方法,不幸的是都不是很聪明,但这更多是环境的限制而不是我:-)
第一个是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:
一旦数字从用户表示(字符串、OCR 编辑的 gif 文件等)转换为浮点数,您不必处理相同的数字。所以严格的,不是很有用的答案是“不”。
如果(case A)可以避免从字符串表示中转换数字,问题就变得容易得多,您只需要计算小数点后的数字并减去尾随零的数量。
如果你做不到(案例 B),那么你需要对最大小数位数做出假设,将数字转换回字符串表示形式,并使用四舍五入方法将其四舍五入到这个最大数字。例如,如果用户提供 1.1,它表示为 1.09999999999999(假设),将其转换回字符串产生,猜猜看,“1.09999999999999”。将这个数字四舍五入到小数点后四位会给你“1.1000”。现在回到案例 A。
在我的头顶上:
从小数部分开始:.2155
反复乘以 10 并丢弃数字的整数部分,直到得到零。步数将是小数位数。例如:
.2155 * 10 = 2.155
.155 * 10 = 1.55
.55 * 10 = 5.5
.5 * 10 = 5.0
4 步 = 4 个十进制数字
像这样的东西也可能起作用:
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();
你是什么意思“自由存储(int”)?一旦存储在一个int中,它就剩下零个小数了,很明显。双精度数以二进制形式存储,因此与“小数”也没有明显或简单的关系。为什么不在将输入发送到最终的数字变量目的地之前,您将输入保留为字符串,长度足以计算这些小数吗?
使用科学记数法格式(避免舍入错误):
#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]
如果您的数字的小数部分存储在单独的int
中,您可以只计算其小数位数。
这是对andrei alexandrescu 的改进。他的版本已经比天真的方法(每个数字除以 10)更快。下面的版本是恒定的时间并且至少在 x86-64 和 ARM 上对于所有大小都更快,但占用的二进制代码是两倍,因此它对缓存不友好。
此版本的基准与我在 Facebook 愚蠢的 PR 上的 alexandrescu 版本。
工作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);
}
战斗多年后,但正如我在三行中提出了自己的解决方案:
string number = "543.014";
size_t dotFound;
stoi(number, &dotFound));
string(number).substr(dotFound).size()
当然你必须先测试它是否真的是一个浮点数(stof(number) == stoi(number)
例如)
这是一个健壮的 C++ 11 实现,适用于float
和double
类型:
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;
T eps = std::numeric_limits<T>::epsilon() * c;
while ((c > eps && c < (1 - eps)) && count < std::numeric_limits<T>::max_digits10)
{
c = v * factor;
c = c - std::floor(c);
factor *= 10;
eps = std::numeric_limits<T>::epsilon() * v * factor;
count++;
}
return count;
}
它会在每次迭代中丢弃该值,而是跟踪 10 乘数的幂,以避免出现舍入问题。它使用机器 epsilon 正确处理无法以二进制精确表示的十进制数,例如问题中规定的 5.2155 的值。
int main()
{
char s[100];
fgets(s,100,stdin);
unsigned i=0,sw=0,k=0,l=0,ok=0;
unsigned length=strlen(s);
for(i=0;i<length;i++)
{
if(isprint(s[i]))
{
if(sw==1)
{
k++;
if(s[i]=='0')
{
ok=0;
}
if(ok==0)
{
if(s[i]=='0')
l++;
else
{
ok=1;
l=0;
}
}
}
if(s[i]=='.')
{
sw=1;
}
}
}
printf("%d",k-l);
return 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 不能以双精度精确表示。
我建议将该值作为字符串读取,搜索小数点,并将其前后的文本解析为整数。没有浮点或舍入错误。
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);
}
这是完整的程序
#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);
}
一种方法是将数字作为字符串读取。找到小数点后子串的长度,这就是该人输入的小数位数。通过使用将此字符串转换为浮点数
atof(string.c_str());
换一种说法;在处理浮点运算时,将它们存储在具有有限精度的特殊对象中总是一个好主意。例如,您可以将浮点数存储在称为“十进制”的特殊类型的对象中,其中整数部分和数字的小数部分都是整数。这样你就有一个有限的精度。这样做的缺点是您必须写出算术运算的方法(+、-、*、/ 等),但您可以轻松地覆盖 C++ 中的运算符。我知道这与您最初的问题有所不同,但是以有限的形式存储小数总是更好。通过这种方式,您还可以回答有关数字有多少位小数的问题。