如何检测浮点数在C#中是否有重复的十进制扩展?

我只需要知道如何在浮点数中检测重复的十进制扩展。

例:

0.123456789123456789

该数字的重复部分为123456789。

我想用C#自动化这个,有没有智能解决方案?

有一个很好的技巧来计算给定浮点数的有理逼近(基于欧几里德GCD算法的一些属性)。 我们可以用它来确定“最佳”近似是否为A/(2^a 5^b) ,如果是则则浮点数终止(在基数10中),否则它将具有一些重复成分。 棘手的位将确定哪个近似值是正确的(由于浮点精度问题)。

那么你是如何得到近似的理性表达式的。

第一次迭代x = 1/x - floor(1/x)跟踪int(x)

 x = 0.12341234 1/x = 8.102917 x <= 1/x - 8 = 0.102917 1/x = 9.7165 x <= 1/x - 9 = 0.71265277 1/x = 1.3956 x < 1/x - 1 = 0.3956 ... 

接下来将x的int部分粘贴到此表的顶行,将其称为k_i。 值A_i = A_{i-2} + k_i * A_{i-1}并且对于B_i是相同的。

  || 8 | 9 | 1 | 2 | 1 | 1 | 8 | 1 | 1 A = 1 0 || 1 | 9 | 10 | 29 | 39 | 68 | 583 | 651 | 1234 B = 0 1 || 8 | 73 | 81 | 235 | 316 | 551 | 4724 | 5275 | 9999 

然后,有理逼近是A_n/B_n

 1/8 = 0.12500000000000000 | e = 1.5e-3 9/73 = 0.12328767123287671 | e = 1.2e-4 10/81 = 0.12345679012345678 | e = 4.4e-5 29/235 = 0.12340425531914893 | e = 8.1e-6 39/316 = 0.12341772151898735 | e = 5.4e-6 68/551 = 0.12341197822141561 | e = 3.6e-7 583/4724 = 0.12341236240474174 | e = 2.2e-8 651/5275 = 0.12341232227488151 | e = 1.8e-8 1234/9999 = 0.12341234123412341 | e = 1.2e-9 

因此,如果我们在1234/9999阶段确定我们的误差足够低,我们注意到9999不能以2 ^ a 5 ^ b的forms写入,因此我们的十进制扩展是重复的。

请注意,虽然这似乎需要很多步骤,但如果我们使用x = 1/x - round(1/x) (并保持跟踪圆形(1 / x)),我们可以获得更快的收敛。 在那种情况下,表格变为

  8 10 -4 2 9 -2 1 0 1 10 -39 -68 -651 1234 0 1 8 81 -316 -551 -5275 9999 

这将以较少的步骤为您提供先前结果的子集。

值得注意的是,分数A_i / B_i总是使得A_i和B_i没有共同的因子,所以你不需要担心取消因子或类似的东西。

为了进行比较,我们来看看x = 0.123的扩展。 我们得到的表是:

  8 8 -3 -5 1 0 1 8 -23 123 0 1 8 65 -187 1000 

然后我们的近似序列是

  1/8 = 0.125 e = 2.0e-3 8/65 = 0.12307.. e = 7.6e-5 23/187 = 0.12299.. e = 5.3e-6 123/1000 = 0.123 e = 0 

我们看到123/1000正好是我们想要的分数,因为1000 = 10 ^ 3 = 2 ^ 3 5 ^ 3我们的分数正在终止。

如果你真的想知道分数的重复部分是什么(什么数字和什么时期)你需要做一些额外的技巧。 这包括分解分母并找到所有这些因子(除了2和5之外)的最小数字(10^k-1) ),然后k将是你的期间。 因此,对于我们的顶级案例,我们发现A = 9999 = 10 ^ 4-1(因此10 ^ 4-1包含A的所有因子 - 我们在这里很幸运...)所以重复部分的周期是4你可以在这里找到关于这个最后部分的更多细节。

不是这种算法的最后一个重要方面是它不要求所有数字都将十进制扩展标记为重复。 考虑x = 0.34482,这有表:

  3 -10 -156 1 0 1 -10 . 0 1 3 -29 . 

我们在第二个条目得到一个非常准确的近似值并在那里停止,得出结论我们的分数大概是10/29(因为它在1e-5内使用),并且从上面链接的表中我们可以看出它的周期将是28数字。 使用短号版本的字符串搜索永远无法确定这一点,这需要至少57位数字才能知道。

你不能像在你的例子中那样检测基数为10的表示,浮点数的精度是7位数。

http://msdn.microsoft.com/en-us/library/aa691146%28v=vs.71%29.aspx

您可以像这样隔离数字的小数(后期)部分:

 value - Math.Floor(value) 

如果使用双精度值“1.25”执行此操作,则最终会得到值“0.25”。 因此,你将把“部分权利”中的部分隔离开来。 当然,你将它作为0和1之间的双倍,而不是你的问题似乎需要的整数。

您的问题表明您需要“检测浮点数”。 如果您只需确定是否存在小数部分 ,则以下代码将近似起作用:

 value != Math.Floor(value) 

我个人会将它转换为String,在句点之后阻塞所有内容的子字符串,然后转换为您需要的数据类型。例如(自从我编写任何C#以来已经多年了,所以请原谅任何语法问题):

 float checkNumber = 8.1234567; String number = new String( checkNumber ); // If memory serves, this is completely valid int position = number.indexOf( "." ); // This could be number.search("."), I don't recall the exact method name off the top of my head if( position >= 0 ){ // Assuming search or index of gives a 0 based index and returns -1 if the substring is not found number = number.substring( position ); // Assuming this is the correct method name to retrieve a substring. int decimal = new Int( number ); // Again, if memory serves this is completely valid } 

你不能。

浮点具有有限的精度。 float类型的每个值都是整数倍2.0(X * 2 Y )的整数倍,其中X和Y是(可能是负数)整数)。 由于10是2的倍数,因此float类型的每个值都可以精确地表示为有限的十进制数字。

例如,虽然您可能期望1.0f/3.0f被表示为重复的十进制(或二进制)数,但实际上float只能保持数学值的近似值,一个不是重复的十进制数(除非您计算非零数字后面的重复0 )。 存储的值可能正好是0.3333333432674407958984375 ; 只有小数点后的前7位数才有意义。

这似乎有效:

 float checkNumber = 0.123456789; float result = checkNumber - (int)checkNumber; if (result > 0f) // number has a decimal point 

你基本上转换为一个整数来删除小数位..从浮点数减去(在这个例子中,0.123456789 – 0),如果结果大于0.0000000,它有一个小数位。 另一个例子,如果checkNumber是12.5,它将是:12.5 – 12 = 0.5,大于0.000000000。

但是,这不适用于舍入数字的小数位,例如:12.00。 但这个要求不在你的问题中。

我认为一般都没有解决方案(至少使用float / double ):

  • 期间可以长时间float (甚至double );
  • float / double是近似值。

例如,这是除法(double)1/(double)97

 0.010309278350515464 

实际上,它是一个重复的小数,在句点中有96个重复的数字。 如果在小数点后只有18位数,如何检测到这一点?

即使是decimal也没有足够的数字:

 0.0103092783505154639175257732