【问题标题】:Can we assume that x == (int)sqrt(x * x) for all positive integers?我们可以假设所有正整数的 x == (int)sqrt(x * x) 吗?
【发布时间】:2021-05-19 11:54:30
【问题描述】:

在 C++ 中,sqrt 函数仅对 double 值起作用。

如果我们使用整数(unsigned long long),我们可以确定

x == sqrt(x * x)

对于任何积极的x 其中x * x <= MAXIMUM_VALUE?

是否取决于机器架构和编译器?

【问题讨论】:

标签: java c++ c math


【解决方案1】:

在 Java 中,Math.sqrt(x) 采用双精度值。您说 x 是这样的,x * x 低于Integer.MAX_VALUE。每个整数都可以用 double 完美表示 - java 中的 double 明确定义为带有 52 位尾数的 iEEE-754 样式的 double;因此,在 java 中,double 可以完美地表示 -2^52 和 +2^52 之间的所有整数值,这很容易涵盖所有 int 值(在 java 上定义为有符号 32 位),但是 它并未涵盖所有long 值。 (定义为有符号的 64 位;64 大于 52,所以不行)。

因此,x * x 在最终从 int 转换为 double 时不会丢失精度。然后,这个数字上的Math.sqrt() 将给出一个也可以完美表示为double 的结果(因为它是x,并且鉴于x*x 适合int,x 也必须适合) ,因此,是的,这将始终适用于所有 x。

但是,嘿,为什么不试一试,对吧?

public static void main(String[] args) {
    int i = 1;
    while (true) {
        if (i * i < 0) break;
        int j = (int) Math.sqrt(i * i);
        if (i != j) System.out.println("Oh dear! " + i + " -> " + j);

        i++;
    }
    System.out.println("Done at " + i);
}

> Done at 46341

因此通过详尽的尝试来证明这一点。

事实证明,不存在 - 任何long 值使得x * x 仍然适合(因此,是&lt;2^63-1)具有x == (long) Math.sqrt(x * x); 的属性。这大概是因为在x*x,这个数字完全适合一个长整数,即使不是所有这么大的整数都可以。证明:

long p = 2000000000L;
for (; true; p++) {
    long pp = p * p;
    if (pp < 0) break;
    long q = (long) Math.sqrt(pp);
    if (q != p) System.out.println("PROBLEM: " + p + " -> " + q);
}
System.out.println("Abort: " + p);

> Abort: 3037000500

当然,如果存在任何不成立的数字,那么在这个高端范围内至少有一个。从 0 开始需要很长时间。

但是我们是否知道 sqrt 总是会返回一个完全平方的精确值,还是可能有点不准确?

我们应该 - 它是 java。与 C 不同,几乎所有东西都是“定义明确的”,如果 JVM 不能产生指定的确切答案,它就不能合法地称自己为一个。 Math.sqrt 文档提供的余地不足以让任何答案(除了 x 之外的任何答案都是合法实施),因此,是的,这是一个保证。

理论上,JVM 对浮点数有一些非常小的余地,strictfp 禁用,但是 [A] 更多的是使用 80 位寄存器来表示数字而不是 64,这不可能破坏这个假设,并且[B] 不久前出现了一个 java 标签问题,显示 strictfp 对任何硬件和任何 VM 版本都有任何影响,唯一可行的结果是 15 年前不可重现的事情。无论硬件或 VM 版本如何,我都非常有信心声明这将永远成立。

【讨论】:

  • 您能否提供任何不适用的long 值?是的,不是所有的long 值都可以用double 表示,但是当结果转换回long 时,舍入误差也可能会丢失。
  • 但是我们是否知道sqrt 总是会返回一个完全平方的精确值,或者它可能有点不准确?
  • “EDIT”绰号在 Stack Overflow 上不是必需的。 Stack Overflow 上的每个帖子都有详细的编辑历史记录,任何人都可以查看。这篇文章的编辑历史位于here。
  • "在 java 中,一个 double 可以完美地表示 -2^52 和 +2^52 之间的所有整数值"
  • @RobertHarvey 但是有五分钟(?)的宽限期,编辑不会出现在历史记录中。在使 cmets 无效的宽限期内进行编辑的“EDIT”绰号将是一件好事......
【解决方案2】:

我想我们可以相信。

