Brainfly: 用 C# 类型系统构建 Brainfuck 编译器

Brainfuck 简介

Brainfuck 是由 Urban Müller 在 1993 年创造的一门非常精简的图灵完备的编程语言。

正所谓大道至简,这门编程语言简单到语法只有 8 个字符,每一个字符对应一个指令,用 C 语言来描述的话就是:

字符 含义
> ++ptr
< --ptr
+ ++*ptr
- --*ptr
. putchar(*ptr)
, *ptr = getchar()
[ while (*ptr) {
] }

然后只需要提供一个已经初始化为 0 的字节数组作为内存、一个指向数组的指针、以及用于输入输出的两个字节流就能够让程序运行了。

比如 Hello World! 程序就可以写成:

++++++++++[>+++++++>++++++++++>+++>+<<<<-]
>++.>+.+++++++..+++.>++.<<+++++++++++++++.
>.+++.------.--------.>+.>.

C# 类型系统入门

既然要用 C# 类型系统来构建 Brainfuck 的编译器,我们需要首先对 C# 类型系统有一些认知。

泛型系统

C# 的类型系统构建在 .NET 的类型系统之上,而众所周知 .NET 是一个有具现化泛型的类型系统的平台,意味着泛型参数不仅不会被擦除,还会根据泛型参数来分发甚至特化代码。

例如:

class Foo<T>
{public void Print() => Console.WriteLine(default(T)?.ToString() ?? "null");
}

对于上面的代码,调用 new Foo<int>().Print() 会输出 0,调用 new Foo<DateTime>().Print() 会输出 0001-01-01T00:00:00,而调用 new Foo<string>().Print() 则会输出 null

更进一步,因为 .NET 泛型在运行时会根据类型参数对代码进行特化,比如:

class Calculator<T> where T : IAdditionOperators<T, T, T>
{public T Add(T left, T right){return left + right;}
}

我们可以前往 godbolt 看看 .NET 的编译器对上述代码产生了什么机器代码:

Calculator`1[int]:Add(int,int):int:this (FullOpts):lea      eax, [rsi+rdx]ret      Calculator`1[long]:Add(long,long):long:this (FullOpts):lea      rax, [rsi+rdx]ret      Calculator`1[ubyte]:Add(ubyte,ubyte):ubyte:this (FullOpts):add      edx, esimovzx    rax, dlret      Calculator`1[float]:Add(float,float):float:this (FullOpts):vaddss   xmm0, xmm0, xmm1ret      Calculator`1[double]:Add(double,double):double:this (FullOpts):vaddsd   xmm0, xmm0, xmm1ret      

可以看到我代入不同的类型参数进去,会得到各自特化后的代码。

接口的虚静态成员

你可能好奇为什么上面的 Calculator<T>leftright 可以直接加,这是因为 .NET 支持接口的虚静态成员。上面的 IAdditionOperators 接口其实定义长这个样子:

interface IAdditionOperators<TSelf, TOther, TResult>
{abstract static TResult operator+(TSelf self, TOther other);
}

我们对 T 进行泛型约束 where T : IAdditionOperators<T, T, T> 之后,就使得泛型代码中可以通过类型 T 直接调用接口中的静态抽象方法 operator+

性能?

有了上面的知识,我想知道在这套类型系统之上,.NET 的编译器到底能生成多优化的代码,那接下来我们进行一些小的测试。

首先让我们用类型表达一下具有 int 范围的数字,毕竟之后构建 Brainfuck 编译器的时候肯定会用到。众所周知 int 有 32 位,用 16 进制表示那就是 8 位。我们可以给 16 进制的每一个数位设计一个类型,然后将 8 位十六进制数位组合起来就是数字。

首先我们起手一个 interface IHex,然后让每一个数位都实现这个接口。

interface IHex
{abstract static int Value { get; }
}

比如十六进制数位 0、6、C 可以分别表示为:

struct Hex0 : IHex
{public static int Value => 0;
}struct Hex6 : IHex
{public static int Value => 6;
}struct HexC : IHex
{public static int Value => 12;
}

这里我们想把数字和数位区分开,因此我们定义一个跟 IHex 长得一样的接口 INum 用来给数字 Int 实现:

interface INum
{abstract static int Value { get; }
}struct Int<H7, H6, H5, H4, H3, H2, H1, H0> : INumwhere H7 : IHexwhere H6 : IHexwhere H5 : IHexwhere H4 : IHexwhere H3 : IHexwhere H2 : IHexwhere H1 : IHexwhere H0 : IHex
{public static int Value{[MethodImpl(MethodImplOptions.AggressiveInlining)]get => H7.Value << 28 | H6.Value << 24 | H5.Value << 20 | H4.Value << 16 | H3.Value << 12 | H2.Value << 8 | H1.Value << 4 | H0.Value;}
}

这里我们给 Value 加了 [MethodImpl(MethodImplOptions.AggressiveInlining)] 确保这个方法会被编译器 inline。

如此一来,如果我们先表达一个 0x1234abcd,我们就可以用 Int<Hex1, Hex2, Hex3, Hex4, HexA, HexB, HexC, HexD> 来表达。

这里我们同样去 godbolt 看看 .NET 编译器给我们生成了怎样的代码:

Int`8[Hex1,Hex2,Hex3,Hex4,HexA,HexB,HexC,HexD]:get_Value():int (FullOpts):push     rbpmov      rbp, rspmov      eax, 0x1234ABCDpop      rbpret      

