针对SSE2之前处理器的Java运行时如何实现浮点基本操作?

针对SSE2之前处理器的Java运行时如何实现浮点基本操作?,java,floating-point,Java,Floating Point,当设置了strictfp时,针对没有SSE2的英特尔处理器的Java运行时如何处理浮点非规范化 即使将387 FPU设置为53位精度,它也会保持一个过大的指数范围,即: 强制在每个中间结果处检测下溢/溢出,以及 这样就很难避免非规范值的双舍入 策略包括重新计算导致使用模拟浮点值的非规范值的操作,或沿的永久指数偏移,以便为OCaml配备63位浮点,从指数中借用一位以避免双舍入 在任何情况下,我都无法避免每个浮点计算至少有一个条件分支,除非可以静态地确定该操作不会下溢/溢出如何处理异常(溢出/下溢)

当设置了
strictfp
时,针对没有SSE2的英特尔处理器的Java运行时如何处理浮点非规范化

即使将387 FPU设置为53位精度,它也会保持一个过大的指数范围,即:

  • 强制在每个中间结果处检测下溢/溢出,以及
  • 这样就很难避免非规范值的双舍入
  • 策略包括重新计算导致使用模拟浮点值的非规范值的操作,或沿的永久指数偏移,以便为OCaml配备63位浮点,从指数中借用一位以避免双舍入


    在任何情况下,我都无法避免每个浮点计算至少有一个条件分支,除非可以静态地确定该操作不会下溢/溢出如何处理异常(溢出/下溢)情况是我问题的一部分,但这不能与表示问题分开(例如,永久指数偏移策略似乎意味着只需要检查溢出)。

    在我看来,从一个非常琐碎的测试案例,与JVM一样,每次
    double
    计算都会通过内存进行往返,以获得所需的舍入。它似乎还做了一些奇怪的事情,有几个神奇的常数。下面是一个简单的“幼稚计算2^n”程序对我的帮助:

    我相信
    0xb523a2c8
    0xb523a2bc
    是热点源代码中的
    次正常偏差1
    次正常偏差2
    <代码>fpu低于正常值偏差1看起来是
    0x03FF80000000000000
    fpu低于正常值偏差2
    看起来是
    0x7FF80000000000000
    <代码>\u fpu\u次正常值偏差1具有将最小正常值
    double
    缩放到最小正常值
    长双精度
    的效果;如果FPU四舍五入到53位,“正确的事情”就会发生

    我推测,似乎毫无意义的
    test
    指令就在那里,因此如果需要GC,线程可以通过将该页面标记为不可读来中断

    以下是Java代码:

    import java.io.*;
    public strictfp class fptest {
     public static double calc(int k) {
      double a = 2.0;
      double b = 1.0;
      for (int i = 0; i < k; i++) {
       b *= a;
      }
      return b;
     }
     public static double intest() {
      double d = 0;
      for (int i = 0; i < 4100; i++) d += calc(i);
      return d;
     }
     public static void main(String[] args) throws Exception {
      for (int i = 0; i < 100; i++)
       System.out.println(intest());
     }
    }
    

    我看不到加法和减法,但我敢打赌他们只是在53位模式下用FPU进行加法/减法运算,然后通过内存对结果进行往返运算。我有点好奇他们是否会出错,但我不太好奇。

    @ChrisJester-Young谢谢你帮我把问题弄清楚。我不知道你问题的答案。但是,如果您有这样一台机器,您可以传递标志
    -XX:+UnlockDiagnosticVMOptions-XX:+PrintAssembly
    ,查看它生成的代码。@tmyklebu我甚至不知道是否安装了Java运行时。我只对387的双精度模拟感兴趣。还有人曾经给我一本关于这个主题的研究生回忆录,我忘了存档,现在再也找不到了。不是你,是吗?看到代码,还没有完全理解它,这表明了“指数偏移”方法的一种变体。代码必须是这样做的:1-将一个参数乘以2^-K1,这样扩展指数乘法的结果是一个非规范的,其有效位数与标准指数2的结果相同-做乘积3-将结果乘以2^K2,这样结果要么是精确的,或+inf(正好是乘积溢出时的标准指数)4-乘以2^(K1-K2)。现在,第三个常量在哪里?等等,在你的方法
    calc
    中,乘法的一个操作数显然是常量。这一个可以预先偏置,只剩下两个乘法来确保正确的下溢和溢出结果。我认为正在发生的是:(1)FPU处于53位模式;(2) 一个奇怪的常数将最小的
    double
    次正常缩放到最小的
    long double
    次正常,另一个将其向后缩放,从而处理下溢;(3) 通过内存中的
    double
    的往返处理指数溢出。所以我猜乘法不会有双舍入。避免双舍入是必要的。目标是使浮点表达式的值独立于软件或硬件实现。在某些情况下,在某些机器上进行双舍入将无法达到此目的。啊,是的,在我的方案中,2^K2的乘法可以被通过内存的往返替换。也许这是最好的,因为2^K2太大了一点,不能用80位浮点表示:要实现“乘以2^K2”步骤,需要两个实际的浮点乘法。
    import java.io.*;
    public strictfp class fptest {
     public static double calc(int k) {
      double a = 2.0;
      double b = 1.0;
      for (int i = 0; i < k; i++) {
       b *= a;
      }
      return b;
     }
     public static double intest() {
      double d = 0;
      for (int i = 0; i < 4100; i++) d += calc(i);
      return d;
     }
     public static void main(String[] args) throws Exception {
      for (int i = 0; i < 100; i++)
       System.out.println(intest());
     }
    }
    
    instruct strictfp_mulD_reg(regDPR1 dst, regnotDPR1 src) %{
      predicate( UseSSE<=1 && Compile::current()->has_method() && Compile::current()
    ->method()->is_strict() );
      match(Set dst (MulD dst src));
      ins_cost(1);   // Select this instruction for all strict FP double multiplies
    
      format %{ "FLD    StubRoutines::_fpu_subnormal_bias1\n\t"
                "DMULp  $dst,ST\n\t"
                "FLD    $src\n\t"
                "DMULp  $dst,ST\n\t"
                "FLD    StubRoutines::_fpu_subnormal_bias2\n\t"
                "DMULp  $dst,ST\n\t" %}
      opcode(0xDE, 0x1); /* DE C8+i or DE /1*/
      ins_encode( strictfp_bias1(dst),
                  Push_Reg_D(src),
                  OpcP, RegOpc(dst),
                  strictfp_bias2(dst) );
      ins_pipe( fpu_reg_reg );
    %}
    
    instruct strictfp_divD_reg(regDPR1 dst, regnotDPR1 src) %{
      predicate (UseSSE<=1);
      match(Set dst (DivD dst src));
      predicate( UseSSE<=1 && Compile::current()->has_method() && Compile::current()
    ->method()->is_strict() );
      ins_cost(01);
    
      format %{ "FLD    StubRoutines::_fpu_subnormal_bias1\n\t"
                "DMULp  $dst,ST\n\t"
                "FLD    $src\n\t"
                "FDIVp  $dst,ST\n\t"
                "FLD    StubRoutines::_fpu_subnormal_bias2\n\t"
                "DMULp  $dst,ST\n\t" %}
      opcode(0xDE, 0x7); /* DE F8+i or DE /7*/
      ins_encode( strictfp_bias1(dst),
                  Push_Reg_D(src),
                  OpcP, RegOpc(dst),
                  strictfp_bias2(dst) );
      ins_pipe( fpu_reg_reg );
    %}