【问题标题】:Determine if a floating point number is a multiple of another floating point number?确定一个浮点数是否是另一个浮点数的倍数?
【发布时间】:2012-09-07 21:42:34
【问题描述】:

我需要使用 JavaScript 确定一个浮点数是否是另一个浮点数的倍数。

我已经阅读了一些关于浮点数的其他问题,并且了解到它们不能与模运算符 (%) 一起正常工作。我还读到您可以通过乘以 10/100/1000 等将浮点数转换为整数,但这并不是在所有情况下都能正常工作。

例子:

var A = 25.13;
var B = .0001;

var Value = A*1e5;
var Step  = B*1e5;

// Is Value a multiple of Step?
if(0 === (Value % Step)) {
// Do something
}

在这种情况下,Value 是 Step 的倍数,它可以正常工作。但是呢:

var A = 2.2;
var B = .0001;

它应该是一个有效的倍数,但我们得到的是:

220000.00000000003 % 10 = 2.9103830456733704e-11

第 11 位小数点有一个错误的 3。我想我可以通过以下方式纠正使用toFixed() 舍入的问题:

var Value = (A*1e5).toFixed(10);
var Step  = (B*1e5).toFixed(10);

但如果你这样做:

var A = 45436212356482;
var B = .0001;

你得到:

4543621235648200192.0000000000 % 10.0000000000=2

这是一个有效的倍数,但它认为不是。

与:

var A = 45436212546522156.45621565421;
var B = .0001;

这不是一个有效的倍数,但它认为它是:

4.543621254652216e+21 % 10.0000000000=0

确定一个浮点数是否是另一个浮点数的倍数是否有一个聪明的技巧?或者这是不可能的?

更新:

目标是将用户输入的数字(整数或小数)限制为一定的增量。

  • 如果Increment为1,用户可以输入1,2,3,4等。
  • 如果增量为 0.5,用户可以输入 0.5,1,1.5,2,2.5 等。
  • 如果增量为 .0002,用户可以输入 1,1.001,1.0004,1.0006,但不能输入 1.0001

从逻辑角度来看,给定值是或不是给定增量的有效倍数。

【问题讨论】:

  • 你对“倍数”的容忍度是多少?
  • 为什么不直接将数字相除,然后检查您是否可以接受接近整数?还需要注意的是,这不是 JavaScript 的限制,而是二进制数表示的限制。
  • 相等比较和浮点数不能一起使用,尤其是 ieee(尤其是零)。您需要查看结果是否在有限范围内,例如 fabs(result)
  • 根本不要使用浮点数。它会在 Smalltalk 中(最新的 Squeak)吗?我会说它就像 ^((Fraction readFrom: '1.0006') / (Fraction readFrom: '0.0002')) fractionPart = 0 一样简单。

标签: javascript floating-point


【解决方案1】:

鉴于您的最后两个示例,例如 4543621235648200192 等,您似乎希望接受 .0001 的整数倍数并拒绝那些不是并且您不想使用 . 0001 但使用包含最接近 .0001 的浮点值的变量。

一般来说,这是不可能的。当您传递其他东西时,算法无法“知道” .0001 的意图。

如果您对问题进行更多限制,则可能会有解决方案。例如,有可能(也许不容易)回答这个问题:浮点值 X 是最接近 0.0001 的整数倍的浮点值吗? (换句话说,是否有一个整数 k 使得 .0001 乘以 k 并四舍五入到最接近的浮点值正好产生 X?)

因此,要获得解决方案,您需要进一步描述您的目标。您是否想要 Step 的任意值或仅某些值的解决方案?由于二进制浮点不能足够精确地表示 Step,您有其他方式来描述它吗?例如,它是否总是 0.0001 的倍数?您希望作为倍数接受的值是否始终是最接近精确数学倍数的二进制浮点数,还是可能有其他错误?

【讨论】:

  • 我已经更新了问题。倍数的值将由人明确设置(不是计算结果)。
  • @Nick:我不认为你已经掌握了这个问题。您的更新表明步长将位于名为Increment 的变量中。但是,当它处于浮点值时,它不是 .0002 或您想要的其他值。这是不同的东西。你已经输掉了这场战斗;原来的值没了,不能解决问题。如果您想使用 .0002 之类的值作为步骤,那么您必须使用直接二进制浮点值以外的其他值来表示它们。例如,您可以使用浮点但添加断言步骤本身是 .0001 的倍数。
  • @EricPostpischil +1,在浮点数中几乎是不可能的,尽管在 Pharo Smalltalk 中我添加了一个实用程序来将浮点数转换为最小的十进制分数,该分数将被舍入到相同的浮点数示例:0.013 asMinimalDecimalFraction -> (13/1000) 参见 pharo issue #4957 code.google.com/p/pharo/issues/detail?id=4957 的讨论
