【问题标题】:FASTEST way to truncate a float in Java在 Java 中截断浮点数的最快方法
【发布时间】:2021-12-04 15:15:15
【问题描述】:

我有一个程序可以接收 20,000 到 500,000 个速度向量,并且必须输出这些向量乘以某个标量。该程序允许用户设置可变精度,这基本上就是计算中要截断的小数位数。程序现在很慢,我发现这不是因为乘了很多数字,而是因为我使用截断浮点值的方法。

我已经在此处查看了几种截断小数的解决方案,like this one,他们大多推荐使用 DecimalFormat。这对于格式化小数一次或两次以打印出漂亮的用户输出非常有效,但对于需要在几秒钟内发生的数十万次截断来说太慢了。

将浮点值截断为 n 个位置的最有效方法是什么,同时将执行时间保持在最高优先级?我不关心资源使用、约定或外部库的使用。只要能以最快的速度完成工作。

编辑:对不起,我想我应该更清楚。这是我试图说明的一个非常简化的版本:

import java.util.*;
import java.lang.*;
import java.text.DecimalFormat;
import java.math.RoundingMode;
public class MyClass {
    
    static class Vector{
        float x, y, z;
        @Override
        public String toString(){
            return "[" + x + ", " + y + ", " + z + "]";
        }
    }
    
    public static ArrayList<Vector> generateRandomVecs(){
      ArrayList<Vector> vecs = new ArrayList<>();
      Random rand = new Random();
      for(int i = 0; i < 500000; i++){
          Vector v = new Vector();
          v.x = rand.nextFloat() * 10;
          v.y = rand.nextFloat() * 10;
          v.z = rand.nextFloat() * 10;
          vecs.add(v);
      }
      return vecs;
    }
    
    public static void main(String args[]) {
        
    int precision = 2;
    
    float scalarToMultiplyBy = 4.0f;
   
     ArrayList<Vector> velocities = generateRandomVecs();
     
     System.out.println("First 10 raw vectors:");
     for(int i = 0; i < 10; i++){
         System.out.print(velocities.get(i) + " ");
     }
      
     /* 
     This is the code that I am concerned about
     */
     
     DecimalFormat df = new DecimalFormat("##.##");
     df.setRoundingMode(RoundingMode.DOWN);

     long start = System.currentTimeMillis();

     for(Vector v : velocities){
        /* Highly inefficient way of truncating*/
        v.x = Float.parseFloat(df.format(v.x * scalarToMultiplyBy));
        v.y = Float.parseFloat(df.format(v.y * scalarToMultiplyBy));
        v.z = Float.parseFloat(df.format(v.z * scalarToMultiplyBy));
     }
      
     long finish = System.currentTimeMillis();
     long timeElapsed = finish - start;
     
     System.out.println();
     System.out.println("Runtime: " + timeElapsed + " ms");
     
     System.out.println("First 10 multiplied and truncated vectors:");
     for(int i = 0; i < 10; i++){
         System.out.print(velocities.get(i) + " ");
     }
    }
}

这样做非常重要的原因是程序的不同部分会将三角函数值存储在查找表中。查找表将预先生成 n 个位置,因此任何具有 7 个位置(即 5.2387471)的浮点值的速度向量必须在查找前截断到 n 个位置。需要截断而不是舍入,因为在这个程序的上下文中,如果一个向量略小于它的真实值,但不大于它是可以的。

Lookup table for 2 decimal places:
...
8.03 -> -0.17511085919
8.04 -> -0.18494742685
8.05 -> -0.19476549993
8.06 -> -0.20456409661
8.07 -> -0.21434223706
...

假设我想查找上表中向量{8.040844, 8.05813164, 8.065688} 中每个元素的余弦值。显然,我不能直接查找这些值,但可以在表格中查找{8.04, 8.05, 8.06}

我需要的是一种从{8.040844, 8.05813164, 8.065688}{8.04, 8.05, 8.06} 的非常快速的方法

【问题讨论】:

  • 您需要向我们展示您现有的代码并解释您如何确定问题出在截断。没有这个,我们只是猜测。请(重新)阅读How to Ask
  • 没有将浮点数舍入或截断到特定小数位数的事情。 FP有二进制位,没有小数位,两者不可通约。我建议您尽可能精确地进行四舍五入,直到 displayprint 以 base-10 为单位的值,此时舍入或截断变得微不足道你愿意。
  • 如果这是一个独立的程序,我认为它“目前非常慢”的真正原因是多种因素的结合。其他因素将/可能包括读取和写入数据的开销,以及 JVM 启动/预热开销。您是否分析您的代码以确定瓶颈的真正位置? (或者你的“发现”实际上只是猜测截断是瓶颈?)
  • 不要尝试使用浮点数作为查找表中的键(除非您乐于进行二进制搜索,在这种情况下不需要截断)使用整数,并且在您进行查找,将数字乘以 100 并截断为整数。
  • Re“……如果一个向量略小于它的真实值,但不大于它是可以的”:这是可疑的。您显示的示例余弦条目是经过原点的几个周期;波浪起伏了好几次。小于理想数的计算数可能会产生小于或大于理想余弦的余弦,具体取决于它在循环中的位置。这指向一个 X-Y 问题:您有一些先前的问题要解决,已经决定了一个解决方案,并且正在询问我们如何实施该解决方案。但这不是一个好的解决方案;你应该重新考虑原来的问题。

标签: java floating-point


【解决方案1】:

引入舍入误差的最快方法是乘以 10^n,调用Math.rint,然后除以 10^n。

这……不过,考虑到引入的错误,而且——更重要的是——它实际上并没有购买任何东西。如果它不能提高效率或任何东西,为什么要去掉小数点?如果是为了使值更短以进行显示等,请截断,但在此之前,如果您只使用完整的float 精度,您的程序将尽可能快地运行。

【讨论】:

  • 这不是为了展示。表中的内部三角函数查找需要截断,该表仅支持任意数量的位置。例如,从 -5 到 5 且有 2 个小数位的三角查找表将包含 [-5, -4.99, -4.98 ... 4.98, 4.99, 5] 的条目。但是,原始速度向量可以包含超过 2 个小数位的浮点分量(即 3.4731345)。该表将不包含 3.4731345 的条目,但将包含 3.47 的条目。我需要可以很快将 3.4731345 转换为 3.47 的代码。
  • 然后做我上面描述的事情。不过,不要期望除数正好是 3.47。
猜你喜欢
  • 1970-01-01
  • 2015-03-05
  • 2011-04-29
  • 1970-01-01
  • 2013-12-31
  • 1970-01-01
  • 2012-05-07
  • 1970-01-01
  • 2017-07-04
相关资源
最近更新 更多