Skip to content

Commit a096f4f

Browse files
authored
✨ mulSqrt (#1452)
1 parent 093d219 commit a096f4f

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

docs/utils/fixedpointmathlib.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,14 @@ Returns the cube root of `x`, denominated in `WAD`, rounded down.
536536
Formally verified by xuwinnie:
537537
https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf
538538

539+
### mulSqrt(uint256,uint256)
540+
541+
```solidity
542+
function mulSqrt(uint256 x, uint256 y) internal pure returns (uint256 z)
543+
```
544+
545+
Returns `sqrt(x * y)`. Also called the geometric mean.
546+
539547
### factorial(uint256)
540548

541549
```solidity

src/utils/FixedPointMathLib.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,16 @@ library FixedPointMathLib {
895895
}
896896
}
897897

898+
/// @dev Returns `sqrt(x * y)`. Also called the geometric mean.
899+
function mulSqrt(uint256 x, uint256 y) internal pure returns (uint256 z) {
900+
if (x == y) return x;
901+
uint256 p = rawMul(x, y);
902+
if (y == rawDiv(p, x)) return sqrt(p);
903+
for (z = saturatingMul(rawAdd(sqrt(x), 1), rawAdd(sqrt(y), 1));; z = avg(z, p)) {
904+
if ((p = fullMulDivUnchecked(x, y, z)) >= z) break;
905+
}
906+
}
907+
898908
/// @dev Returns the factorial of `x`.
899909
function factorial(uint256 x) internal pure returns (uint256 z) {
900910
/// @solidity memory-safe-assembly

test/FixedPointMathLib.t.sol

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,4 +2294,139 @@ contract FixedPointMathLibTest is SoladyTest {
22942294
function mul(uint256 x, uint256 y) public pure returns (uint256) {
22952295
return x * y;
22962296
}
2297+
2298+
function testMulSqrtSmall(uint256 x, uint256 y) public {
2299+
x = _bound(x, 0, 2 ** 128 - 1);
2300+
y = _bound(y, 0, 2 ** 128 - 1);
2301+
assertEq(FixedPointMathLib.mulSqrt(x, y), FixedPointMathLib.sqrt(x * y));
2302+
}
2303+
2304+
function testMulSqrt(uint256 x, uint256 y) public {
2305+
if (x == y) {
2306+
assertEq(FixedPointMathLib.mulSqrt(x, y), x);
2307+
return;
2308+
}
2309+
uint256 z = FixedPointMathLib.mulSqrt(x, y);
2310+
2311+
emit LogUint("z", z);
2312+
(uint256 p0, uint256 p1) = _fullMul(x, y);
2313+
(uint256 z0, uint256 z1) = _fullMul(z, z);
2314+
2315+
if (z == type(uint256).max) return;
2316+
(uint256 zp0, uint256 zp1) = _fullMul(z + 1, z + 1);
2317+
2318+
assertTrue((z1 < p1) || (z1 == p1 && z0 <= p0));
2319+
assertTrue((p1 < zp1) || (p1 == zp1 && p0 < zp0));
2320+
}
2321+
2322+
function _fullMul(uint256 x, uint256 y) internal pure returns (uint256 p0, uint256 p1) {
2323+
/// @solidity memory-safe-assembly
2324+
assembly {
2325+
p0 := mul(x, y)
2326+
let mm := mulmod(x, y, not(0))
2327+
p1 := sub(mm, add(p0, lt(mm, p0)))
2328+
}
2329+
}
2330+
2331+
function _testMulSqrt(uint256 x, uint256 y, uint256 z) public {
2332+
assertEq(FixedPointMathLib.mulSqrt(x, y), z);
2333+
assertEq(FixedPointMathLib.mulSqrt(y, x), z);
2334+
}
2335+
2336+
function testMulSqrtDifferential(uint256 x, uint256 y) public {
2337+
assertEq(FixedPointMathLib.mulSqrt(x, y), _mulSqrtOriginal(x, y));
2338+
}
2339+
2340+
function _mulSqrtOriginal(uint256 x, uint256 y) internal pure returns (uint256 z) {
2341+
if (x == 0 || y == 0) return 0;
2342+
if (x == y) return x;
2343+
uint256 p = FixedPointMathLib.rawMul(x, y);
2344+
if (y == p / x) return FixedPointMathLib.sqrt(p);
2345+
uint256 sqrtX = FixedPointMathLib.sqrt(x);
2346+
uint256 sqrtY = FixedPointMathLib.sqrt(y);
2347+
for (z = FixedPointMathLib.saturatingMul(sqrtX + 1, sqrtY + 1);;) {
2348+
uint256 zNext = FixedPointMathLib.fullMulDivUnchecked(x, y, z);
2349+
zNext = FixedPointMathLib.avg(z, zNext);
2350+
if (zNext >= z) break;
2351+
z = zNext;
2352+
}
2353+
}
2354+
2355+
function testMulSqrt() public {
2356+
// forgefmt: disable-start
2357+
this._testMulSqrt(1,40899,202);
2358+
this._testMulSqrt(2,126475466778170,15904431);
2359+
this._testMulSqrt(1,7531755327269063297785,86785686188);
2360+
this._testMulSqrt(4,1145375910940206129316611151348,2140444730368159);
2361+
this._testMulSqrt(1,6000249325576079771745702731971550324701397394,77461276297102668974080);
2362+
this._testMulSqrt(19990973968927499539515487029982657202480,351588509,2651150077078442553440984);
2363+
this._testMulSqrt(884589532366,8944067071329830864412327072945291263924221217993,2812797203510731992653280306277);
2364+
this._testMulSqrt(6,231793030923641024945144526163927788762812410599871060107884767317,1179304110711841006457676781313383);
2365+
this._testMulSqrt(7,25918817354934577870911410398594504620656949910895919963803454597218,13469659293558320924081748803885430);
2366+
this._testMulSqrt(2,6184767715379885918995468503098991544649672240672289523626396828206377,111218413182169488449746241587513722);
2367+
this._testMulSqrt(11262102866604455182725961449733527925032397745743648245810757,2289531243,160576886179084819940315039841174360);
2368+
this._testMulSqrt(2,386128680208745785717489332755004235996307094856550925460526784315796445,878781747885953518587400193470685282);
2369+
this._testMulSqrt(20,133823397008372075942399557492827432876972721009673572412945073431794356,1635991424234076331806369976651119304);
2370+
this._testMulSqrt(64385011511148092020199840672969561808819251747679314637,16788189010063055495896874,32877161414335023454300652451233783772805);
2371+
this._testMulSqrt(307695058396291282867900569126929438472472421745,16650777576165153239643978609535636374,2263484477225720726470604509270073120374679);
2372+
this._testMulSqrt(37369048817028070,403547546041382623075657545097967233546021171804178222743888832459267032,122801416718262293521136665081803979269652981);
2373+
this._testMulSqrt(108757921013865829718776238158835403990222487425921424856119171941423640,311530010377180825,184068889989727037643726419490138992138431758);
2374+
this._testMulSqrt(71968390915585988831780603371197224690006783200448692196810504,1317434458380206291313344071812603846743105529396590981616,307918232825485645925260268942424415111262027158036186068393);
2375+
this._testMulSqrt(169786846411291950176086783315992030432326301383306594102503280,262091314248283560844199184902391093238338514869222967307939442153535,210949419809600269188992711762466980237552872839406150493359215238);
2376+
this._testMulSqrt(13125530490447682132624267118512105994743022946653752643039816341671957523,2703992648396778482623576839914577810228120087337379417522074085943689092,5957460696679273501196497896950129901097275853419042552123499864438199223);
2377+
this._testMulSqrt(759343816572101418250144283506759951951139556413747077593352856047453741042,5783606897390246194179048291049046711192537808438654462819097925235107871790,2095649334935842670420229418803511179917932829424728325651967137561547960515);
2378+
this._testMulSqrt(1453693402301034209593542434097237131647588800659364354488100189009468111235,3021095182839377497836893600683197190212994171204262758821708252445961887922,2095649334935842670420229418803511179917932829424728325651967137561547960515);
2379+
this._testMulSqrt(1500322080289732537847477243966572315817180906638531006084846839970956040737,2927202227250387439989971689119336628619848415002314520835497068418776903816,2095649334935842670420229418803511179917932829424728325651967137561547960515);
2380+
this._testMulSqrt(1418188734037590650038894298784738194557827267133881612919761886151138174982,3096728968163296409290287978802863257249612545684811929660405453902456993799,2095649334935842670420229418803511179917932829424728325651967137561547960515);
2381+
this._testMulSqrt(72649803940522399886719930058523700039023197833404875144131115786317960957502,1058032812382543109188733542274803566994606781443813721786480120922367936989,8767318654082973239818507301503235653779978250722724160009440005060322575671);
2382+
this._testMulSqrt(5373922104039726654109199686576171393319670483688158333460332678277457737472,29698357225200387337129817970745776673739254860209898274723677280893657304883,12633157101301807356759851956784431867751428103301339039224348881394628761742);
2383+
this._testMulSqrt(4110435947028096136332171788401813256200194295940648314773165026203401589664,47629135145959642265403944627116611572745368779360562832098585565036412006720,13992015910147179362140946267238865150610331346635053034797342180359096262561);
2384+
this._testMulSqrt(6670439349065019066867111962665902305718250536829827331422008770563721526834,23925958995271068490107533294147958150213516645841822380228819548185161301331,12633157101301807356759851956784431867751428103301339039224348881394628761742);
2385+
this._testMulSqrt(2969132035655378982552797810065716750184480456087472143547576763384185040150,65937286344558903530415649738599306075672958462949062884739418323163356428267,13992015910147179362140946267238865150610331346635053034797342180359096262561);
2386+
this._testMulSqrt(7491404489992521390184991330802592986343520371455692604718960104437178752665,21303970244748005562096092471791804290084308713402287738808276131330039069727,12633157101301807356759851956784431867751428103301339039224348881394628761742);
2387+
this._testMulSqrt(6121895596657181220666267444651436648237333295237933576541838582595521000977,70855413278339511916656827078592824537105782268733407488004367896456997184904,20827132364970253561103779902092578934359252280464989168083476470668469620748);
2388+
this._testMulSqrt(9663464530001823655566072879594302677711586601817760096066022478590461523681,44887570208519155988166566051306580252890388816305191368753991821976222373501,20827132364970253561103779902092578934359252280464989168083476470668469620748);
2389+
this._testMulSqrt(3146233082020622571934575333002634745373987161876939823151669615808091349575,62225685168905914639685682949327855631601195004914029384687573254167511859580,13992015910147179362140946267238865150610331346635053034797342180359096262561);
2390+
this._testMulSqrt(81292148375414543538357432189784118864691517292040335173170009268459477601704,2516741501174680293198396542611706642053439931235618117323066049365884914216,14303542342233117118521903523784342299628960478384746102119577025303325649767);
2391+
this._testMulSqrt(6235952310590095577242208648287326629803669887249954646740971273060426215074,86267974483515850745154997335441073241510839437043584529161844505392388495406,23194028861118718470348997581404528819309525163067296635449034115712705381145);
2392+
this._testMulSqrt(8202270509291188368161098479682643484535231613691719026429761609950890177708,110093889479585348411203054124693950081761292952495296567025182505398163143046,30050288899302887082115267696735954136708432495128508248566789198018211109331);
2393+
this._testMulSqrt(58567595367007003434029003100140382325611053031291395486564269268008176012515,58567595367007003434029003100140382325611053031291395486564269268008176012528,58567595367007003434029003100140382325611053031291395486564269268008176012521);
2394+
this._testMulSqrt(66674750232480974592958260963213523118377591895163248013022969308057159666106,66674750232480974592958260963213523118377591895163248013022969308057159666106,66674750232480974592958260963213523118377591895163248013022969308057159666106);
2395+
this._testMulSqrt(33585912866580500017428740937261524277391765196007489833430561956127819834566,52199687981615271967060816285063413507671559301970565963089267667884242865491,41870922753304826754657295225348362266441633929084405750532911003697653673376);
2396+
this._testMulSqrt(67059371761480772870120378810526551624172903367552398549958050667166366320070,18204720352041367142096118905131984188387648432760494796813976946511020703864,34939907124967249547474326305667618125822338407481424538436068764138475863743);
2397+
this._testMulSqrt(91758599666022652235931878429035236115420549503042702161402892861112704213437,91758599666022652235931878429035236115420549503042702161402892861112704213446,91758599666022652235931878429035236115420549503042702161402892861112704213441);
2398+
this._testMulSqrt(57643576768106877450231061433780292981040727232162059949268652098179482795200,25879261867897264383063632807223059137462762609820894729277419678914890663158,38623480140765111324896240658186465312359836452286369356057144242747750076434);
2399+
this._testMulSqrt(97780904848435401688577228945316029068942540304391356583531299677110827351347,97780904848435401688577228945316029068942540304391356583531299677110827351347,97780904848435401688577228945316029068942540304391356583531299677110827351347);
2400+
this._testMulSqrt(39125312885387593452616334467597728868299242980364796136151236474971873470685,86828779128818620639494340617473793523235661436852771257233255355108198117255,58285531230925917668113509553487197292966405880544341575100636208705254025063);
2401+
this._testMulSqrt(28103463145585940293035771056326313713678024127581863885732629790555352610632,76815787111206973955699536219912347630679580756295209504314028436731758019833,46462776951867402066001606132202385212266391791890179377014155917534178988307);
2402+
this._testMulSqrt(55085887312063313304662947434367535525632525671759520071608379009942981796106,82800294118819938958395766683447025163837791408331540436336605895422829072207,67536121233270524981094905764087840435055016173869770891190734091029911998939);
2403+
this._testMulSqrt(11374112687571915868904337573234209429066724575146519193518714103220820309055,38136552227230499196160353868270595333171776294556651442476000096535825394060,20827132364970253561103779902092578934359252280464989168083476470668469620748);
2404+
this._testMulSqrt(58730317605090576282758883651000593881824017604589031401161423874294761362545,58730317605090576282758883651000593881824017604589031401161423874294761362556,58730317605090576282758883651000593881824017604589031401161423874294761362550);
2405+
this._testMulSqrt(50831499485199945339590926255329493296549952108872771415944026608406615545066,50831499485199945339590926255329493296549952108872771415944026608406615545075,50831499485199945339590926255329493296549952108872771415944026608406615545070);
2406+
this._testMulSqrt(85172087893546842302544308826796315864783084900992147451076334534702648635115,85172087893546842302544308826796315864783084900992147451076334534702648635115,85172087893546842302544308826796315864783084900992147451076334534702648635115);
2407+
this._testMulSqrt(65565054857983221818976561024613786630057794203730058177227059603745603013729,65565054857983221818976561024613786630057794203730058177227059603745603013729,65565054857983221818976561024613786630057794203730058177227059603745603013729);
2408+
this._testMulSqrt(55911495894251782213187662010802819104143247543461889936919543738942314665456,55911495894251782213187662010802819104143247543461889936919543738942314665463,55911495894251782213187662010802819104143247543461889936919543738942314665459);
2409+
this._testMulSqrt(17879363847304668652748901453932635872715480532463893033011272860213208357255,30088485217079270634851249038350032152097092409273038120371466325762898598320,23194028861118718470348997581404528819309525163067296635449034115712705381145);
2410+
this._testMulSqrt(22574079799238893389795996210248534007844658027018240720384507454263215868089,23831003504672027024172723977212295224054264531333207324229467512231849039503,23194028861118718470348997581404528819309525163067296635449034115712705381145);
2411+
this._testMulSqrt(10611306079283782718696192094919244168932152831659131747498031131196511452379,15040246427133913011360593741211490257502964477997080664156994032702222657211,12633157101301807356759851956784431867751428103301339039224348881394628761742);
2412+
this._testMulSqrt(73858585336675259861717749966041474360396794822352124567893338971277971476418,73858585336675259861717749966041474360396794822352124567893338971277971476418,73858585336675259861717749966041474360396794822352124567893338971277971476418);
2413+
this._testMulSqrt(93493926051444437853990951601184659120548710831844470529111733337613793165495,54214116350546943146180291574636539900604297425564498997024649792129389692208,71194737059858860760008503832934929635934877910339827282451164211383009505882);
2414+
this._testMulSqrt(31741633454414803277828892614935784705765176373701489896871054927885458857680,31741633454414803277828892614935784705765176373701489896871054927885458857683,31741633454414803277828892614935784705765176373701489896871054927885458857681);
2415+
this._testMulSqrt(73131310464141944405157343302384814531452681906375563388541324773631263794690,73131310464141944405157343302384814531452681906375563388541324773631263794693,73131310464141944405157343302384814531452681906375563388541324773631263794691);
2416+
this._testMulSqrt(65455015498287996254702939291233980776874205016778705952818114191323439048349,65455015498287996254702939291233980776874205016778705952818114191323439048349,65455015498287996254702939291233980776874205016778705952818114191323439048349);
2417+
this._testMulSqrt(81462032043753857067983762687246123130574773932537561797650535926862438796461,81462032043753857067983762687246123130574773932537561797650535926862438796478,81462032043753857067983762687246123130574773932537561797650535926862438796469);
2418+
this._testMulSqrt(78209457813515644067726265623499202575598482171839000555604704620897437284905,78209457813515644067726265623499202575598482171839000555604704620897437284905,78209457813515644067726265623499202575598482171839000555604704620897437284905);
2419+
this._testMulSqrt(13412474850080548643225312277495789264111309375181229577549786099110203380624,40109150684236126557038510591608000846411423768057235283250658763488731560550,23194028861118718470348997581404528819309525163067296635449034115712705381145);
2420+
this._testMulSqrt(40011146698470653129691243350367280704077684991355208240666470591824566065223,40011146698470653129691243350367280704077684991355208240666470591824566065236,40011146698470653129691243350367280704077684991355208240666470591824566065229);
2421+
this._testMulSqrt(84469863776974389618863256801434264209320466806351315058376170339221016552781,105252699476938925947685053655574147531645177588390239327939130772357535593314,94290408775102118898547330988195929704226272047925587806802926560630963930928);
2422+
this._testMulSqrt(30101390199343843269859768555851977933764901751609935209985867350357455919180,112858679561759692460068611865224624921408338260436569801949431998683320106122,58285531230925917668113509553487197292966405880544341575100636208705254025063);
2423+
this._testMulSqrt(49252281286569566201714256790755535986103544271522937010444395775245383022031,103313582627134147756455088718855511170155287731084491962378585746899181375663,71333229509639179283943164935234626333287573837435931792829607795291911850054);
2424+
this._testMulSqrt(103203975778750389840486623148704163858899099747818966282132716369708045200084,103203975778750389840486623148704163858899099747818966282132716369708045200084,103203975778750389840486623148704163858899099747818966282132716369708045200084);
2425+
this._testMulSqrt(107596843987355231847546681774981365988735519046298392541656481902159008087733,107596843987355231847546681774981365988735519046298392541656481902159008087733,107596843987355231847546681774981365988735519046298392541656481902159008087733);
2426+
this._testMulSqrt(102830484155533021557704227144831932637592583365851544616380718131012301176108,102830484155533021557704227144831932637592583365851544616380718131012301176108,102830484155533021557704227144831932637592583365851544616380718131012301176108);
2427+
this._testMulSqrt(115780671983614014435071450400947059939097134791386840528286481782155947852587,115780671983614014435071450400947059939097134791386840528286481782155947852608,115780671983614014435071450400947059939097134791386840528286481782155947852597);
2428+
this._testMulSqrt(102599946188735338018377241438053351371953655256423785960693281164040632229692,113977391922349753717143966833940674036437676311140700292665530749893373322354,108139143134969783777522484667256465358316928947625235238969806848330075092141);
2429+
this._testMulSqrt(104750355640940008131284436009813329129514270958235948287118057008845178828468,111637561575924997019627058419809811559908173308145846004665185899087410950352,108139143134969783777522484667256465358316928947625235238969806848330075092141);
2430+
// forgefmt: disable-end
2431+
}
22972432
}

0 commit comments

Comments
 (0)