类型将浮点数转换为整数是只取整数部分。我相信您可能会担心,例如,sqrt(4) 会产生一个类似于1.999...9 的浮点数,并且它的类型转换为1。 (产生 2.000...1 很好,因为它将被强制转换为 2。)

但浮点数4 是这样的

(1 * 2-0 + 0 + 2-1 + ... + 0 * 2-23) * 22

根据Floating-point arithmetic.

也就是说,它不能小于4,如3.999...9。同样,sqrt 的数量不得小于

(1 * 2-0) * 2

所以整数平方的sqrt 至少会产生一个大于但足够接近整数的浮点数。

【讨论】:

    【解决方案3】:

    试试吧。是的,它适用于 Java,适用于非负数。甚至为long 工作,这与普遍看法相反。

    class Code {
        public static void main(String[] args) throws Throwable {
            for (long x=(long)Math.sqrt(Long.MAX_VALUE);; --x) {
                if (!(x == (long)Math.sqrt(x * x))) {
                    System.err.println("Not true for: "+x);
                    break;
                }
            }
            System.err.println("Done");
        }
    }
    

    (第一个无效的数字是 3037000500L,平方后变为负数。)

    即使是长整数,可测试值的范围也在 2^31 或 2*10^9 左右,因此对于这种微不足道的事情,检查每一个值是合理的。您甚至可以对 32 位值强制使用合理的加密函数——更多人应该意识到这一点。对于完整的 64 位,效果不会那么好。

    【讨论】:

      【解决方案4】:

      大整数 - sqrt(since 9)

      1. 需要更严格限制溢出可能性的用例可以使用 BigInteger
      2. BigInteger 应该适用于任何实际用例。
      3. 对于正常使用情况,这可能效率不高。

      约束

      BigInteger Limits

      BigInteger 必须支持 -2^Integer.MAX_VALUE(不包含)到 +2^Integer.MAX_VALUE(不包含)范围内的值,并且可能支持该范围之外的值。当 BigInteger 构造函数或方法将生成超出支持范围的值时,将引发 ArithmeticException。可能的素数范围是有限的,并且可能小于 BigInteger 的完全支持的正范围。范围必须至少为 1 到 2500000000。
      实施说明:

      在参考实现中,当结果超出 -2^Integer.MAX_VALUE(不包含)到 +2^Integer.MAX_VALUE(不包含)的支持范围时,BigInteger 构造函数和操作会抛出 ArithmeticException。

      初始化为字节array时的数组大小限制

      初始化为String时的字符串长度限制

      肯定不支持1/0

      jshell> new BigInteger("1").divide(new BigInteger("0"))
      |  Exception java.lang.ArithmeticException: BigInteger divide by zero
      |        at MutableBigInteger.divideKnuth (MutableBigInteger.java:1178)
      |        at BigInteger.divideKnuth (BigInteger.java:2300)
      |        at BigInteger.divide (BigInteger.java:2281)
      |        at (#1:1)
      

      示例代码

      import java.math.BigInteger;
      import java.util.Arrays;
      import java.util.List;
      
      public class SquareAndSqrt {
      
          static void valid() {
              List<String> values = Arrays.asList("1", "9223372036854775807",
                  "92233720368547758079223372036854775807", 
                  new BigInteger("2").pow(Short.MAX_VALUE - 1).toString());
      
              for (String input : values) {
                  final BigInteger value = new BigInteger(input);
                  final BigInteger square = value.multiply(value);
                  final BigInteger sqrt = square.sqrt();
      
                  System.out.println("value: " + input + System.lineSeparator()
                      + ", square: " + square + System.lineSeparator()
                      + ", sqrt: " + sqrt + System.lineSeparator()
                      + ", " + value.equals(sqrt));
      
                  System.out.println(System.lineSeparator().repeat(2)); // pre java 11 - System.out.println(new String(new char[2]).replace("\0", System.lineSeparator()));
              }
          }
      
          static void mayBeInValid() {
              try {
                  new BigInteger("2").pow(Integer.MAX_VALUE);
              } catch (ArithmeticException e) {
                  System.out.print("value: 2^Integer.MAX_VALUE, Exception: " + e);
                  System.out.println(System.lineSeparator().repeat(2));
              }
          }
      
          public static void main(String[] args) {
              valid();
              mayBeInValid();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-12-16
        • 1970-01-01
        • 1970-01-01
        • 2019-02-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-14
        • 1970-01-01
        相关资源
        最近更新 更多