查找公共乘数以将十进制数转换为整数的算法

时间:2008-09-12 08:18:38

标签: algorithm math

我有一个数字数组,可能有多达8个小数位,我需要找到我可以乘以它们的最小公共数,以便它们都是整数。我需要这个所以所有的原始数字都可以乘以相同的比例并由一个只处理整数的密封系统处理,然后我可以检索结果并用公共乘数除以得到我的相对结果

目前我们对这些数字进行了一些检查并乘以100或1,000,000,但是*密封系统完成的处理在处理大量数据时可能会非常昂贵,因此只需要将所有内容乘以一百万真的是一个很好的选择。可以说,每次乘以10倍时,密封算法的成本会高出10倍。

什么是最有效的算法,也能提供最好的结果,以实现我的需要,是否有我需要的数学名称和/或公式?

*密封系统没有真正密封。我拥有/维护它的源代码,但它拥有100,000多条专有魔法线,它已经彻底的bug和性能测试,改变它以处理浮动不是一个选择,原因很多。它是一个创建X×Y单元网格的系统,然后X by Y的网格被放入网格中,“专有魔法”发生并且结果被吐出 - 显然这是现实的极简化版本,但它是一个足够好的近似值。

到目前为止,有一些很好的答案,我想知道如何选择'正确'的答案。首先,我认为唯一公平的方法是创建每个解决方案并对其进行性能测试,但后来我意识到纯粹的速度并不是唯一的相关因素 - 更准确的解决方案也非常相关。无论如何我写了性能测试,但目前我正在使用“肠道感觉”公式,根据速度和准确度选择正确的答案。

我的性能测试处理1000组不同的100个随机生成的数字。 使用相同的随机数集测试每个算法。 算法用.Net 3.5编写(虽然到目前为止兼容2.0) 我努力使测试尽可能公平。

  • 格雷格 - 乘以大数字 然后除以GCD - 63 毫秒
  • Andy - String Parsing - 199毫秒
  • Eric - Decimal.GetBits - 160毫秒
  • Eric - 二进制搜索 - 32 毫秒
  • 我 - 抱歉,我不能 弄清楚如何实现你的 很容易在.Net中解决(我没有 想花太多时间))
  • 比尔 - 我认为你的答案非常好 接近格雷格所以没有实施 它。我敢肯定它会更快 但可能不太准确。

所以格雷格的乘数乘以大数然后除以GCD“解决方案是第二快的算法,它给出了最准确的结果,所以现在我称之为正确。

我真的希望Decimal.GetBits解决方案最快,但速度非常慢,我不确定这是由于Double转换为Decimal还是Bit掩码和转换。应该有一个 使用BitConverter.GetBytes以及此处包含的一些知识的直接Double的类似可用解决方案:http://blogs.msdn.com/bclteam/archive/2007/05/29/bcl-refresher-floating-point-types-the-good-the-bad-and-the-ugly-inbar-gazit-matthew-greig.aspx但是每次我阅读该文章时我的眼睛都会一直亮着,我最终没时间尝试实现解决方案。

如果有人能想到更好的东西,我总是愿意接受其他解决方案。

7 个答案:

答案 0 :(得分:6)

我会乘以足够大的东西(小数点后8位为100,000,000),然后除以结果数的GCD。你最终会得到一堆最小的整数,你可以提供给另一个算法。获得结果后,请反转该过程以恢复原始范围。

答案 1 :(得分:1)

如果你想找到一个整数N,这样N * x也是一组浮点数的精确整数x在给定的集合中都是整数,那么你就有一个基本上无法解决的问题。假设x =你的类型可以代表的最小正浮点数,比如它是10 ^ -30。如果你将所有数字乘以10 ^ 30,然后尝试用二进制表示它们(否则,你为什么要努力使它们成为整数?),那么你将基本上丢失所有其他数字的信息溢出。