可以看到直接被编译器折叠成 0x123ABCD 了,没有比这更优的代码,属于是真正的零开销抽象。

那么性能方面放心了之后,我们就可以开始搞 Brainfuck 编译器了。

Brainfuck 编译器

Brainfuck 编译分为两个步骤,一个是解析 Brainfuck 源代码,一个是产生编译结果。

对于 Brainfuck 源代码的解析,可以说是非常的简单,从左到右扫描一遍源代码就可以,这里就不详细说了。问题是怎么产生编译结果呢?

这里我们选择使用类型来表达一个程序,因此编译结果自然也就是类型。

我们需要用类型来表达程序的结构。

基本操作

Brainfuck 程序离不开 4 个基本操作:

  • 移动指针
  • 操作内存
  • 输入
  • 输出

因此我们对此抽象出一套操作接口:

interface IOp
{abstract static int Run(int address, Span<byte> memory, Stream input, Stream output);
}

然后我们就可以定义各种操作了。

首先是移动指针,我们用两个泛型参数分别表达移动指针的偏移量和下一个操作:

struct AddPointer<Offset, Next> : IOpwhere Offset : INumwhere Next : IOp
{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span<byte> memory, Stream input, Stream output){return Next.Run(address + Offset.Value, memory, input, output);}
}

然后是操作内存:

struct AddData<Data, Next> : IOpwhere Data : INumwhere Next : IOp
{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span<byte> memory, Stream input, Stream output){memory.UnsafeAt(address) += (byte)Data.Value;return Next.Run(address, memory, input, output);}
}

我们 Brainfuck 不需要什么内存边界检查,因此这里我用了一个 UnsafeAt 扩展方法跳过边界检查:

internal static ref T UnsafeAt<T>(this Span<T> span, int address)
{return ref Unsafe.Add(ref MemoryMarshal.GetReference(span), address);
}

接下来就是输入和输出了,这个比较简单,直接操作 inputoutput 就行了:

struct OutputData<Next> : IOpwhere Next : IOp
{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span<byte> memory, Stream input, Stream output){output.WriteByte(memory.UnsafeAt(address));return Next.Run(address, memory, input, output);}
}struct InputData<Next> : IOpwhere Next : IOp
{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span<byte> memory, Stream input, Stream output){var data = input.ReadByte();if (data == -1){return address;}memory.UnsafeAt(address) = (byte)data;return Next.Run(address, memory, input, output);}
}

控制流

有了上面的 4 种基本操作之后,我们就需要考虑程序控制流了。

