这不是家庭作业,只是我喜欢的东西。因此,直接计算因子不是很快; memoization可以提供帮助,但如果结果适合32位或64位,则factorial只能分别用于输入0
到12
和20
。所以...我们不妨使用查找表:
n n!
0 1
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
8 40320
9 362880
10 3628800
11 39916800
12 479001600
13 6227020800 2^32= 4294967296
14 87178291200
15 1.30767E+12
16 2.09228E+13
17 3.55687E+14
18 6.40237E+15
19 1.21645E+17
20 2.4329E+18
2^64= 1.84467E+19
所以,假设我想要一个使用内联汇编的内联C ++阶乘函数,结果需要32位或64位无符号整数。如果输入为负或大到足以导致溢出,则输出应为0.如何在汇编中完成此操作以使其消耗最少量的循环?此代码将在64位Intel / AMD架构上运行。如果可行,我有兴趣改进最坏的情况,因此20!
计算的时间不应超过0!
- 希望有一种二元搜索方法。希望有一个聪明的伎俩可以做if (n == 0 || n == 1) { return 1; }
。此外,如果输出需要是32位,那么我认为汇编指令可以包含代码和数据。我的装配知识很薄弱。如果这个问题没有多大意义,请告诉我。
能够在C ++中使用该函数会很好 - 这使它成为一个更现实的问题。例如,如果调用函数是昂贵的,那么尝试在程序集的主体中保存1-2个时钟周期将无济于事。
答案 0 :(得分:10)
我巧妙地在程序集中构建了一个查找表。为了防止你的装配生锈,
例程期望参数位于ecx
寄存器中。我验证它在范围内,然后将查找表的值读入eax
和edx
寄存器。如果该值超出范围,我只是将eax
和edx
寄存器与它们相加(这会强制它们为0)。不幸的是,由于它是一个汇编例程,编译器将无法内联代码。但是,我确信通过编写我精彩的汇编程序而节省的几个周期将通过内联来弥补任何收益。
factorial:
xorl %eax, %eax
xorl %edx, %edx
cmpl $20, %ecx
ja .TOOBIG
movl CSWTCH.1(,%ecx,8), %eax
movl CSWTCH.1+4(,%ecx,8), %edx
.TOOBIG:
LOOKUP_TABLE:
.section .rodata
.align 32
.type CSWTCH.1, @object
.size CSWTCH.1, 168
CSWTCH.1:
.long 1
.long 0
.long 1
.long 0
.long 2
.long 0
.long 6
.long 0
.long 24
.long 0
.long 120
.long 0
.long 720
.long 0
.long 5040
.long 0
.long 40320
.long 0
.long 362880
.long 0
.long 3628800
.long 0
.long 39916800
.long 0
.long 479001600
.long 0
.long 1932053504
.long 1
.long 1278945280
.long 20
.long 2004310016
.long 304
.long 2004189184
.long 4871
.long -288522240
.long 82814
.long -898433024
.long 1490668
.long 109641728
.long 28322707
.long -2102132736
.long 566454140
查找表很难维护,所以我已经包含了我用来构建它的脚本
static constexpr uint64_t const_factorial(uint32_t i) {
return (i==0)? 1: (i * const_factorial(i-1));
}
uint64_t factorial(uint32_t i) {
switch(i) {
case 0: return const_factorial(0);
case 1: return const_factorial(1);
case 2: return const_factorial(2);
case 3: return const_factorial(3);
case 4: return const_factorial(4);
case 5: return const_factorial(5);
case 6: return const_factorial(6);
case 7: return const_factorial(7);
case 8: return const_factorial(8);
case 9: return const_factorial(9);
case 10: return const_factorial(10);
case 11: return const_factorial(11);
case 12: return const_factorial(12);
case 13: return const_factorial(13);
case 14: return const_factorial(14);
case 15: return const_factorial(15);
case 16: return const_factorial(16);
case 17: return const_factorial(17);
case 18: return const_factorial(18);
case 19: return const_factorial(19);
case 20: return const_factorial(20);
default: return 0;
}
}
万一你因为我的幽默尝试而错过了它。 C ++编译器能够正确优化您的代码。正如您所看到的,我不需要对查找表,二叉搜索树或哈希进行任何花哨的操作。只是一个简单的switch
语句,编译器完成了其余的工作。
答案 1 :(得分:5)
我弯曲组装肌肉已经有一段时间了,所以我只是提供一些一般的建议。
由于您事先知道所有项目的确切数量和大小,只需创建一个连续的值数组(硬编码或预先计算)。验证函数的输入(< 0或> 12/20)后,您可以使用简单的偏移量寻址来检索适当的值。这将在O(1)时间内有效。
答案 2 :(得分:1)
从 2021 年开始更新。手头有 C++17。
我想没有比下面更快的方法了。不需要汇编程序。
因为适合无符号 64 位值的阶乘数量非常少 (21),编译时 constexpr 数组将主要只使用 21*8 = 168 个字节。
168 字节
这个数字很低,我们可以轻松构建编译时间 public class MyActivity extends AppcompatActivity{
MediaPlayer mp ;
NumberPicker myPicker;
...
@Override
protected void onCreate(Bundle savedInstanceState){
...
myPicker = findByViewId(R.id.my_picker);
myPicker.setOnValueChangedListener(this::changeValue);
mp = MediaPlayer.create(this,R.raw.my_tick_sound);
....
}
private void changeValue(NumberPicker numberPicker, int i, int i1){
if(mp != null){
if(mp.isPlaying) mp.stop();
mp.start();
}
//do logic.....//
}
并停止所有进一步的考虑。
实际上一切都可以在编译时完成。
我们首先将计算阶乘的默认方法定义为 constexpr std::array
函数:
constexpr
这样,可以在编译时轻松计算阶乘。然后,我们用所有阶乘填充 constexpr unsigned long long factorial(unsigned long long n) noexcept {
return n == 0ull ? 1 : n * factorial(n - 1ull);
}
。我们还使用 std::array
并使其成为带有可变参数包的模板。
我们使用 constexpr
为索引 0,1,2,3,4,5, .... 创建阶乘
这很直接,并不复杂:
std::integer_sequence
这个函数将输入一个整数序列 0,1,2,3,4,... 并返回一个带有相应阶乘的 template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t, ManyIndices...>) noexcept {
return std::array<unsigned long long, sizeof...(ManyIndices)>{ { factorial(ManyIndices)... } };
};
。
我们知道最多可以存储 21 个值。因此我们创建了一个 next 函数,它将使用整数序列 1,2,3,4,...,20,21 调用上面的函数,如下所示:
std::array<unsigned long long, ...>
现在,终于,
constexpr auto generateArray()noexcept {
return generateArrayHelper(std::make_integer_sequence<size_t, MaxIndexFor64BitValue>());
}
会给我们一个名为 Factorial 的编译时 constexpr auto Factorial = generateArray();
,其中包含所有阶乘。如果我们需要第 i 个阶乘,那么我们可以简单地写 std::array<unsigned long long, 21>
。运行时不会进行计算。
我不认为有更快的方法来计算阶乘。
请查看下面的完整程序:
Factorial[i]
使用 Microsoft Visual Studio Community 2019 版本 16.8.2 开发、编译和测试
使用 gcc 10.2 和 clang 11.0.1 额外编译和测试
语言:C++17
答案 3 :(得分:0)
谁说你的程序集版本要比C ++版本更快。事实上,谁说它甚至会在速度上匹配?我打赌100美元,你甚至无法像编译器一样快速地做到这一点。
答案 4 :(得分:0)
在流行的需求上,从表面上讲,它是传说中的二元搜索,而不是哈希表(std C ++没有我相信的那样)。
#include <map>
void main()
{
std::map<int, BigIntThing> factMap;
// insert all elements here, probably fancier ways to do this
factMap.insert( 1 );
factMap.insert( 1 );
factMap.insert( 2 );
// ....
// to access, say 15!
BigIntThing factMap[15]; // I think the index is right >_<
}
就是这样。订购std::map
,因此如果您的BigIntThing具有比较运算符,那么一切都很好。应该有办法让const
和/或static
和/或global
按照您想要的方式进行编译。
答案 5 :(得分:0)
如果您只使用0-19之间的数字,则哈希表或二叉树过度。只需创建一个unsigned int[20]
,然后查询索引:
const unsigned int FACTORIALS[20] = {1,1,2,6,24,120,etc..};
unsigned int factorial(unsigned int num) {
if(num >= 0 && num <= 19) {
return FACTORIALS[num];
}
else {
throw // some sort of exception
}
}
您也可以使用模板来构建阵列。
答案 6 :(得分:0)
......可能打败了你的,来自:
uint64_t answers[] = {
1ULL,
1ULL,
2ULL,
6ULL,
24ULL,
...
2432902008176640000ULL,
};
uint64_t factorial(unsigned int i) {
if(i >= sizeof(answers) / sizeof(*answers))
return 0;
else
return answers[i];
}
......和集会......
factorial:
cmpl $20, %edi
movl $0, %eax
ja .L3
movslq %edi,%eax
movq answers(,%rax,8), %rax
.L3:
rep
ret
answers:
.quad 1
.quad 1
...
...这似乎是第一个64位汇编程序的答案......