所以这里有两个建议:

  1. 如果您可以控制所有相关代码,请找到另一个代码 做法。例如,如果你有一些只需要的功能 int's,但你有漂浮物,你想把你的花车塞进去 该函数,只需重写或重载此函数即可接受 也漂浮着。
  2. 如果您无法控制所需的系统部分 int,然后选择你关心的精度,接受它 你有时会丢失一些信息(但它会 在某种意义上总是“小”,然后只是乘以你的所有 按常量浮动,并舍入到最接近的整数。
  3. 顺便说一句,如果你处理分数而不是浮动分数,那么它就是一个不同的游戏。如果你有一堆分数a / b,c / d,e / f;并且你想要一个最小公倍乘数N,使得N *(每个分数)=一个整数,然后N = a b c / gcd(a,b,c);和gcd(a,b,c)= gcd(a,gcd(b,c))。您可以使用Euclid's algorithm查找任意两个数字的gcd。

答案 2 :(得分:0)

你在编写什么语言?像

这样的东西
myNumber.ToString().Substring(myNumber.ToString().IndexOf(".")+1).Length

会给你C#中double的小数位数。您可以通过它运行每个数字并找到最大小数位数(x),然后将每个数字乘以10得到x的幂。

编辑:出于好奇,这个密封系统是什么,你只能将整数传递给它?

答案 3 :(得分:0)

Greg:很好的解决方案,但是不会计算出100多个数字中常见的GCD会有点贵吗?你会怎么做?很容易为两个数字做GCD但是100它变得更复杂(我认为)。

Evil Andy:我正在使用.Net进行编程,你所提出的解决方案几乎与我们现在所做的相匹配。我不想把它包含在我原来的问题中,因为我希望在盒子外面(或者我的盒子)思考,我不想用可能的解决方案来玷污人们的答案。虽然我没有任何可靠的性能统计数据(因为我没有任何其他方法来比较它)我知道字符串解析会相对昂贵,我认为纯粹的数学解决方案可能更有效。 公平地说,当前的字符串解析解决方案正在生产中,并且没有关于其性能的抱怨(即使在VB6格式的单独系统中生产也没有抱怨)。只是它感觉不对,我想它会冒犯我的编程敏感性 - 但它可能是最好的解决方案。

那说我仍然对任何其他解决方案持开放态度,纯粹是数学或其他方式。

答案 4 :(得分:0)

在循环中获取尾数并将每个数字的指数表示为整数。你可以使用frexp作为指数,但我认为尾数需要位掩码。找到最小指数。查找尾数中的最高有效位(循环查找最后“1”的位) - 或者只使用预定义的有效位数。 你的倍数就像2 ^(numberOfDigits-minMantissa)。 “喜欢的东西”,因为我不记得偏差/偏移/范围,但我认为这个想法很清楚。

答案 5 :(得分:0)

所以基本上你想确定每个数字小数点后的位数。

如果你有数字的二进制表示,这将更容易。这些数字是否会在您的计划中更早地从理性或科学记数转换而来?如果是这样,您可以跳过先前的转换并更容易。否则,您可能希望将每个数字传递给用C编写的外部DLL中的函数,您可以直接使用浮点表示。或者您可以将数字转换为十进制数,并使用Decimal.GetBits进行处理。

我能够在现场考虑并遵循您的条件的最快方法是找到之前建议的最小必要的10次幂(或2或其他)。但是不是在循环中进行,而是通过对可能的幂进行二进制搜索来节省一些计算。假设最多8个,例如:

int NumDecimals( double d )
{
   // make d positive for clarity; it won't change the result
   if( d<0 ) d=-d;

   // now do binary search on the possible numbers of post-decimal digits to 
   // determine the actual number as quickly as possible:

   if( NeedsMore( d, 10e4 ) )
   {
      // more than 4 decimals
      if( NeedsMore( d, 10e6 ) )
      {
          // > 6 decimal places
          if( NeedsMore( d, 10e7 ) ) return 10e8;
          return 10e7;
      }
      else
      {
         // <= 6 decimal places
         if( NeedsMore( d, 10e5 ) ) return 10e6;
         return 10e5;
      }
   }
   else
   {
      // <= 4 decimal places
      // etc...
   }

}

bool NeedsMore( double d, double e )
{
   // check whether the representation of D has more decimal points than the 
   // power of 10 represented in e.
   return (d*e - Math.Floor( d*e )) > 0;
}

PS:您不会将安全价格传递给期权定价引擎吗?它有完全的味道......

答案 6 :(得分:0)

  1. 将所有数字乘以10 直到你有整数。
  2. 鸿沟 2,3,5,7,而你仍然拥有所有 整数。
  3. 我认为这涵盖了所有情况。

    2.1 * 10/7 -> 3
    0.008 * 10^3/2^3 -> 1
    

    假设你的乘数可以是一个合理的分数。