首先,我们的程序最终毕竟是要停下来的,因此我们定义一个什么也不干的操作:

struct Stop : IOp
{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span<byte> memory, Stream input, Stream output){return address;}
}

然后,Brainfuck 是支持循环的,这要怎么处理呢?其实也很简单,模拟 while (*ptr) { 这个操作就行了,也就是反复执行当前操作直到指针变成 0,然后跳到下一个操作去。

struct Loop<Body, Next> : IOpwhere Body : IOpwhere Next : IOp
{[MethodImpl(MethodImplOptions.AggressiveInlining)]public static int Run(int address, Span<byte> memory, Stream input, Stream output){while (memory.UnsafeAt(address) != 0){address = Body.Run(address, memory, input, output);}return Next.Run(address, memory, input, output);}
}

Hello World!

有了上面的东西,我们就可以用类型表达 Brainfuck 程序了。

我们来看看最基础的程序:Hello World!。

++++++++++[>+++++++>++++++++++>+++>+<<<<-]
>++.>+.+++++++..+++.>++.<<+++++++++++++++.
>.+++.------.--------.>+.>.

上面这个实现可能不是很直观,那我们换一种非常直观的实现:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
++++++++++++++++++++++++++++++++>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>
+++++++++++++++++++++++++++++++++
<<<<<<<<<<<
.>.>.>.>.>.>.>.>.>.>.>.

这段程序很粗暴的分别把内存从左到右写成 Hello World! 的每一位,然后把指针移回到开头后逐位输出。

不过这么看 Hello World! 还是太长了,不适合用来一上来就展示,我们换个简单点的输出 123

+++++++++++++++++++++++++++++++++++++++++++++++++
>
++++++++++++++++++++++++++++++++++++++++++++++++++
>
+++++++++++++++++++++++++++++++++++++++++++++++++++
<<
.>.>.

表达这个程序的类型自然就是:

AddData<49, AddPointer<1, AddData<50, AddPointer<1, AddData<51, // 分别设置 1 2 3
AddPointer<-2, // 指针移回开头
OutputData<AddPointer<1, OutputData<AddPointer<1, OutputData< // 输出
Stop>>>>>>>>>>> // 停止

这里为了简洁,我把数字全都带入了数字类型,不然会变得很长。例如实际上 49 应该表达为 Int<Hex0, Hex0, Hex0, Hex0, Hex0, Hex0, Hex3, Hex1>

那怎么运行呢?很简单:

AddData<49, AddPointer<1, AddData<50, AddPointer<1, AddData<51, AddPointer<-2, OutputData<AddPointer<1, OutputData<AddPointer<1, OutputData<Stop>>>>>>>>>>>.Run(0, stackalloc byte[8], Console.OpenStandardInput(), Console.OpenStandardOutput());

即可。

我们可以借助 C# 的 Type Alias,这样我们就不需要每次运行都打那么一大长串的类型:

using Print123 = AddData<49, AddPointer<1, AddData<50, AddPointer<1, AddData<51, AddPointer<-2, OutputData<AddPointer<1, OutputData<AddPointer<1, OutputData<Stop>>>>>>>>>>>;Print123.Run(0, stackalloc byte[8], Console.OpenStandardInput(), Console.OpenStandardOutput());

那我们上 godbolt 看看 .NET 给我们的 Brainfuck 程序产生了怎样的机器代码?

push     rbp
push     r15
push     r14
push     r13
push     rbx
lea      rbp, [rsp+0x20]
mov      rbx, rsi
mov      r15, r8
movsxd   rsi, edi
add      rsi, rbx
add      byte  ptr [rsi], 49 ; '1'
inc      edi
movsxd   rsi, edi
add      rsi, rbx
add      byte  ptr [rsi], 50 ; '2'
inc      edi
movsxd   rsi, edi
add      rsi, rbx
add      byte  ptr [rsi], 51 ; '3'
lea      r14d, [rdi-0x02]
movsxd   rsi, r14d
movzx    rsi, byte  ptr [rbx+rsi]
mov      rdi, r15
mov      rax, qword ptr [r15]
mov      r13, qword ptr [rax+0x68]
call     [r13]System.IO.Stream:WriteByte(ubyte):this
inc      r14d
movsxd   rsi, r14d
movzx    rsi, byte  ptr [rbx+rsi]
mov      rdi, r15
call     [r13]System.IO.Stream:WriteByte(ubyte):this
inc      r14d
movsxd   rsi, r14d
movzx    rsi, byte  ptr [rbx+rsi]
mov      rdi, r15
call     [r13]System.IO.Stream:WriteByte(ubyte):this
mov      eax, r14d
pop      rbx
pop      r13
pop      r14
pop      r15
pop      rbp
ret      

这不就是

*(ptr++) = '1';
*(ptr++) = '2';
*ptr = '3';
ptr -= 2;
WriteByte(*(ptr++));
WriteByte(*(ptr++));
WriteByte(*ptr);

吗?可以看到我们代码里的抽象全都被 .NET 给优化干净了。

而前面那个不怎么直观的 Hello World! 代码则编译出:

AddData<8, Loop<AddPointer<1, AddData<4, Loop<AddPointer<1, AddData<2, AddPointer<1, AddData<3, AddPointer<1, AddData<3, AddPointer<1, AddData<1, AddPointer<-4, AddData<-1, Stop>>>>>>>>>>,AddPointer<1, AddData<1, AddPointer<1, AddData<1, AddPointer<1, AddData<-1, AddPointer<2, AddData<1,Loop<AddPointer<-1, Stop>,AddPointer<-1, AddData<-1, Stop>>>>>>>>>>>>>>,AddPointer<2, OutputData<AddPointer<1, AddData<-3, OutputData<AddData<7, OutputData<OutputData<AddData<3, OutputData<AddPointer<2, OutputData<AddPointer<-1, AddData<-1, OutputData<AddPointer<-1, OutputData<AddData<3, OutputData<AddData<-6, OutputData<AddData<-8, OutputData<AddPointer<2, AddData<1, OutputData<AddPointer<1, AddData<2, OutputData<Stop>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

JIT 编译

如果我们想以 JIT 的形式运行 Brainfuck 代码,那如何在运行时生成类型然后运行代码呢?我们在 .NET 中有完善的反射支持,因此完全可以做到运行时创建类型。

比如根据数字来生成数字类型:

var type = GetNum(42);static Type GetHex(int hex)
{return hex switch{0 => typeof(Hex0),1 => typeof(Hex1),2 => typeof(Hex2),3 => typeof(Hex3),4 => typeof(Hex4),5 => typeof(Hex5),6 => typeof(Hex6),7 => typeof(Hex7),8 => typeof(Hex8),9 => typeof(Hex9),10 => typeof(HexA),11 => typeof(HexB),12 => typeof(HexC),13 => typeof(HexD),14 => typeof(HexE),15 => typeof(HexF),_ => throw new ArgumentOutOfRangeException(nameof(hex)),};
}static Type GetNum(int num)
{var hex0 = num & 0xF;var hex1 = (num >>> 4) & 0xF;var hex2 = (num >>> 8) & 0xF;var hex3 = (num >>> 12) & 0xF;var hex4 = (num >>> 16) & 0xF;var hex5 = (num >>> 20) & 0xF;var hex6 = (num >>> 24) & 0xF;var hex7 = (num >>> 28) & 0xF;return typeof(Int<,,,,,,,>).MakeGenericType(GetHex(hex7), GetHex(hex6), GetHex(hex5), GetHex(hex4), GetHex(hex3), GetHex(hex2), GetHex(hex1), GetHex(hex0));
}

同理也可以用于生成各种程序结构上。

最后我们只需要对构建好的类型进行反射然后调用 Run 方法即可:

var run = (EntryPoint)Delegate.CreateDelegate(typeof(EntryPoint), type.GetMethod("Run")!);
run(0, memory, input, output);delegate int EntryPoint(int pc, Span<byte> memory, Stream input, Stream output);

AOT 编译

那如果我不想 JIT,而是想 AOT 编译出来一个可执行文件呢?

你会发现,因为编译出的东西是类型,因此我们不仅可以在 JIT 环境下跑,还能直接把类型当作程序 AOT 编译出可执行文件!只需要编写一个入口点方法调用 Run 即可:

using HelloWorld = AddData<8, Loop<AddPointer<1, AddData<4, Loop<AddPointer<1, AddData<2, AddPointer<1, AddData<3, AddPointer<1, AddData<3, AddPointer<1, AddData<1, AddPointer<-4, AddData<-1, Stop>>>>>>>>>>,AddPointer<1, AddData<1, AddPointer<1, AddData<1, AddPointer<1, AddData<-1, AddPointer<2, AddData<1,Loop<AddPointer<-1, Stop>,AddPointer<-1, AddData<-1, Stop>>>>>>>>>>>>>>,AddPointer<2, OutputData<AddPointer<1, AddData<-3, OutputData<AddData<7, OutputData<OutputData<AddData<3, OutputData<AddPointer<2, OutputData<AddPointer<-1, AddData<-1, OutputData<AddPointer<-1, OutputData<AddData<3, OutputData<AddData<-6, OutputData<AddData<-8, OutputData<AddPointer<2, AddData<1, OutputData<AddPointer<1, AddData<2, OutputData<Stop>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;static void Main()
{HelloWorld.Run(0, stackalloc byte[16], Console.OpenStandardInput(), Console.OpenStandardOutput());
}

然后调用 AOT 编译:

dotnet publish -c Release -r linux-x64 /p:PublishAot=true /p:IlcInstructionSet=native /p:OptimizationPreference=Speed

上面的 /p:IlcInstructionSet=native 即 C++ 世界里的 -march=nativeOptimizationPreference=Speed 则是 -O2

运行编译后的程序就能直接输出 Hello World!

性能测试

这里我们采用一段用 Brainfuck 编写的 Mandelbrot 程序进行性能测试,代码见 Pastebin。

它运行之后会在屏幕上输出:

mandelbrot_brainfuck

这段程序编译出来的类型也是非常的壮观:

mandelbrot_brainfuck_type

去掉所有空格之后类型名称足足有 165,425 个字符!

这里我们采用 5 种方案来跑这段代码:

  • C 解释器:C 语言编写的 Brainfuck 解释器直接运行
  • GCC:用 Brainfuck 翻译器把 Brainfuck 代码翻译到 C 语言后,用 gcc -O3 -march=native 编译出可执行程序后运行
  • Clang:用 Brainfuck 翻译器把 Brainfuck 代码翻译到 C 语言后,用 clang -O3 -march=native 编译出可执行程序后运行
  • .NET JIT:通过 JIT 现场生成类型后运行,统计之前会跑几轮循环预热
  • .NET AOT:通过 .NET NativeAOT 编译出可执行程序后运行

测试环境:

  • 系统:Debian GNU/Linux 12 (bookworm)
  • CPU:13th Gen Intel(R) Core(TM) i7-13700K
  • RAM:CORSAIR DDR5-6800MHz 32Gx2

运行 10 次取最优成绩,为了避免输出影响性能,所有输出重定向到 /dev/null

得出的性能测试结果如下:

项目 运行时间(毫秒) 排名 比例
C 解释器 4874.6587 5 5.59
GCC 901.0225 3 1.03
Clang 881.7177 2 1.01
.NET JIT 925.1596 4 1.06
.NET AOT 872.2287 1 1.00

最后 .NET AOT 在这个项目里取得了最好的成绩,当然,这离不开 .NET 类型系统层面的零开销抽象。

项目地址

该项目以 MIT 协议开源,欢迎 star。

项目开源地址:https://github.com/hez2010/Brainfly

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/877439.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

01-hytest简介

参考教程:http://vip3.byhy.net/auto/pyatframework/hytest-01/ 安装:pip3 install hytest hytest对应的python版本要求:Python3.6或者更高版本 运行hytest自动化测试:打开命令行窗口 切换到自动化代码根目录 运行hytest,运行hytest其实就是运行python -m hytest.run 如果…

【编码】自定义通信协议——实现零拷贝文件传输

前言 上一篇随笔,介绍了如何扩展自定义协议的请求类型。本篇随笔我将介绍如何基于这个自定义协议来实现文件传输,其中将涉及数据分片和零拷贝 在设计自定义协议之前,我们首先了解一下HTTP协议是如何处理文件传输的。 HTTP协议的实现方式 在这里,我们主要讨论应用最广泛的HT…

研发的护城河到底是什么?

0 你的问题,我知道! 和大厂朋友聊天,他感叹原来努力干活,做靠谱研发,积累职场经验,干下来,职业发展一般问题不大。而如今大厂“年轻化”,靠谱再不能为自己续航,企业似乎也不愿意持续为经验买单。 在这不确定时代,职业发展中有无硬通货? 更长远职业发展角度:要抓住机…

冶炼金属

暴力做法 #include<iostream> #include<vector> using namespace std;void solve() {int n; cin >> n;vector<int>a(n), b(n);for (int i = 0; i < n; i++){cin >> a[i] >> b[i];}for (int i = 1; i < 1e6; i++)//从小到大,找最小值…

昆明理工大学2025年硕士研究生调剂汇总表1月31日更新

这是今年昆明理工大学调剂信息,目前只更新了部分学院的部分专业,后续会持续更新。 【腾讯文档】昆明理工大学2025年硕士研究生调剂汇总表 https://docs.qq.com/sheet/DZERIbnpPb3JjeHFO

子串简写

二分法: 要求:pc2-pc1+1>=k 变形:i(pc2)-k+1>=pc1#include <iostream> #include <string> #include<vector> #define int long long using namespace std; void solve() {int k;cin >> k;char c1, c2;cin >> c1 >> c2;string s;…

傻瓜教程 一步一步把blazor项目发布到linux(debian12,nginx反向代理设置,net8,net9适用)

接触blazor有一段时间了,感觉非常好用,特别适合企业内部开发用。开发效率高,界面优美,重要得是会c#的朋友不用再去学习js等前端技术了,虽然平时也看得懂js,html,css,但要自己写还是需要从头去学习的,不想再浪费精力去学习,毕竟会的再多,杂而不精也没什么意义。而自己…

[Tools] 发布代码

我们已经将我们的代码开源到了 github 上面,但是如果是其他开发者想要使用我们的库,还需要去 github 上面手动下载下来,添加到他们的项目里面,这样是非常低效的一种方式。 npm 的出现解决了这个问题,npm 是前端领域非常出名的一个包的托管平台,提供了代码的托管和检索以及…

威海市,杨文召——老赖!!!

威海市,杨文召——老赖!!!

思科静态路由(包含小实验)

思科静态路由 路由:从源主机到目标主机的转发过程 路由器是根据路由表转发数据 路由表:路由器中维护的路由条目的集合 路由器根据路由表做路径选择 路由表的形成 直连网段 本地接口配置IP地址和子网掩码,端口开启状态,形成直连路由 非直连路由 不是本地接口配置IP地址和子网…

PKUWC2025 D2T1

其实是场上的想到的做法,但是当时被卡 corner case 了 QaQ。 注意到,我们其实可以 \(O(1)\) 次 query 求出 \(x\) 和 \(y\) 的距离。具体地,我们再找三个点,现在有 \(5\) 个点,\(10\) 个距离,而我们又可以 query \(10\) 次,正好可以解出两两距离。这里如果 \(n\le 4\) 特…

MATLAB程序测试

% Interference cancellation % 悦博特北京科技有限公司 lxdawn@163.com %clear all, close all, clctime = 0:0.1:10;r = sin(time*4*pi);% Random initialisation of the W weight and b biasR = length(time); % number of inputsS = R;% p parasite signalp = randn(size(r…