컴파일러가 정확히 어떻게 할 것인지는 그 자신만이 알고 있습니다. 최신 컴파일러에는 놀라운 휴리스틱이 내장되어 있습니다. 그들은 평균적인 코더에 적응하고 그가 필요로 하는 것을 이미 더 잘 알고 있습니다. 컴파일러가 할 수 있는 최선의 방법은 짧은 함수 로 간단하고 명확한 코드를 작성하는 것입니다. 컴파일러가 많은 노드 함수로 구성된 소스 코드 그래프를 분석하여 최종 프로그램을 빌드하는 것이 훨씬 쉽고 효율적입니다. 필요한 기능이 올바른 위치에 인라인되기 때문에 이는 성능에 긍정적인 영향을 미칠 뿐입니다.
확실히 맞아.
MQL5에 대해 이야기하면 10-20-30년 전의 "최적화" 방법을 잊을 수 있습니다. 가능한 한 읽기 쉬운 코드를 작성해야 합니다. 품질의 척도는 해커의 종소리와 휘파람과 순수한 과시가 아니라 바로 그 사람입니다.
왜요?
예, 컴파일러는 5-10주기의 코드 재정렬을 거치기 때문에 수십 가지 최적화 패턴의 사용은 말할 것도 없고 놀랍도록 명확하고 간략하게 그래프의 끝 부분을 표시합니다.
MQL5 컴파일러는 종소리와 휘파람으로 +2% 속도를 만들려는 인간의 시도에서 재미있다는 것을 알게 되었습니다.
관심 있는 사람들을 위해 4개의 어셈블러 명령어 수학적 계산 은 분기 없이 진행되며 하나의 명령(128비트 데이터)에 대해 두 개의 근이 한 번에 계산됩니다.
분명히 하나의 조건을 먼저 추적하고 실행의 경우 함수를 종료한 다음 다른 조건을 확인하고 실행의 경우 떠나는 것이 더 쉽습니다. 논리적 "또는"("and"와 쉽게 혼동될 수 있음)을 통해 복잡한 조건의 결과로 어떤 일이 발생하는지 파악하고 두 반환 옵션을 모두 추적하는 것보다.
아래에서 "디버깅에 대한 정당화"를 읽는 것이 매우 재미있습니다. 왜냐하면 이것은 그러한 코드가 훨씬 더 이해하기 쉽다는 것을 의미하기 때문입니다(그렇지 않으면 디버깅에 있는 이유는 무엇입니까?).
"Apotheosis" 나는 fxsaber의 한 표현을 고려하는데, 그 자신이 어떻게 작동하는지 말할 수 없었고 단순히 "코드가 반복적으로 테스트되었고 작동합니다."라고 말했습니다. 제 생각에는 다음과 같이 되어서는 안 됩니다.
이 코드는 otfFilingType 순서 를 채울 수 있는지 확인하고 strSymbol 기호에서 사용할 수 있으면 반환하고 그렇지 않으면 올바른지 확인합니다.
나는 그것이 어떻게 작동하는지 잘 이해할 수 없습니다. 그리고 저는 fxsaber의 권위에만 의존합니다.
누군가 설명해줄까요?
한 번 앉아서 단계적으로 분해했는데 종이가있는 펜이 필요했던 것 같습니다)
이 분석이 유용한 이유 - 열거형의 구조가 변경되면 모든 것이 깨질 것이라는 것을 깨달았습니다. 열거 값의 정수 비율이 사용됩니다(열거 개념에 따라 캡슐화되어야 함). 나는 이것을 스스로 피하려고 노력합니다. 최대 관계는 다소간입니다. WinAPI를 다루는 사람들에게는 아마도 익숙할 것입니다.
코드는 매우 간단하고 짧습니다( description ). FP에 쓰면 비교해보는 재미가 쏠쏠하다.
물론이죠. FP 스타일의 C#:
using System;
using System.Linq;
using System.Collections.Generic;
using static FeesCalculator.FeesCalcs;
using System.Text;
using static System.Math;
namespace FeesCalculator
{
publicstaticclass EnumerableExt
{
/// <summary>/// Call of function 'action' for each element if sequence (mapping)./// </summary>/// <typeparam name="T"></typeparam>/// <param name="seq">Enumerable sequence</param>/// <param name="action">Need action</param>publicstaticvoid Map<T>( this IEnumerable<T> seq, Action<T> action)
{
foreach (var item in seq)
action(item);
}
}
/// <summary>/// Инвестиционный результат/// </summary>public record AccountRecord
{
publicdouble Investor { get; init; }
publicdouble Manager { get; init; }
publicdouble Summary { get; init; }
publicstatic AccountRecord operator *(AccountRecord r, double v)
{
returnnew AccountRecord
{
Investor = r.Investor * v,
Manager = r.Manager * v,
Summary = r.Summary * v
};
}
publicoverridestring ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append(nameof(Investor)).Append( " = " ).Append(Investor.ToString( "F4" )).Append( ' ' );
sb.Append(nameof(Manager)).Append( " = " ).Append(Manager.ToString( "F4" )).Append( ' ' );
sb.Append(nameof(Summary)).Append( " = " ).Append(Summary.ToString( "F4" ));
return sb.ToString();
}
}
/// <summary>/// Параметры оферты/// </summary>public record PammSet
{
/// <summary>/// Доходность за расчетный период/// </summary>publicdouble Perfomance { get; init; }
/// <summary>/// Вознаграждение управляющего/// </summary>publicdouble Gift { get; init; }
/// <summary>/// Количество расчетных периодов/// </summary>publicint N { get; init; }
/// <inheritdoc/>publicoverridestring ToString()
{
StringBuilder str = new StringBuilder();
str.Append( "Доходность за расчетный период: " ).Append((Perfomance * 100.0 ).ToString( "F1" )).Append( "%\t\n" );
str.Append( "Вознаграждение управляющего: " ).Append((Gift * 100.0 ).ToString( "F1" )).Append( "%\t\n" );
str.Append( "Количество расчетных периодов: " ).Append(N);
return str.ToString();
}
}
/// <summary>/// Формулы расчета/// </summary>publicclass FeesCalcs
{
/// <summary>/// Если управляющий снимает деньги в конце каждого расчетного периода/// </summary>/// <param name="Performance"></param>/// <param name="Gift"></param>/// <param name="N"></param>/// <returns></returns>publicstatic AccountRecord Set1(PammSet set)
{
Func< double > investor = () => Pow( 1 + (set.Perfomance) * ( 1 - set.Gift), set.N);
Func< double > manager = () => (investor() - 1 ) * set.Gift / ( 1 - set.Gift);
AccountRecord ac = new AccountRecord
{
Investor = investor(),
Manager = manager(),
Summary = investor() + manager()
};
return ac;
}
/// <summary>/// Если ничего не делалось в расчетные периоды/// </summary>/// <param name="Performance"></param>/// <param name="Gift"></param>/// <param name="N"></param>/// <returns></returns>publicstatic AccountRecord Set2(PammSet set)
{
Func< double > summary = () => Pow(set.Perfomance + 1 , set.N);
Func< double > manager = () => (summary() - 1 ) * set.Gift;
double s = summary();
double d = manager();
AccountRecord ac = new AccountRecord
{
Summary = summary(),
Manager = manager(),
Investor = summary() - manager()
};
return ac;
}
/// <summary>/// Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их/// </summary>/// <param name="Performance"></param>/// <param name="Gift"></param>/// <param name="N"></param>/// <returns></returns>publicstatic AccountRecord Set3(PammSet set)
{
Func< double > summary = () => Pow(set.Perfomance + 1 , set.N);
Func< double > investor = () => Pow( 1 + (set.Perfomance) * ( 1 - set.Gift), set.N);
returnnew AccountRecord
{
Summary = summary(),
Investor = investor(),
Manager = summary() - investor()
};
}
/// <summary>/// Сопостовление: описание - функция расчета/// </summary>publicstatic readonly Dictionary< string , Func<PammSet, AccountRecord>> DescrToAccountRecord = new Dictionary< string , Func<PammSet, AccountRecord>>
{
{ "Если управляющий снимает деньги в конце каждого расчетного периода (1)" , Set1 },
{ "Если ничего не делалось в расчетные периоды (2)" , Set2 },
{ "Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их (3)" , Set3 },
};
}
class Program
{
staticvoid Main( string [] args)
{
var _ = args.Select((a) => double .Parse(a)).Take( 3 ).ToList();
PammSet pamm = new PammSet
{
Perfomance = _[ 0 ]/ 100.0 ,
Gift = _[ 1 ]/ 100.0 ,
N = ( int )_[ 2 ]
};
Console.WriteLine($ "Данные для инвестиционного счета со следующими характеристиками:\n\n{pamm}\n" );
Console.WriteLine( "Конечный результат для Инвестора, Управляющего и Суммарно:\n" );
Func<KeyValuePair< string , Func<PammSet, AccountRecord>>, string > f = (kvp) => kvp.Key + ".\n" + kvp.Value(pamm).ToString() + "\n" ;
Enumerable.Select(DescrToAccountRecord, f).Map((s) => Console.WriteLine(s));
Console.WriteLine(pamm);
Func< int , PammSet> toPamm = (n) => new PammSet { Perfomance = pamm.Perfomance, Gift = pamm.Gift, N = n };
Func<PammSet, IEnumerable<AccountRecord>> toAccs = (n) => DescrToAccountRecord.Select((s) => s.Value(n));
var accounts = Enumerable.Repeat( 1 , pamm.N).Select((a, b) => a + b).Select(toPamm).Select(toAccs);
Func<AccountRecord, string > toString = (ar) => ar.Investor.ToString( "F2" ) + "\t" +
ar.Manager.ToString( "F2" ) + "\t" +
ar.Summary.ToString( "F2" ) + "\t" ;
int period = default ;
accounts.Map
(
(en) =>
{
Console.Write($ "{++period}:\t" );
en.Map((ar) =>
Console.Write(toString(ar)));
Console.WriteLine();
}
);
}
}
}
FP는 물론 매우 비뚤어져 있습니다.(FP 코더가 비뚤어진 코더를 열등한 FP 언어 C #으로 작성했기 때문에) 그러나 목표는 현대 OOP YaP에서 FP에서 가능하다는 것을 보여주는 것입니다. 물론 제한적이지만 F #이나 Haskell은 아니지만 FP 스타일로 쓰는 것을 금지하는 사람은 없습니다. 불변성, 고차 함수, 클로저, 매핑 등을 사용하십시오. - 모든 것이 가능합니다. 이 코드 만이 어떤 이유로 이상적이지 않습니다.
추신 코드의 일반적인 구조는 의도적으로 원본을 모방합니다. FP에서 좋은 방법은 완전히 다른 방식으로 필요하지만 복잡한 개체와 foreach가 지도를 모방하지 않고 아티스트는 최선을 다해 그렸습니다.
#property strict#property script_show_inputsinputdouble inPerformance = 100 ; // Сколько процентов дает система за периодinputdouble inGift = 50 ; // Награда управляющего в процентах (< 100)inputint inN = 12 ; // Сколько расчетных периодовstruct BASE
{
double Investor;
double Manager;
double Summary;
// Если управляющий снимает деньги в конце каждого расчетного периода.void Set1( constdouble Performance, constdouble Gift, constint N )
{
this .Investor = :: MathPow ( 1 + (Performance - 1 )* ( 1 - Gift), N);
this .Manager = ( this .Investor - 1 ) * Gift / ( 1 - Gift);
this .Summary = this .Investor + this .Manager;
return ;
}
// Если ничего не делалось в расчетные периоды.void Set2( constdouble Performance, constdouble Gift, constint N )
{
this .Summary = :: MathPow (Performance, N);
this .Manager = ( this .Summary - 1 ) * Gift;
this .Investor = this .Summary - this .Manager;
return ;
}
// Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их.void Set3( constdouble Performance, constdouble Gift, constint N )
{
this .Summary = :: MathPow (Performance, N);
this .Investor = :: MathPow ( 1 + (Performance - 1 )* ( 1 - Gift), N);
this .Manager = this .Summary - this .Investor;
return ;
}
voidoperator *=( constdouble Deposit = 1 )
{
this .Investor *= Deposit;
this .Manager *= Deposit;
this .Summary *= Deposit;
return ;
}
#define TOSTRING(A) #A + " = " + :: DoubleToString (A, 4 ) + " "string ToString( constbool FlagName = true ) const
{
return (FlagName ? TOSTRING(Investor) + TOSTRING(Manager) + TOSTRING(Summary)
: :: StringFormat ( "||%-12.4f||%-12.4f||%-12.4f||" , this .Investor, this .Manager, this .Summary));
}
#undef TOSTRING
};
struct PAMM
{
double Performance; // Доходность за расчетный периодdouble Gift; // Вознагражение управляющего.int N; // Сколько расчетных периодов.
BASE Base1; // Если управляющий снимает деньги в конце каждого расчетного периода.
BASE Base2; // Если ничего не делалось в расчетные периоды.
BASE Base3; // Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их.void Set( constdouble dPerformance, constdouble dGift, constint iN )
{
this .Performance = dPerformance;
this .Gift = dGift;
this .N = iN;
this .Base1.Set1( 1 + this .Performance / 100 , this .Gift / 100 , this .N);
this .Base2.Set2( 1 + this .Performance / 100 , this .Gift / 100 , this .N);
this .Base3.Set3( 1 + this .Performance / 100 , this .Gift / 100 , this .N);
}
voidoperator *=( constdouble Deposit = 1 )
{
this .Base1 *= Deposit;
this .Base2 *= Deposit;
this .Base3 *= Deposit;
return ;
}
string GetDescription( void ) const
{
return ( "Доходность за расчетный период " + :: DoubleToString ( this .Performance, 1 ) + "%\n" +
"Вознагражение управляющего " + :: DoubleToString ( this .Gift, 1 ) + "%\n" +
"Количество расчетных периодов. " + ( string ) this .N);
}
string ToString( constbool FlagName = true , constbool FlagDescription = true ) const
{
return (FlagDescription ? "Данные для инвестиционного счета со следующими характеристиками:\n\n" + this .GetDescription() +
"\n\nКонечный результат для Инвестора, Управляющего и Суммарно:""\n\nЕсли управляющий снимает деньги в конце каждого расчетного периода (1).\n" + this .Base1.ToString(FlagName) +
"\n\nЕсли ничего не делалось в расчетные периоды (2).\n" + this .Base2.ToString(FlagName) +
"\n\nЕсли управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их (3).\n" + this .Base3.ToString(FlagName)
: this .Base1.ToString(FlagName) + this .Base2.ToString(FlagName) + this .Base3.ToString(FlagName));
}
};
voidOnStart ()
{
PAMM Pamm;
Pamm.Set(inPerformance, inGift, inN);
Print (Pamm.ToString());
string Str = Pamm.GetDescription() + "\n " ;
for ( int i = 1 ; i <= 3 ; i++ )
Str += :: StringFormat ( "||%-12s||%-12s||%-12s||" , "Investor" + ( string )i, "Manager" + ( string )i, "Summary" + ( string )i);
for ( int i = 1 ; i <= inN; i++ )
{
Pamm.Set(inPerformance, inGift, i);
Str += StringFormat ( "\n%-2d:" , i) + Pamm.ToString( false , false );
}
Print (Str);
}
공식적으로 이것은 OOP입니다. 그러나 가장 원시적인 부분입니다. 그러나 그는 그녀 없이는 살 수 없었습니다. 아마도 어리석게도 뇌가 다른 것으로 바뀌었고 나는 이것을 리벳으로 고정합니다.
"Apotheosis" 나는 fxsaber의 한 표현을 고려하는데, 그 자신이 어떻게 작동하는지 말할 수 없었고 단순히 "코드가 반복적으로 테스트되었고 작동합니다."라고 말했습니다. 제 생각에는 다음과 같이 되어서는 안 됩니다.
이 코드는 otfFilingType 순서 를 채울 수 있는지 확인하고 strSymbol 기호에서 사용할 수 있으면 반환하고 그렇지 않으면 올바른지 확인합니다.
나는 그것이 어떻게 작동하는지 잘 이해할 수 없습니다. 그리고 저는 fxsaber의 권위에만 의존합니다.
누군가 설명해줄까요?
이해하려면 반환에서 구성 요소로 이 성가신 표현을 구문 분석하는 것으로 충분합니다.
"목적이 수단을 정당화합니다"의 예
그건 그렇고, 이 코드는 그냥 스파게티처럼 보이지만 이것이 아마도 지금 코드 기반에서 가장 유용한 작성자이고 나 자신이 그의 코드를 있는 그대로 사용한다는 사실을 감안할 때 다른 사람이 비판하게 하십시오.
그건 그렇고, 이 코드는 그냥 스파게티처럼 보이지만 이것이 아마도 지금 코드 기반에서 가장 유용한 작성자이고 나 자신이 그의 코드를 있는 그대로 사용한다는 사실을 감안할 때 다른 사람이 비판하게 하십시오.
나는 비판에 대해 말하는 것이 아니라 그것이 줄 수 있는 것을 알아내려고 노력하고 있습니다. 하지만 대화에서 알 수 있듯이 @fxsaber 자신도 솔직히 인정했습니다.
추신: 작은 괴물 의 원래 버전이 더 시각적으로 보였을 가능성이 큽니다.)
추신: 작은 괴물 의 원래 버전이 더 시각적으로 보였을 가능성이 큽니다.)
ZIP 에서 가져옴(최초 릴리스 포함).
그러나 그 후, 점점 더 많은 기능이 필요했고 코드는 천천히 고기로 무성해졌습니다. 채우기 기능에서는 달랐습니다. 거기에는 아무것도 자라지 않았고 바로 기록되었습니다.
컴파일러가 정확히 어떻게 할 것인지는 그 자신만이 알고 있습니다. 최신 컴파일러에는 놀라운 휴리스틱이 내장되어 있습니다. 그들은 평균적인 코더에 적응하고 그가 필요로 하는 것을 이미 더 잘 알고 있습니다. 컴파일러가 할 수 있는 최선의 방법은 짧은 함수 로 간단하고 명확한 코드를 작성하는 것입니다. 컴파일러가 많은 노드 함수로 구성된 소스 코드 그래프를 분석하여 최종 프로그램을 빌드하는 것이 훨씬 쉽고 효율적입니다. 필요한 기능이 올바른 위치에 인라인되기 때문에 이는 성능에 긍정적인 영향을 미칠 뿐입니다.
확실히 맞아.
MQL5에 대해 이야기하면 10-20-30년 전의 "최적화" 방법을 잊을 수 있습니다. 가능한 한 읽기 쉬운 코드를 작성해야 합니다. 품질의 척도는 해커의 종소리와 휘파람과 순수한 과시가 아니라 바로 그 사람입니다.
왜요?
예, 컴파일러는 5-10주기의 코드 재정렬을 거치기 때문에 수십 가지 최적화 패턴의 사용은 말할 것도 없고 놀랍도록 명확하고 간략하게 그래프의 끝 부분을 표시합니다.
MQL5 컴파일러는 종소리와 휘파람으로 +2% 속도를 만들려는 인간의 시도에서 재미있다는 것을 알게 되었습니다.
이것은 결국 예술 작품입니다. 어셈블러 명령어의 4번 호출에 대해 8개의 루트가 계산됩니다. 한 번의 호출로 두 개의 이중 숫자가 계산되었습니다.관심 있는 사람들을 위해 4개의 어셈블러 명령어 수학적 계산 은 분기 없이 진행되며 하나의 명령(128비트 데이터)에 대해 두 개의 근이 한 번에 계산됩니다.
이 코드는 다음 어셈블러 SSE 코드로 바뀝니다.
일반적인 결론: MQL5의 수학은 완벽한 최적화 덕분에 승리했습니다. 지는 것은 배열이 아니라 수학이 이깁니다.
왜 그런 겁니까 ?
반대의 경우 두 개의 "if"가 있습니다. "or" 연산자보다 훨씬 쉽습니다.
분명히 하나의 조건을 먼저 추적하고 실행의 경우 함수를 종료한 다음 다른 조건을 확인하고 실행의 경우 떠나는 것이 더 쉽습니다. 논리적 "또는"("and"와 쉽게 혼동될 수 있음)을 통해 복잡한 조건의 결과로 어떤 일이 발생하는지 파악하고 두 반환 옵션을 모두 추적하는 것보다.
아래에서 "디버깅에 대한 정당화"를 읽는 것이 매우 재미있습니다. 왜냐하면 이것은 그러한 코드가 훨씬 더 이해하기 쉽다는 것을 의미하기 때문입니다(그렇지 않으면 디버깅에 있는 이유는 무엇입니까?).
"Apotheosis" 나는 fxsaber의 한 표현을 고려하는데, 그 자신이 어떻게 작동하는지 말할 수 없었고 단순히 "코드가 반복적으로 테스트되었고 작동합니다."라고 말했습니다. 제 생각에는 다음과 같이 되어서는 안 됩니다.
이 코드는 otfFilingType 순서 를 채울 수 있는지 확인하고 strSymbol 기호에서 사용할 수 있으면 반환하고 그렇지 않으면 올바른지 확인합니다.
나는 그것이 어떻게 작동하는지 잘 이해할 수 없습니다. 그리고 저는 fxsaber의 권위에만 의존합니다.
누군가 설명해줄까요?
한 번 앉아서 단계적으로 분해했는데 종이가있는 펜이 필요했던 것 같습니다)
이 분석이 유용한 이유 - 열거형의 구조가 변경되면 모든 것이 깨질 것이라는 것을 깨달았습니다. 열거 값의 정수 비율이 사용됩니다(열거 개념에 따라 캡슐화되어야 함). 나는 이것을 스스로 피하려고 노력합니다. 최대 관계는 다소간입니다. WinAPI를 다루는 사람들에게는 아마도 익숙할 것입니다.
이해하려면 반환에서 구성 요소로 이 성가신 표현을 구문 분석하는 것으로 충분합니다.
네, 제가 시도한 것입니다. 하지만 분해할 동기가 충분하지 않았습니다...
확실히 맞아.
MQL5에 대해 이야기하면 10-20-30년 전의 "최적화" 방법을 잊을 수 있습니다. 가능한 한 읽기 쉬운 코드를 작성해야 합니다. 품질의 척도는 해커의 종소리와 휘파람과 순수한 과시가 아니라 바로 그 사람입니다.
정확히. 나는 오래전에 이런 결론에 도달했다. 그러나 컴파일러가 더 잘할 것이라고 생각하기 때문이 아닙니다. 그리고 코드에서 문제의 주요 원인은 사람 자신이기 때문에 가능한 한 코드를 작성해야 코드가 최대한 단순하고 투명해집니다.
"투명하게" 수행할 수 없는 경우에는 자세한 설명이 필요하며, 그렇지 않은 경우에는 해당되지 않습니다.
글쎄요, 컴파일러는... 충분히 효율적이지 못하더라도 프로그램 작업의 일부 측면을 고려하지 않는다는 사실 때문에 잠재적인 버그보다 문제가 덜합니다.
코드는 매우 간단하고 짧습니다( description ). FP에 쓰면 비교해보는 재미가 쏠쏠하다.
물론이죠. FP 스타일의 C#:
FP는 물론 매우 비뚤어져 있습니다.(FP 코더가 비뚤어진 코더를 열등한 FP 언어 C #으로 작성했기 때문에) 그러나 목표는 현대 OOP YaP에서 FP에서 가능하다는 것을 보여주는 것입니다. 물론 제한적이지만 F #이나 Haskell은 아니지만 FP 스타일로 쓰는 것을 금지하는 사람은 없습니다. 불변성, 고차 함수, 클로저, 매핑 등을 사용하십시오. - 모든 것이 가능합니다. 이 코드 만이 어떤 이유로 이상적이지 않습니다.
추신 코드의 일반적인 구조는 의도적으로 원본을 모방합니다. FP에서 좋은 방법은 완전히 다른 방식으로 필요하지만 복잡한 개체와 foreach가 지도를 모방하지 않고 아티스트는 최선을 다해 그렸습니다.
물론이죠. FP 스타일의 C#:
문법에 대한 습관과 지식의 문제라는 것은 이해하지만, 제가 원저작자인데도 코드를 입력하는 것이 매우 어렵습니다.
거래, 자동 거래 시스템 및 거래 전략 테스트에 관한 포럼
OOP에 대한 흥미로운 해석
fxsaber , 2021.01.29 13:39
깔개.
공식적으로 이것은 OOP입니다. 그러나 가장 원시적인 부분입니다. 그러나 그는 그녀 없이는 살 수 없었습니다. 아마도 어리석게도 뇌가 다른 것으로 바뀌었고 나는 이것을 리벳으로 고정합니다.