【解决方案2】:

基本问题是 32 位浮点数不能正确表示所有实数。例如,如果用户输入 0.1,则浮点数的值为 0.099999994。

因此,如果您有 0.1 的增量,您无法判断他是否输入了 0.1(这将是有效的),或者他是否输入了 0.09999..(这将是无效的)。

我的建议是改用整数数据类型并将其视为定点数。这样您就不会失去精度,并且可以轻松地检查多重性。

【讨论】:

    【解决方案3】:

    如果我错了,请纠正我,但解决方案如下:

    function IsMultipleOf(a, b) {
       var result = a % b;
       return (result < 1e-3);
    }
    

    只回答了一半的问题。假设如下(使用python代码):

    dt = 2.4
    >>> dt = 2.2999999999999998
    t = 13 * dt
    >>> t = 29.899999999999999
    t % dt
    >>> 8.8817841970012523e-16
    

    在这种情况下,它会正常工作。现在假设:

    dt = 1.4
    >>> dt = 1.3999999999999999
    t = 3 * dt
    >>> t = 4.1999999999999993
    t % dt
    >>> 1.3999999999999995
    

    由于舍入误差使得 t 低于 dt 的下一个倍数, 模的值更接近 dt 而不是 0。

    解决此问题的一种可能方法是检查两种情况:

    modt = t % dt
    (abs(modt) <= tolerance) or (abs(dt - modt) <= tolerance)
    

    我期望tolerance = machine epsilon * abs(dt) / 2based on this answer, 但是模运算以某种方式引入了更多的错误。 它看起来像:

    tolerance = machine epsilon * max(abs(t), abs(dt)) / 2
    

    处理这项工作,但这只是一个猜测。或者,

    tolerance = 2 * machine epsilon * abs(dt)
    

    似乎也可以正常工作。

    【讨论】:

      【解决方案4】:

      以下适用于所有小数。

      var result = Math.round( Math.round(number1 * 100000) % Math.round(number2 * 100000) ) / 100000;
      

      【讨论】:

        【解决方案5】:

        这是您可以做到的一种方法:

        function floatingPointAMultipleOfB(a, b) {
          const precision_a = getNumbersAfterDecimal(a)
          const precision_b = getNumbersAfterDecimal(b)
        
          if (precision_a > precision_b) return false;
        
          const int_a = Math.round(multBy10(a, precision_b))
          const int_b = Math.round(multBy10(b, precision_b))
          return int_a % int_b === 0
        }
        
        
        function getNumbersAfterDecimal(n) {
          const exponential = n.toString().split('e');
          if (exponential.length === 2) n = n.toFixed(Math.abs(exponential[1]))
          return (n.toString().split('.')[1] || []).length;
        }
        
        function multBy10(val, n) {
          if (n === 0) return val
          return multBy10(val, n-1) * 10
        }
        

        【讨论】:

          【解决方案6】:

          由于您正在处理浮点数,因此最好的办法是确定关闭的“接近”程度,并使用它:

          function IsMultipleOf(a, b) {
             var result = a % b;
             return (result < 1e-3);
          }
          

          【讨论】:

          • 这几乎可以工作,但接受1.12345(例如)作为.0001的倍数。 .0001 的有效倍数不能超过 4 位小数。
          • 这需要更严格的约束(例如 1e-5)。如果绝对精度是要求/目标,那么浮点数学是徒劳的。
          • 这也不起作用,因为例如。 (.7 + .1) % .1 == 0.09999。 (而不是上面代码所期望的0.00001)编辑:哦,这是同样的问题cvr mentions below
          【解决方案7】:

          我需要使用 JavaScript 确定一个浮点数是否是另一个浮点数的倍数。

          如果我们假设浮点数用于逼近实数,那么根据定义,每个浮点数都是另一个数的倍数。当然,由于浮点数实际上是有理数的子集,所以当然有很多对浮点数没有共同的浮点除数。例如,尾数中具有不同素数系数的任何两个浮点数都没有公约数。指数的范围进一步限制了精确倍数的对。

          但也许您不是在寻找具有任意乘法因子的数字,而是寻找具有整数除数的数字,并测试结果是否低于所选阈值(通常称为 epsilon)。

          例如函数式伪代码!

          fmod :: float -> float -> float
          fmod a b =
              b - a * floor( b / a )
          
          EPSILON = 1e-5;
          divides_to_integer :: float -> float -> boolean
          divides_to_integer a b =
              fmod(a, b) < EPSILON
          

          fmod 函数应取自 JavaScript 数学库。

          【讨论】:

          • 不过,你离编程语言还有很长的路要走
          • @Alexander:可能:自从我上次使用 JavaScript 以来已经很长时间了。
          • @Alexander:我确实将代码示例更改为 System-F 函数式伪代码。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-10-22
          • 2013-11-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多