assembly 截断为1位尾数的二进制大整数科学记数法转换算法

tez616oj  于 2023-03-08  发布在  其他
关注(0)|答案(1)|浏览(99)

我有一个非常大的无符号二进制大整数,其规模为6*10^120。假设该大整数存储在一个由许多QWORD(8字节)无符号整数或几个YMM寄存器组成的结构体中。
我想用十进制(而不是二进制)科学记数法来显示它,比如6E120。尾数总是1位数,并且必须是完整十进制表示法的首位数;将其截断为1个有效数字,而不是舍入到最接近的数字。指数始终为3位数字。格式为aExyz,如8 E095。
求数量级(10的幂)和小数点前几位最省时(最快)的算法是什么?我问的是算法,不是程序,我自己写。
这将是在MASM 64汇编语言。如果有指令,可以帮助像位操作或FPU/SSE/AVX 512技巧,请建议他们。
这不是一个高级程序,所以任何包含第三方库或高级语言构造的响应都没有帮助。我知道某个算法涉及许多除法。这些在ASM中是昂贵的,所以我正在寻找替代方法。我知道如何从二进制转换为十进制,然后转换为科学记数法。我正在努力避免中间的步骤。

3qpi33ja

3qpi33ja1#

假设最大可能值小于1E154,因此所有值都适合512位,那么我猜想答案 * 可能 * 是:

  • 预先计算静态常量数组中所有可能的powers_of_10。(#ops~0)
  • 如果所有数字都适合2个YMM寄存器,则表示支持的最大值为1E154,这意味着10的幂查找表将占用约9856个字节。
  • 在您的数字中,计算前导 * 二进制 * 零的数量(也使用clz)(#ops〈~10)
  • 使用(max-bits - number_of_leading_zeroes) / 3.32192809489可以很好地估计十进制数字的最终个数。这也是一个接近10的幂的很好的估计。(#ops~2)
  • 从该估计值迭代powers_of_10,直到找到小于您的值的最大10次幂(#ops~8)。
  • 如果您愿意牺牲 * exponent * 的准确性,则可以跳过这一步。
  • 在循环中将10的幂加到它自己上,直到大于输入。(#ops〈~100)(10次加法,10 uint64 s)
  • 如果您愿意牺牲尾数的微小精度,那么double(input)/double(power_of_ten)将在一个除法中完成。
  • 在bigint-〉double转换中,有很多捷径可以走。
  • 发出loop_count-1Epower_of_ten_index。(操作数约为4)

如果你愿意牺牲指数和尾数的精确度,那么剩下的16个操作完全忽略了低位。

性能

在不写出最终实现的情况下,很难猜测性能,而使用较大的LUT、缓存,因此程序的其余部分就成为一个因素,但这里是初步数据:https://quick-bench.com/q/53k-xSQz7y4iCO7ny66Dz2w62dQ(我运行了几次,试图消除离群值)
在我的测试中,最快的组合似乎(毫不奇怪)是:

  • 不要迭代powers_of_ten来确认指数。
  • 不要精确计算尾数。
  • 使用浮点数来猜测尾数(更喜欢速度而不是准确性)

从这个基线,我们可以看到:

  • 迭代powers_of_ten看起来对平均时间没有明显的影响,只要你还使用浮点数来猜测尾数。如果你不使用浮点数来猜测尾数,那么尾数计算将花费更长的时间。这意味着它不会显著增加平均精度,可以跳过它以最小化代码大小。
  • 使用浮点数来猜测尾数似乎使算法平均快了~5%,而对准确性没有影响。
  • 迭代寻找精确的尾数会使算法减慢大约16%,但这也意味着它会增加精度,所以我假设您希望保持这一点。
  • 在估算尾数时,我有两个bigint-〉double转换代码的变体。一个版本只确保double至少有1个有效位,另一个版本确保double有〉4个有效位。额外的代码确保更多的有效位对平均时间没有明显的影响。因此,这一步增加的代码/精度并没有显著提高查找精确尾数的性能,我假设您希望跳过这一步,以最小化代码大小。

值得注意的是,所有这些都比将bigint转换为double然后使用printf("%.0E",value);快约4倍
(本人不保证任何此代码结果的准确性)

相关问题