123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211 | // number.c implements the number datatype: small rational immediates,
// heap-allocated big rationals over a GMP-backed bigint, and the tier-3
// constructive-real approximation engine for irrational values.
// See number-design.md.
#include "number.h"
#include <ctype.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// The numeric substrate is GMP: a heap big rational is a canonical mpq_t,
// tier 3's magnitudes are heap mpz_t's behind the thin bi_* ownership
// veneer below, and the hot approximation loops run on in-place mpz_t
// locals. See number-design.md's "Bigint backend" note.
#include <gmp.h>
#include <gc.h>
// ---------------------------------------------------------------------------
// Allocation
//
// Every heap allocation funnels through xmalloc, which is the Boehm
// collector, and nothing is ever explicitly released.
//
// GC_MALLOC (not GC_MALLOC_ATOMIC) is required here: tier-3 nodes hold
// `Num_t` fields that are themselves pointers to other heap objects, so the
// collector must scan them to keep a DAG's interior nodes alive. GMP's limb
// buffers go through GMP's own process-global allocator rather than this
// one; tomo_init() (stdlib.c) already points that at GC_MALLOC_ATOMIC, which
// is both correct (limbs hold no pointers) and better than scanning them.
static void *xmalloc(size_t size) {
void *p = GC_MALLOC(size);
if (!p) abort();
return p;
}
static char *xstrdup(const char *s) {
size_t n = strlen(s) + 1;
return memcpy(xmalloc(n), s, n);
}
static char *xsprintf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (n < 0) abort();
char *s = xmalloc((size_t)n + 1);
va_start(ap, fmt);
vsnprintf(s, (size_t)n + 1, fmt, ap);
va_end(ap);
return s;
}
// ---------------------------------------------------------------------------
// Instrumentation (opt-in via -DNUMBER_STATS; dump via number_stats_dump,
// declared in number.h under the same guard). Dispatch counts only, with no
// timing here, since wall-clock belongs to a profiler (perf, callgrind,
// rdtsc microbenchmarks, ...), not the library itself. This answers a
// different question than a profiler: not "how slow is this call" but
// "which tier and which path do real workloads' calls actually take,"
// which is what should decide where profiling effort goes next.
//
// Plain (non-atomic) counters: this code isn't thread-safe elsewhere
// either, so a stats build doesn't add synchronization no other part of it
// needs.
#ifdef NUMBER_STATS
enum { STAT_ADD, STAT_SUB, STAT_MUL, STAT_DIV, STAT_COMPARE, STAT_OP_COUNT };
static struct {
uint64_t ops[STAT_OP_COUNT]; // top-level number_* calls, by operation
uint64_t tier1_fastpath; // small x small, integer sub-path (both denominators 1)
uint64_t tier1_general; // small x small, needs gcd reduction / cross-multiply
uint64_t tier2; // at least one heap bigrat operand
uint64_t tier2_u128_fast; // tier2 op whose operands both still fit u64, no bigint temporaries
uint64_t tier3; // at least one real/irrational operand
uint64_t promotions; // number_from_ratio64 result didn't fit back into tier 1
uint64_t bi_allocs; // heap-bigint (mpz) allocations (tier-3 magnitudes route through bi_new)
uint64_t irr_memo_hits; // irr_fixed: memoized approximation already precise enough
uint64_t irr_recompute[10]; // irr_fixed: recompute needed, indexed by IRR_* (10 ops; see enum below)
uint64_t interval_hits; // double-interval fast path decided a sign/rounding outright
uint64_t interval_fallbacks; // interval was computable but too wide (or undecidable): bigint path
} number_stats;
#define NSTAT(field) (number_stats.field++)
#else
#define NSTAT(field) ((void)0)
#endif
// ---------------------------------------------------------------------------
// Tags and small-rational packing
#define TAG_MASK 0x3u
#define TAG_POINTER 0x0u
#define TAG_SMALL 0x1u
#define TAG_ERROR 0x3u
#define SMALL_NUM_MAX INT32_MAX // |numerator| bound for the immediate form
#define SMALL_DEN_MAX 0x3FFFFFFFu // 30-bit denominator bound
static inline uint32_t number_tag(Num_t x) {
return x.bits & TAG_MASK;
}
static inline Num_t small_make(int32_t num, uint32_t den) {
return (Num_t){((uint64_t)(uint32_t)num << 32) | ((uint64_t)den << 2) | TAG_SMALL};
}
static inline int64_t small_num(Num_t x) {
return (int32_t)(x.bits >> 32);
}
static inline uint64_t small_den(Num_t x) {
return (x.bits >> 2) & SMALL_DEN_MAX;
}
static inline Num_t small_zero(void) {
return small_make(0, 1);
}
// ---------------------------------------------------------------------------
// Error values
//
// The error tag (0x3) only pins the low 2 bits (see TAG_MASK/TAG_ERROR
// above). number_is_error checks number_tag(x) == TAG_ERROR, not bit-exact
// equality to NUMBER_ERROR, so the remaining 62 bits are free to carry a
// reason code with no change to the immediate representation: an error
// value is still tagless, with no heap object at all, whatever its payload
// bits. NUMBER_ERROR itself
// (0x3) is ERR_GENERIC (code 0, shifted in), so every existing bare
// NUMBER_ERROR return continues to work unchanged, just with a generic
// message; call sites that know *why* construct a specific code instead via
// err().
enum {
ERR_GENERIC = 0, // NUMBER_ERROR's own code: no specific reason given
ERR_DIV_BY_ZERO,
ERR_SQRT_NEGATIVE,
ERR_LOG_NONPOSITIVE,
ERR_ASIN_ACOS_DOMAIN,
ERR_POW_ZERO_NEGATIVE,
ERR_POW_NEGATIVE_BASE,
ERR_NOT_FINITE,
ERR_PARSE,
ERR_UNDECIDABLE,
ERR_UNDECIDABLE_INT,
ERR_ATAN2_ORIGIN,
ERR_GCD_IRRATIONAL,
ERR_COUNT,
};
static const char *const ERROR_MESSAGES[ERR_COUNT] = {
[ERR_GENERIC] = "undefined result",
[ERR_DIV_BY_ZERO] = "division by zero",
[ERR_SQRT_NEGATIVE] = "square root of a negative number",
[ERR_LOG_NONPOSITIVE] = "logarithm of a non-positive number",
[ERR_ASIN_ACOS_DOMAIN] = "arcsine or arccosine of a value outside [-1, 1]",
[ERR_POW_ZERO_NEGATIVE] = "zero raised to a negative power",
[ERR_POW_NEGATIVE_BASE] = "a negative number raised to a non-integer power (not a real result)",
[ERR_NOT_FINITE] = "not a finite number (NaN or infinity)",
[ERR_PARSE] = "invalid number syntax",
[ERR_UNDECIDABLE] = "an irrational value's sign could not be resolved within the precision limit",
[ERR_UNDECIDABLE_INT] = "an irrational value's integer part could not be resolved within the precision limit",
[ERR_ATAN2_ORIGIN] = "atan2 of (0, 0) (the angle is undefined)",
[ERR_GCD_IRRATIONAL] = "greatest common divisor or least common multiple of an irrational value",
};
// An error value carrying a specific reason code. err(ERR_GENERIC) is bit-
// identical to NUMBER_ERROR.
static inline Num_t err(uint32_t code) {
return (Num_t){((uint64_t)code << 2) | TAG_ERROR};
}
// ---------------------------------------------------------------------------
// Bigint: a heap-allocated GMP integer (mpz_t), used magnitude-only. Every
// value stored here is nonnegative; signs live in the containing object
// (num_bigrat's sign field, num_irr's appr_sign). The struct itself comes
// from xmalloc (the collector); the limb buffer inside is owned by GMP and
// follows GMP's process-global allocator, which tomo_init() (stdlib.c)
// already points at the collector.
// unsigned __int128 is a GCC/Clang extension, not ISO C, so __extension__
// suppresses the -Wpedantic warning for introducing it (verified clean
// under this project's exact warning set); every other u128 use below is
// just referencing this typedef, not introducing new non-standard syntax,
// so it needs no further __extension__ markers.
__extension__ typedef unsigned __int128 u128;
typedef mp_limb_t bi_limb;
typedef __mpz_struct bigint;
static bigint *bi_new(void) {
NSTAT(bi_allocs);
bigint *b = xmalloc(sizeof(bigint));
mpz_init(b);
return b;
}
// NULL-safe, like free(): the memoized appr fields stay NULL until first use.
static void bi_free(bigint *b) {
if (!b) return;
mpz_clear(b);
}
static bigint *bi_from_u64(uint64_t v) {
bigint *b = bi_new();
mpz_set_ui(b, v);
return b;
}
static bigint *bi_copy(const bigint *a) {
bigint *b = bi_new();
mpz_set(b, a);
return b;
}
static inline bool bi_is_zero(const bigint *a) {
return mpz_sgn(a) == 0;
}
static inline bool bi_is_one(const bigint *a) {
return mpz_cmp_ui(a, 1) == 0;
}
static bool bi_fits_u64(const bigint *a, uint64_t *out) {
if (mpz_size(a) > 1) return false;
*out = mpz_size(a) ? mpz_getlimbn(a, 0) : 0;
return true;
}
// Write u128 v into an already-initialized mpz (magnitude only). The hi==0
// case, the common one since a u128 here is usually a widened u64, is a
// single mpz_set_ui with no shift.
static void mpz_set_u128(mpz_ptr dst, u128 v) {
uint64_t hi = (uint64_t)(v >> 64);
if (hi == 0) {
mpz_set_ui(dst, (uint64_t)v);
return;
}
mpz_set_ui(dst, hi);
mpz_mul_2exp(dst, dst, 64);
mpz_add_ui(dst, dst, (uint64_t)v);
}
// Binary (Stein's) GCD: shifts/subtracts/ctz instead of Euclid's
// modulo loop. 64-bit idiv is the slowest ALU op on x86 (the arith_bench
// int64-vs-double div numbers show it directly), and Euclid's loop is
// nothing but idivs. Measured on this machine, binary gcd is ~1.7x
// faster on small-rational-sized operands, which cuts the dominant cost
// of every small-fraction operation (see number_from_ratio64 and the
// coprime-denominator reduction in rat_addsub_u128). Same contract as the
// Euclid version it replaced, including gcd(x,0) == x and gcd(0,x) == x.
static uint64_t gcd_u64(uint64_t a, uint64_t b) {
if (a == 0) return b;
if (b == 0) return a;
// gcd(x, 1) == 1 in one step, the hot case for integer operands, where
// rat_mul_u128/rat_div_u128 take gcd(n, 1) against a denominator of 1 (and
// rat_addsub_u128 takes gcd(da, db) with both 1). Without this, binary GCD
// would grind ~log(x) subtract-shift iterations down to 1. Mirrors the
// same guard in gcd_u128.
if (a == 1 || b == 1) return 1;
int shift = __builtin_ctzll(a | b); // common factors of 2
a >>= __builtin_ctzll(a);
for (;;) {
b >>= __builtin_ctzll(b); // b nonzero here: loop exits before b hits 0
if (a > b) {
uint64_t t = a;
a = b;
b = t;
}
b -= a; // both odd, so b-a is even: next ctz strips >= 1 bit
if (b == 0) break;
}
return a << shift;
}
// Count trailing zeros of a nonzero u128 (there's no __builtin_ctz for
// 128-bit): low word if it carries any set bit, else 64 + the high word's.
static int ctz_u128(u128 v) {
uint64_t lo = (uint64_t)v;
return lo ? __builtin_ctzll(lo) : 64 + __builtin_ctzll((uint64_t)(v >> 64));
}
// Binary (Stein's) GCD, the u128 twin of gcd_u64 above, using shifts,
// subtracts, and ctz instead of Euclid's modulo loop. This matters more here
// than at 64 bits: u128 has no hardware divide at all (unlike gcd_u64, whose
// modulo was at least one idiv), so the old `a % b` loop compiled to a full
// software division routine per iteration. The one caller left is
// rat_addsub_u128's g>1 leftover reduction gcd(num, g); add/sub with coprime
// denominators and all of mul/div reduce with 64-bit gcds only, never
// reaching here. Same contract as before, including gcd(x,0) == x and
// gcd(0,x) == x.
static u128 gcd_u128(u128 a, u128 b) {
if (a == 0) return b;
if (b == 0) return a;
// gcd(x, 1) == 1 in one step. This is the hot case for *integer*
// operands, whose denominator is 1 (every large-integer add/sub/mul
// reduces num/1): without this guard binary GCD would grind ~log(num)
// subtract-shift iterations down to 1, where Euclid's modulo collapsed
// x % 1 == 0 immediately. The both-large case that motivates the binary
// algorithm (coprime fraction num/den) essentially never hits this.
if (a == 1 || b == 1) return 1;
int shift = ctz_u128(a | b); // common factors of 2
a >>= ctz_u128(a);
for (;;) {
b >>= ctz_u128(b); // b nonzero here: loop exits before b hits 0
if (a > b) {
u128 t = a;
a = b;
b = t;
}
b -= a; // both odd, so b-a is even: next ctz strips >= 1 bit
if (b == 0) break;
}
return a << shift;
}
static int bi_cmp(const bigint *a, const bigint *b) {
return mpz_cmp(a, b);
}
static uint32_t bi_bitlen(const bigint *a) {
if (mpz_sgn(a) == 0) return 0;
return (uint32_t)mpz_sizeinbase(a, 2);
}
static bigint *bi_add(const bigint *a, const bigint *b) {
bigint *res = bi_new();
mpz_add(res, a, b);
return res;
}
// Callers maintain a >= b (magnitude convention: results stay nonnegative);
// mpz_sub itself has no such requirement.
static bigint *bi_sub(const bigint *a, const bigint *b) {
bigint *res = bi_new();
mpz_sub(res, a, b);
return res;
}
static bigint *bi_mul(const bigint *a, const bigint *b) {
bigint *res = bi_new();
mpz_mul(res, a, b);
return res;
}
static bigint *bi_mul_u32(const bigint *a, uint32_t m) {
bigint *res = bi_new();
mpz_mul_ui(res, a, m);
return res;
}
static bigint *bi_shl(const bigint *a, uint32_t k) {
bigint *res = bi_new();
mpz_mul_2exp(res, a, k);
return res;
}
static bigint *bi_shr(const bigint *a, uint32_t k) {
bigint *res = bi_new();
mpz_tdiv_q_2exp(res, a, k);
return res;
}
// d must be nonzero.
static void bi_divmod(const bigint *n, const bigint *d, bigint **q_out, bigint **r_out) {
bigint *q = bi_new(), *r = bi_new();
mpz_tdiv_qr(q, r, n, d);
*q_out = q;
*r_out = r;
}
// floor(sqrt(n)).
static bigint *bi_isqrt(const bigint *n) {
bigint *s = bi_new();
mpz_sqrt(s, n);
return s;
}
static bigint *bi_pow10(uint32_t k) {
bigint *r = bi_new();
mpz_ui_pow_ui(r, 10, k);
return r;
}
// If n (a positive bigint magnitude) is exactly base^k for some k >= 0, sets
// *out = k and returns true. base is a GMP sizeinbase base (2..62).
static bool bi_exact_power(const bigint *n, unsigned base, int64_t *out) {
if (bi_is_zero(n)) return false;
// sizeinbase is the digit count (or one more); base^k is the only
// (k+1)-digit value in that base, so compute the candidate and compare.
size_t k = mpz_sizeinbase(n, (int)base) - 1;
mpz_t p;
mpz_init(p);
mpz_ui_pow_ui(p, base, k);
bool eq = mpz_cmp(p, n) == 0;
mpz_clear(p);
if (eq) *out = (int64_t)k;
return eq;
}
// Decimal digits of a magnitude, as an xmalloc'd string ("0" for zero).
// mpz_get_str is subquadratic (divide-and-conquer conversion), unlike the
// old chunked divide-by-10^19 loop this replaces.
static char *bi_to_decimal(const bigint *b) {
char *buf = xmalloc(mpz_sizeinbase(b, 10) + 2);
mpz_get_str(buf, 10, b);
return buf;
}
// Decimal digits of |z| for a possibly-negative mpz (an mpq numref): the
// printers place signs themselves, per their own spacing/TeX conventions.
static char *mpz_mag_decimal(mpz_srcptr z) {
char *buf = xmalloc(mpz_sizeinbase(z, 10) + 2);
mpz_get_str(buf, 10, z);
if (buf[0] == '-') memmove(buf, buf + 1, strlen(buf));
return buf;
}
// ---------------------------------------------------------------------------
// Heap objects: big rationals (canonical: gcd(num,den)==1, den > 0, sign in
// its own field, and the value does NOT fit in a small rational).
enum { KIND_BIGRAT = 1, KIND_REAL = 2, KIND_IRRATIONAL = 3 };
// Common prefix of every heap object. kind holds one of the three KIND_*
// values above; the rest of the word is unused.
typedef struct {
uint32_t kind : 2;
} num_head;
typedef struct {
num_head head;
// mpq_t is a one-element array of __mpq_struct, so this field embeds the
// num/den __mpz_struct pair directly in the allocation (no pointer
// indirection): a bigrat is one xmalloc plus GMP's two limb buffers.
// Canonical: den > 0, gcd(num,den) == 1, sign in num, and the value does
// not fit the small immediate tier.
mpq_t q;
} num_bigrat;
// Tier 3: an irrational real, stored as the linear form a + b*F where a and b
// are rationals (b nonzero) and F = fn(arg) is a transcendental function
// applied to arg: pi (arg unused), sqrt(arg) for a positive non-square
// integer arg, ln(arg) for a rational arg > 1 (see make_real's
// canonicalization, where 0 < arg < 1 is rewritten as -ln(1/arg) so F stays
// positive, matching the invariant every other fn already relies on), or
// exp(arg) for any rational arg. fn is generic on purpose, so more
// transcendentals (sin, cos, tan, ...) can join later without renaming
// anything. Each sqrt(n)-class is a field (Q(sqrt n)) and the pi form is
// closed under affine operations, so most arithmetic stays symbolic; ln/exp
// are closed under narrower rules (see real_add/real_mul/real_div: same-arg
// affine combination for both, plus exp(a)*exp(b) = exp(a+b) and
// ln(a)+ln(b) = ln(a*b) generalized to integer linear combinations); results
// outside every form fall back to the general IRRATIONAL engine.
enum { FN_PI = 1, FN_SQRT = 2, FN_LN = 3, FN_EXP = 4 };
typedef struct {
num_head head;
uint32_t fn : 3; // FN_PI/FN_SQRT/FN_LN/FN_EXP: which transcendental
Num_t a; // rational part (owned)
Num_t b; // coefficient of F: nonzero rational (owned)
Num_t arg; // FN_SQRT: positive non-square integer; FN_LN: rational > 1;
// FN_EXP: any rational; unused for FN_PI
mpz_t appr; // memoized approximation of F: F * 2^appr_prec
uint32_t appr_prec; // bits of precision in appr (error <= 2 ulp); 0 = no
// memo yet (every stored prec carries a +64 overshoot,
// so 0 never occurs naturally); unbounded, full-width
} num_real;
// Tier 3 (general fallback): a lazy constructive-real DAG node, used once a
// computation falls outside every closed symbolic form above (pi + sqrt(2),
// pi*pi, sin(2), ...). Evaluated to arbitrary bit precision by bigint
// fixed-point approximation (arctan_recip/machin_pi below is the prototype
// this generalizes), memoizing the best-known approximation like num_real's
// appr/appr_prec. Most of the calculator's transcendental functions (tan,
// asin, acos, sinh, cosh, tanh, log10, pow, ...) don't need their own op:
// they're built by composing these primitives plus existing number_* calls
// (e.g. tan(x) = sin(x)/cos(x) is just an IRR_DIV of two IRR_SIN/IRR_COS
// results). The DAG is acyclic: an operation only ever references
// already-built operands.
enum {
IRR_SIN,
IRR_COS,
IRR_EXP,
IRR_LN,
IRR_ATAN,
IRR_SQRT,
IRR_ADD,
IRR_SUB,
IRR_MUL,
IRR_DIV,
};
typedef struct {
num_head head;
uint32_t op : 4; // one of the IRR_* ops above (10 values, needs unsigned 4 bits)
int32_t appr_sign : 2; // sign of the memoized approximation; 0 until first evaluated
Num_t x; // operand (owned); LHS for binary ops
Num_t y; // RHS for ADD/SUB/MUL/DIV (owned); small_zero() (unused) for unary ops
mpz_t appr; // memoized |value| * 2^appr_prec
uint32_t appr_prec; // bits of precision in appr; 0 = no memo yet
// (stored precs always carry +64); unbounded, full-width
} num_irr;
static inline num_head *number_head(Num_t x) {
return (num_head *)(uintptr_t)x.bits;
}
static inline num_bigrat *number_heap(Num_t x) {
return (num_bigrat *)(uintptr_t)x.bits;
}
static inline num_real *as_real(Num_t x) {
return (num_real *)(uintptr_t)x.bits;
}
static inline num_irr *as_irr(Num_t x) {
return (num_irr *)(uintptr_t)x.bits;
}
static inline bool is_real_kind(Num_t x) {
return number_tag(x) == TAG_POINTER && x.bits != 0 && number_head(x)->kind == KIND_REAL;
}
static inline bool is_irrational_kind(Num_t x) {
return number_tag(x) == TAG_POINTER && x.bits != 0 && number_head(x)->kind == KIND_IRRATIONAL;
}
// Past this many bits of requested precision, an approximation attempt gives
// up rather than loop forever. The only way a refinement can fail to
// terminate is a general IRRATIONAL value that is (or is suspiciously close
// to) exactly zero without any symbolic proof either way, e.g.
// sin(x)^2+cos(x)^2-1, which is mathematically zero but unrecognized by any
// identity this design knows. ~5k decimal digits: generous for any
// calculator use, but bounded, and it needs to stay fairly low, not just
// "safely below overflow": machin_pi-style series cost still grows
// superlinearly with precision. The timings that set this value (2^14 well
// under a second; 2^16 ~7s; 2^18 unfinished in 40s) were measured on the
// old portable schoolbook backend; GMP's subquadratic multiplication moves the
// wall substantially, so re-measure if this threshold is ever revisited. A
// give-up is meant to be a rare, cheap dead end, not a multi-second hang.
#define IRR_MAX_PREC (1u << 14)
// Tier 3 forward declarations (implementations after the rational tiers).
static Num_t real_add(Num_t x, Num_t y);
static Num_t real_mul(Num_t x, Num_t y);
static Num_t real_div(Num_t x, Num_t y);
static char *real_to_string(num_real *r, uint32_t max_frac_digits);
// Sign of a real/irrational value via interval refinement; false if the
// value can't be resolved within IRR_MAX_PREC bits (see the macro above).
static bool refine_sign(Num_t x, int *sign_out);
// refine_sign's two halves, split for the one caller (comparison's tier 3)
// that has something cheap to try in between them, and that wants a say in
// how far the expensive half goes.
static bool sign_from_interval(Num_t x, int *sign_out);
static bool refine_sign_exact(Num_t x, int *sign_out, uint32_t max_prec);
// Builds a general IRRATIONAL node combining x and y (y is small_zero(),
// i.e. unused, for unary ops), taking ownership of both. For IRR_DIV, first
// establishes y is nonzero via refine_sign; gives up -> NUMBER_ERROR.
static Num_t make_irr(int op, Num_t x, Num_t y);
// Any non-error number as fixed-point at precision w, |error| <= 4. False
// if the approximation can't be resolved (only possible for an IRRATIONAL
// value; see IRR_MAX_PREC).
static bool value_fixed(Num_t x, uint32_t w, int *sign_out, bigint **mag_out);
static bool irr_fixed(num_irr *n, uint32_t w, int *sign_out, bigint **mag_out);
// exp(x)/ln(x) * 2^w as sign/magnitude, |error| <= 4, the numeric core
// shared by irr_fixed's IRR_EXP/IRR_LN cases (x arbitrary) and factor_appr's
// FN_EXP/FN_LN cases (x always a plain rational, so these never actually
// fail there). False only if x's own approximation can't be resolved.
static bool exp_fixed_core(Num_t x, uint32_t w, int *sign_out, bigint **mag_out);
static bool ln_fixed_core(Num_t x, uint32_t w, int *sign_out, bigint **mag_out);
static int signed_add(int s1, bigint *m1, int s2, bigint *m2, bigint **out);
// Wrap a canonical, known-not-small mpq into a fresh heap bigrat, MOVING
// q's contents (struct copy; the limb buffers change owner and q must not
// be used or cleared afterwards).
static Num_t bigrat_steal(mpq_t q) {
num_bigrat *p = xmalloc(sizeof(num_bigrat));
p->head.kind = KIND_BIGRAT;
p->q[0] = q[0];
return (Num_t){(uint64_t)(uintptr_t)p};
}
// Allocate a fresh heap bigrat with an initialized (zero) embedded mpq, ready
// to fill in place. Lets the u128/u64 constructors below write num/den
// straight into the final allocation instead of building a throwaway pair of
// heap-mpz shells and swapping their limbs in.
static num_bigrat *bigrat_alloc(void) {
num_bigrat *p = xmalloc(sizeof(num_bigrat));
p->head.kind = KIND_BIGRAT;
mpq_init(p->q);
return p;
}
// Build a heap bigrat directly from a sign and two already-reduced magnitudes
// (num, den coprime, den > 0, value known not to fit the small tier), writing
// into the embedded mpq with no intermediate bigint shells. u128 covers the
// u64 callers too (a widened u64 takes mpz_set_u128's single-word fast path).
static Num_t bigrat_from_u128s(int sign, u128 num, u128 den) {
num_bigrat *p = bigrat_alloc();
mpz_set_u128(mpq_numref(p->q), num);
mpz_set_u128(mpq_denref(p->q), den);
if (sign < 0) mpz_neg(mpq_numref(p->q), mpq_numref(p->q));
return (Num_t){(uint64_t)(uintptr_t)p};
}
// Consume a CANONICAL mpq (mpq_canonicalize invariants already hold) and
// return it as a number: the small immediate if it fits, else a heap bigrat.
static Num_t number_from_mpq(mpq_t q) {
if (mpq_sgn(q) == 0) {
mpq_clear(q);
return small_zero();
}
if (mpz_cmpabs_ui(mpq_numref(q), SMALL_NUM_MAX) <= 0 && mpz_cmp_ui(mpq_denref(q), SMALL_DEN_MAX) <= 0) {
int32_t n = (int32_t)mpz_get_si(mpq_numref(q));
uint32_t d = (uint32_t)mpz_get_ui(mpq_denref(q));
mpq_clear(q);
return small_make(n, d);
}
return bigrat_steal(q);
}
// Canonicalize sign * num/den into a number, taking ownership of num and den:
// reduce by the gcd, then demote to a small rational if it fits.
static Num_t canon_make(int sign, bigint *num, bigint *den) {
if (bi_is_zero(den)) {
bi_free(num);
bi_free(den);
return err(ERR_DIV_BY_ZERO);
}
mpq_t q;
mpq_init(q);
mpz_swap(mpq_numref(q), num);
mpz_swap(mpq_denref(q), den);
if (sign < 0) mpz_neg(mpq_numref(q), mpq_numref(q));
bi_free(num);
bi_free(den);
mpq_canonicalize(q);
return number_from_mpq(q);
}
// ---------------------------------------------------------------------------
// Tier 2 u128 fast path
//
// A number reaches tier2 once it no longer fits the small immediate's
// 31-bit numerator / 30-bit denominator, but its magnitude very commonly
// still fits a plain uint64_t. The immediate form's fields are narrower
// than the machine word for tagging reasons (see number.h), not because
// values that size are rare. When both operands' num/den fit u64, every
// intermediate of add/sub/mul/div fits in a single u128 (products of two
// u64s never exceed ~2^128, and the one addition that could theoretically
// carry past that (unlike-sign-free add/sub's cross-term sum) is
// overflow-checked and falls back on the rare operand pair large enough to
// trip it). That keeps the whole operation off the GMP heap: zero
// allocations when the reduced result re-fits a small immediate, versus
// the general mpq path's result construction (and canonicalizing gcd) per
// op. Comparison gets the same treatment: it needs no result construction,
// so the whole operation allocates nothing.
// Unpacks x's magnitude as sign/num/den with no allocation, if it fits u64.
// A zero-allocation read-only mpq view of a rational number. For a heap
// bigrat the stored mpq is returned directly (borrowed); for a small
// immediate, v is filled with read-only mpz's over its own limb storage
// (mpz_roinit_n, so no GMP allocation and nothing to clear). Valid only
// while x is live and only for read-only mpq_* arguments.
typedef struct {
mpq_t q;
mp_limb_t nl[1], dl[1];
} mpq_view;
static mpq_srcptr number_mpq_view(Num_t x, mpq_view *v) {
if (number_tag(x) == TAG_POINTER) return number_heap(x)->q;
int64_t n = small_num(x);
uint64_t un = n < 0 ? (uint64_t)-n : (uint64_t)n;
v->nl[0] = un;
v->dl[0] = small_den(x);
mpz_roinit_n(mpq_numref(v->q), v->nl, n < 0 ? -1 : (un != 0));
mpz_roinit_n(mpq_denref(v->q), v->dl, 1);
return v->q;
}
// Always succeeds for TAG_SMALL; for a heap bigrat, only if its num and den
// (already reduced/canonical) each still fit 64 bits.
static bool unpack_u64_rat(Num_t x, int *sign, uint64_t *num, uint64_t *den) {
if (number_tag(x) == TAG_SMALL) {
int64_t n = small_num(x);
*sign = n < 0 ? -1 : 1;
*num = n < 0 ? (uint64_t)-n : (uint64_t)n;
*den = small_den(x);
return true;
}
num_bigrat *p = number_heap(x);
*sign = mpq_sgn(p->q) < 0 ? -1 : 1;
return bi_fits_u64(mpq_numref(p->q), num) && bi_fits_u64(mpq_denref(p->q), den);
}
// Turns an ALREADY-REDUCED sign*num/den (gcd(num,den) == 1, den > 0) into a
// number: a small immediate if it fits, else a heap bigrat built directly
// into the embedded mpq (bigrat_from_u128s), one xmalloc plus GMP's two
// limb buffers, no throwaway bigint shells. num == 0 canonicalizes to zero
// regardless of den (callers that can produce a cancellation reach here with
// den == 1 anyway, but the guard keeps this correct for any caller).
static Num_t finish_reduced_u128(int sign, u128 num, u128 den) {
if (num == 0) return small_zero();
if (num <= SMALL_NUM_MAX && den <= SMALL_DEN_MAX) {
int32_t n32 = (int32_t)num;
return small_make(sign < 0 ? -n32 : n32, (uint32_t)den);
}
return bigrat_from_u128s(sign, num, den);
}
// Attempts add/sub entirely in u64/u128; false means an operand didn't fit
// u64 or the cross-term sum overflowed u128, and the caller should fall
// back to the general bigint path. bsign flips b's sign for subtraction.
//
// Reduces denominators first, the way GMP's mpq_aors does, rather than
// forming the raw cross-product and reducing after. With g = gcd(da, db):
// the common denominator is lcm = da*(db/g), and the summed numerator is
// na*(db/g) +/- nb*(da/g). Because both operands are already canonical, the
// only factor the result can still share is one dividing g (na*(db/g) is
// coprime to da/g and to db/g, likewise nb*(da/g)); so the leftover
// reduction is gcd(num, g), NOT a fresh gcd against the full ~126-bit den.
// In the common case gcd(da,db) == 1 (~61% of random pairs) there is no
// leftover factor at all: the result is canonical with zero reduction work,
// where an unconditional reduce-the-full-product approach would pay a 126-bit
// binary GCD plus two software u128 divisions on every add. This is the bulk
// of the large-fraction add/sub cost, and the reason mpq_add outran number.
static bool rat_addsub_u128(Num_t a, Num_t b, int bsign, Num_t *out) {
int sa, sb;
uint64_t na, da, nb, db;
if (!unpack_u64_rat(a, &sa, &na, &da) || !unpack_u64_rat(b, &sb, &nb, &db)) return false;
sb *= bsign;
uint64_t g = gcd_u64(da, db);
uint64_t da_r = da / g, db_r = db / g; // da_r == da, db_r == db when g == 1
u128 t1 = (u128)na * db_r, t2 = (u128)nb * da_r; // each a single product: fits u128
u128 den = (u128)da * db_r; // lcm(da, db); <= the old da*db, so no more prone to overflow
u128 num;
int sign;
if (sa == sb) {
// The only op in this fast path where two u128 products are summed
// rather than one taken alone, and the one place a 64-bit operand
// pair can still overflow u128 (each term can be within a factor of
// 2 of 2^128), so this is the one checked add.
if (__builtin_add_overflow(t1, t2, &num)) return false;
sign = sa;
} else if (t1 >= t2) {
num = t1 - t2;
sign = sa;
} else {
num = t2 - t1;
sign = sb;
}
if (g != 1) {
// Strip the only factor num/den can still share (a divisor of g).
u128 g2 = gcd_u128(num, g);
num /= g2;
den /= g2;
}
*out = finish_reduced_u128(sign, num, den);
return true;
}
// Attempts multiplication entirely in u64/u128; false means an operand
// didn't fit u64.
//
// Cross-cancels before multiplying, the way GMP's mpq_mul does, rather than
// forming na*nb / da*db and reducing after. The result na*nb/(da*db) can only
// share a factor across the two diagonals, gcd(na,db) and gcd(nb,da), since
// gcd(na,da) and gcd(nb,db) are already 1 (canonical operands). Dividing those
// out first leaves a product that is coprime by construction: canonical with
// NO final ~126-bit gcd, and with smaller multiplicands. Each of num/den is
// still a single u64*u64 product (the reduced factors are <= the originals),
// so there is no overflow case to guard.
static bool rat_mul_u128(Num_t a, Num_t b, Num_t *out) {
int sa, sb;
uint64_t na, da, nb, db;
if (!unpack_u64_rat(a, &sa, &na, &da) || !unpack_u64_rat(b, &sb, &nb, &db)) return false;
// Denominators are >= 1, so both gcds are >= 1 (no zero divide); na == 0
// gives g1 == db, na/g1 == 0 -> finish_reduced_u128's num==0 zero guard.
uint64_t g1 = gcd_u64(na, db), g2 = gcd_u64(nb, da);
u128 num = (u128)(na / g1) * (nb / g2);
u128 den = (u128)(da / g2) * (db / g1);
*out = finish_reduced_u128(sa * sb, num, den);
return true;
}
// Attempts division entirely in u64/u128; false means an operand didn't fit
// u64. Caller has already excluded b == 0.
//
// a/b = (na*db)/(da*nb), multiplication by the reciprocal, so the same
// cross-cancel as rat_mul_u128 applies, on the diagonals gcd(na,nb) and
// gcd(db,da); the other two pairs (na,da) and (db,nb) are already coprime.
static bool rat_div_u128(Num_t a, Num_t b, Num_t *out) {
int sa, sb;
uint64_t na, da, nb, db;
if (!unpack_u64_rat(a, &sa, &na, &da) || !unpack_u64_rat(b, &sb, &nb, &db)) return false;
uint64_t g1 = gcd_u64(na, nb), g2 = gcd_u64(db, da); // nb >= 1 (b != 0), da/db >= 1
u128 num = (u128)(na / g1) * (db / g2);
u128 den = (u128)(nb / g1) * (da / g2);
*out = finish_reduced_u128(sa * sb, num, den);
return true;
}
// Attempts a same-sign magnitude comparison (|a| vs |b|, both already known
// rational) entirely in u64/u128; false means an operand didn't fit u64.
// Unlike addsub, a single cross product each side is all this needs, with no
// sum and so no overflow case to guard.
static bool rat_compare_u128(Num_t a, Num_t b, int sa, int *out) {
int sa_unused, sb_unused;
uint64_t na, da, nb, db;
if (!unpack_u64_rat(a, &sa_unused, &na, &da) || !unpack_u64_rat(b, &sb_unused, &nb, &db)) return false;
u128 l = (u128)na * db, r = (u128)nb * da;
*out = ((l > r) - (l < r)) * sa;
return true;
}
// Build a number from an int64 ratio (the workhorse for all small-value
// construction). Handles sign, reduction, and promotion to big rationals.
static Num_t number_from_ratio64(int64_t num, int64_t den) {
if (den == 0) return err(ERR_DIV_BY_ZERO);
int sign = 1;
uint64_t un = num < 0 ? (sign = -sign, -(uint64_t)num) : (uint64_t)num;
uint64_t ud = den < 0 ? (sign = -sign, -(uint64_t)den) : (uint64_t)den;
if (un == 0) return small_zero();
uint64_t g = gcd_u64(un, ud);
un /= g;
ud /= g;
if (un <= SMALL_NUM_MAX && ud <= SMALL_DEN_MAX) {
int32_t n32 = (int32_t)un;
return small_make(sign < 0 ? -n32 : n32, (uint32_t)ud);
}
NSTAT(promotions);
return bigrat_from_u128s(sign, un, ud);
}
// ---------------------------------------------------------------------------
// Constructors
Num_t number_from_int(int64_t value) {
return number_from_ratio64(value, 1);
}
Num_t number_from_ratio(int64_t numerator, int64_t denominator) {
return number_from_ratio64(numerator, denominator);
}
Num_t number_from_double(double value) {
if (!isfinite(value)) return err(ERR_NOT_FINITE);
if (value == 0) return small_zero();
int sign = value < 0 ? -1 : 1;
int e;
double m = frexp(fabs(value), &e); // m in [0.5, 1)
uint64_t mant = (uint64_t)ldexp(m, 53); // exact 53-bit integer
int p2 = e - 53; // value = mant * 2^p2
// Trim mant's trailing zero bits into p2 up front, exactly what the
// gcd-based bigint path below would eventually reduce down to, but
// doing it in pure integer ops first lets a "round" double (0.5, 1.25,
// 100.0, ...) take a heap-free int64 fast path instead of always
// building through bi_shl/bi_from_u64 regardless of how few
// significant bits it actually has. Shift guards (p2 < 63 / -p2 < 63)
// avoid UB from shifting a 64-bit value by >= its width; number_from_
// ratio64 handles its own further reduction and tier placement, so
// this only needs to avoid overflowing the int64 num/den it's handed.
if (mant != 0) {
int tz = __builtin_ctzll(mant);
mant >>= tz;
p2 += tz;
}
if (p2 >= 0) {
if (p2 < 63 && mant <= (uint64_t)INT64_MAX >> p2) return number_from_ratio64(sign * (int64_t)(mant << p2), 1);
} else if (-p2 < 63) {
return number_from_ratio64(sign * (int64_t)mant, (int64_t)1 << -p2);
}
// Out of int64 range: mpq_set_d is the same exact conversion (mantissa
// times a power of two, trailing zeros trimmed), producing a canonical
// mpq directly.
mpq_t q;
mpq_init(q);
mpq_set_d(q, value);
return number_from_mpq(q);
}
// ---------------------------------------------------------------------------
// Predicates
bool number_is_error(Num_t x) {
return number_tag(x) == TAG_ERROR;
}
const char *number_error_message(Num_t x) {
if (!number_is_error(x)) return NULL;
uint64_t code = x.bits >> 2;
return ERROR_MESSAGES[code < ERR_COUNT ? code : ERR_GENERIC];
}
bool number_is_zero(Num_t x) {
return x.bits == small_zero().bits;
}
int number_sign(Num_t x) {
switch (number_tag(x)) {
case TAG_SMALL: {
int64_t n = small_num(x);
return (n > 0) - (n < 0);
}
case TAG_POINTER: {
uint32_t kind = number_head(x)->kind;
if (kind == KIND_REAL || kind == KIND_IRRATIONAL) {
int s;
// A real is always nonzero (irrational); an irrational node
// might not resolve within the cap, in which case treat it as
// indistinguishable from zero (number_sign has no error return).
return refine_sign(x, &s) ? s : 0;
}
return mpq_sgn(number_heap(x)->q);
}
default: return 0;
}
}
bool number_is_negative(Num_t x) {
return number_sign(x) < 0;
}
bool number_is_rational(Num_t x) {
return !number_is_error(x) && !is_real_kind(x) && !is_irrational_kind(x);
}
bool number_is_integer(Num_t x) {
if (number_tag(x) == TAG_SMALL) return small_den(x) == 1;
// Reals are irrational by construction; an IRRATIONAL node can't be
// proven integer (see the header comment: false means "not provably").
if (number_tag(x) != TAG_POINTER || x.bits == 0 || number_head(x)->kind != KIND_BIGRAT) return false;
return bi_is_one(mpq_denref(number_heap(x)->q));
}
// ---------------------------------------------------------------------------
// Comparison
int number_compare_capped_general(Num_t a, Num_t b, uint32_t max_prec) {
NSTAT(ops[STAT_COMPARE]);
if (number_is_error(a) || number_is_error(b)) return 2;
if (a.bits == b.bits) return 0; // same immediate, or the same shared heap reference
if (number_tag(a) == TAG_SMALL && number_tag(b) == TAG_SMALL) {
NSTAT(tier1_general);
// Cross-multiply: |num| <= 2^31, den < 2^30, so products fit in int64.
int64_t l = small_num(a) * (int64_t)small_den(b);
int64_t r = small_num(b) * (int64_t)small_den(a);
return (l > r) - (l < r);
}
if (is_real_kind(a) || is_real_kind(b) || is_irrational_kind(a) || is_irrational_kind(b)) {
NSTAT(tier3);
// number_sub always succeeds for non-error operands here (symbolic
// forms that don't unify fall back to a general IRRATIONAL node
// rather than erroring): if the difference simplifies to an exact
// rational/real symbolic form, its sign decides exactly. Otherwise
// it needs refine_sign's halves directly (NOT the public number_sign,
// whose give-up fallback of 0 would be misread here as "equal" --
// this is the one place that distinction matters: refining only ever
// succeeds by proving nonzero, never by proving exactly zero, so a
// give-up here means "can't decide", which is unordered (2), not
// equal (0)).
Num_t diff = number_sub(a, b);
int result;
if (is_real_kind(diff) || is_irrational_kind(diff)) {
int s;
if (sign_from_interval(diff, &s)) {
// Cheapest: the hardware interval already excludes zero.
result = s;
} else if (strcmp(number_to_symbolic(a), number_to_symbolic(b)) == 0) {
// The difference didn't cancel symbolically, but that only
// means number_sub's simplifier didn't spot it. Two values
// written the same way ARE the same value, whatever it is,
// and sin(2) vs sin(2) shouldn't cost a refinement at all.
// Worth checking here rather than after refine_sign_exact
// gives up, because a difference that is genuinely zero can
// never be excluded from zero: that call would run every
// doubling out to IRR_MAX_PREC before failing, which is the
// single most expensive thing this function can do. The
// symbolic form is canonical for structure and linear even
// for a heavily shared expression (see number_to_symbolic).
result = 0;
} else {
result = refine_sign_exact(diff, &s, max_prec) ? s : 2;
}
} else {
result = number_sign(diff);
}
return result;
}
int sa = number_sign(a), sb = number_sign(b);
if (sa != sb) return sa < sb ? -1 : 1;
if (sa == 0) return 0;
NSTAT(tier2);
int fast;
if (rat_compare_u128(a, b, sa, &fast)) {
NSTAT(tier2_u128_fast);
return fast;
}
mpq_view va, vb;
// GMP only guarantees the *sign* of mpq_cmp, not its magnitude (it often
// returns the cross products' limb-count difference), so normalize to
// -1/0/+1. Returning it raw would leak values outside the documented
// range, and a raw 2 would be misread as the reserved "unordered" code.
int c = mpq_cmp(number_mpq_view(a, &va), number_mpq_view(b, &vb));
return (c > 0) - (c < 0);
}
int number_compare_general(Num_t a, Num_t b) {
return number_compare_capped_general(a, b, IRR_MAX_PREC);
}
// The out-of-line counterpart to number.h's gnu_inline definition, exactly as
// number_compare has one below:
int number_compare_capped(Num_t a, Num_t b, uint32_t max_prec) {
return number_compare_capped_general(a, b, max_prec);
}
int number_compare(Num_t a, Num_t b) {
return number_compare_general(a, b);
} // see comment above number_add_general
bool number_equal_general(Num_t a, Num_t b) {
if (number_is_error(a) || number_is_error(b)) return false;
if (a.bits == b.bits) return true; // same immediate, or the same shared heap reference
if (is_real_kind(a) || is_real_kind(b) || is_irrational_kind(a) || is_irrational_kind(b))
return number_compare(a, b) == 0; // exact, incl. sqrt(8) == 2*sqrt(2)
// Canonical forms make equality structural: two equal values have
// identical representations (immediates are bit-equal; a value never
// exists as both a small and a big rational).
if (number_tag(a) != number_tag(b)) return false;
if (number_tag(a) == TAG_SMALL) return a.bits == b.bits;
return mpq_equal(number_heap(a)->q, number_heap(b)->q) != 0;
}
bool number_equal(Num_t a, Num_t b) {
return number_equal_general(a, b);
} // see comment above number_add_general
// ---------------------------------------------------------------------------
// Arithmetic
static Num_t rat_addsub_general(Num_t a, Num_t b, int bsign) {
mpq_view va, vb;
mpq_srcptr qa = number_mpq_view(a, &va), qb = number_mpq_view(b, &vb);
mpq_t r;
mpq_init(r);
if (bsign < 0) mpq_sub(r, qa, qb);
else mpq_add(r, qa, qb);
return number_from_mpq(r); // mpq results are canonical
}
static Num_t number_addsub(Num_t a, Num_t b, int bsign) {
if (number_is_error(a)) return a;
if (number_is_error(b)) return b;
if (is_irrational_kind(a) || is_irrational_kind(b) || is_real_kind(a) || is_real_kind(b)) {
NSTAT(tier3);
Num_t bb = bsign < 0 ? number_neg(b) : b;
// Try the exact symbolic form first (real_add never sees an
// IRRATIONAL operand: it doesn't know how to, since !is_real_kind()
// means "treat as rational" there). Only on failure, when no closed
// form unifies them or an operand is already IRRATIONAL, does it fall
// back to a general node.
Num_t result = (is_irrational_kind(a) || is_irrational_kind(bb)) ? NUMBER_ERROR : real_add(a, bb);
if (number_is_error(result)) result = make_irr(IRR_ADD, a, bb);
return result;
}
if (number_tag(a) == TAG_SMALL && number_tag(b) == TAG_SMALL) {
int64_t n1 = small_num(a), n2 = bsign * small_num(b);
uint64_t d1 = small_den(a), d2 = small_den(b);
if (d1 == 1 && d2 == 1) { // integer sub-path: no gcd needed
NSTAT(tier1_fastpath);
return number_from_ratio64(n1 + n2, 1);
}
NSTAT(tier1_general);
// Raw cross-multiply, no pre-reduction: |each num term| <=
// (2^31-1)(2^30-1) < 2^61, so |their sum| < 2^62, and den <
// (2^30-1)^2 < 2^60, so everything fits int64 with 2x headroom.
// An earlier version gcd-reduced the denominators first (the
// classic trick for keeping intermediates small), but at these
// sizes the intermediates were never in danger, and it meant two
// full gcd passes per add, this one and number_from_ratio64's
// canonicalizing one. number_mul/number_div's fraction paths were
// already shaped this way (raw product, single reduction in
// number_from_ratio64); measured, this matches: dropping the
// pre-gcd took fraction add/sub from ~2x mul's cost to parity.
int64_t num = n1 * (int64_t)d2 + n2 * (int64_t)d1;
int64_t den = (int64_t)(d1 * d2);
return number_from_ratio64(num, den);
}
NSTAT(tier2);
Num_t fast;
if (rat_addsub_u128(a, b, bsign, &fast)) {
NSTAT(tier2_u128_fast);
return fast;
}
return rat_addsub_general(a, b, bsign);
}
Num_t number_add_general(Num_t a, Num_t b) {
NSTAT(ops[STAT_ADD]);
return number_addsub(a, b, 1);
}
Num_t number_sub_general(Num_t a, Num_t b) {
NSTAT(ops[STAT_SUB]);
return number_addsub(a, b, -1);
}
// Plain (non-inline) bodies: the GNU89 "true definition" number.h's
// extern-inline fast-path dispatchers fall back on when they don't inline
// a call (address-of; also every call, in a -DNUMBER_STATS build, where
// number.h deliberately doesn't shadow these names with an inline version
// at all; see number.h's comment above number_add_general).
Num_t number_add(Num_t a, Num_t b) {
return number_add_general(a, b);
}
Num_t number_sub(Num_t a, Num_t b) {
return number_sub_general(a, b);
}
Num_t number_mul_general(Num_t a, Num_t b) {
NSTAT(ops[STAT_MUL]);
if (number_is_error(a)) return a;
if (number_is_error(b)) return b;
if (is_irrational_kind(a) || is_irrational_kind(b) || is_real_kind(a) || is_real_kind(b)) {
NSTAT(tier3);
// Multiplying by exactly 1 is an identity: return the other
// operand instead of a useless IRR_MUL node around it (int_pow's
// accumulator starts at 1, so pow(pi, n) would otherwise carry a
// "1 *" factor forever). A rational 1 is always the canonical
// small immediate, so one bit-compare decides it.
if (a.bits == NUMBER_ONE.bits) return b;
if (b.bits == NUMBER_ONE.bits) return a;
// ... and multiplying by exactly 0 is exactly 0, whatever the other
// operand is. real_mul already does this (number_is_zero -> zero), but
// an IRRATIONAL operand never reaches real_mul, so without this
// `0 * sin(2)` would build an IRR_MUL node that no later refinement can
// ever prove is zero, leaving a value that isn't number_is_zero, that
// compares "unordered" against 0, and that floor/mod reject.
if (a.bits == NUMBER_ZERO.bits || b.bits == NUMBER_ZERO.bits) return small_zero();
Num_t result = (is_irrational_kind(a) || is_irrational_kind(b)) ? NUMBER_ERROR : real_mul(a, b);
if (number_is_error(result)) result = make_irr(IRR_MUL, a, b);
return result;
}
if (number_tag(a) == TAG_SMALL && number_tag(b) == TAG_SMALL) {
int64_t n1 = small_num(a), n2 = small_num(b);
uint64_t d1 = small_den(a), d2 = small_den(b);
if (d1 == 1 && d2 == 1) { // integer sub-path
NSTAT(tier1_fastpath);
return number_from_ratio64(n1 * n2, 1);
}
NSTAT(tier1_general);
return number_from_ratio64(n1 * n2, (int64_t)(d1 * d2));
}
NSTAT(tier2);
Num_t fast;
if (rat_mul_u128(a, b, &fast)) {
NSTAT(tier2_u128_fast);
return fast;
}
mpq_view va, vb;
mpq_t r;
mpq_init(r);
mpq_mul(r, number_mpq_view(a, &va), number_mpq_view(b, &vb));
return number_from_mpq(r);
}
Num_t number_mul(Num_t a, Num_t b) {
return number_mul_general(a, b);
} // see comment above number_add_general
Num_t number_div_general(Num_t a, Num_t b) {
NSTAT(ops[STAT_DIV]);
if (number_is_error(a)) return a;
if (number_is_error(b)) return b;
if (number_is_zero(b)) return err(ERR_DIV_BY_ZERO); // catches the exact-rational-zero case
// An exactly-zero numerator (e.g. ln(1)/ln(10)) is always 0, however
// unsimplified b is. This short-circuits refine_sign ever needing to
// prove an already-known-zero value nonzero all the way out to
// IRR_MAX_PREC.
if (number_is_zero(a)) return small_zero();
if (is_irrational_kind(a) || is_irrational_kind(b) || is_real_kind(a) || is_real_kind(b)) {
NSTAT(tier3);
Num_t result = (is_irrational_kind(a) || is_irrational_kind(b)) ? NUMBER_ERROR : real_div(a, b);
if (number_is_error(result))
// make_irr does the eager nonzero check on b for IRR_DIV.
result = make_irr(IRR_DIV, a, b);
return result;
}
if (number_tag(a) == TAG_SMALL && number_tag(b) == TAG_SMALL) {
NSTAT(tier1_general); // no denominator-1 fast path here: always cross-multiplies
// a/b = (n1*d2) / (d1*n2); both products fit in int64.
int64_t num = small_num(a) * (int64_t)small_den(b);
int64_t den = (int64_t)small_den(a) * small_num(b);
return number_from_ratio64(num, den);
}
NSTAT(tier2);
Num_t fast;
if (rat_div_u128(a, b, &fast)) {
NSTAT(tier2_u128_fast);
return fast;
}
mpq_view va, vb;
mpq_t r;
mpq_init(r);
mpq_div(r, number_mpq_view(a, &va), number_mpq_view(b, &vb));
return number_from_mpq(r);
}
Num_t number_div(Num_t a, Num_t b) {
return number_div_general(a, b);
} // see comment above number_add_general
static Num_t make_real(Num_t a, Num_t b, uint32_t fn, Num_t arg);
Num_t number_neg_general(Num_t x) {
if (number_is_error(x)) return x;
if (number_tag(x) == TAG_SMALL) return number_from_ratio64(-small_num(x), (int64_t)small_den(x));
if (is_real_kind(x)) {
num_real *r = as_real(x);
return make_real(number_neg(r->a), number_neg(r->b), r->fn, r->arg);
}
if (is_irrational_kind(x)) {
// Not number_sub(small_zero(), x): that's subtraction with bsign<0,
// which calls number_neg on its second operand, straight back
// here, infinitely. Negate via multiplication instead.
Num_t neg1 = number_from_int(-1);
Num_t result = number_mul(x, neg1);
return result;
}
num_bigrat *p = number_heap(x);
mpq_t r;
mpq_init(r);
mpq_neg(r, p->q); // same magnitude: still not small, no demote check
return bigrat_steal(r);
}
Num_t number_neg(Num_t x) {
return number_neg_general(x);
} // see comment above number_add_general
Num_t number_abs(Num_t x) {
if (number_is_error(x)) return x;
return number_is_negative(x) ? number_neg(x) : x;
}
Num_t number_inverse(Num_t x) {
if (number_is_error(x)) return x;
if (is_real_kind(x) || is_irrational_kind(x)) {
Num_t one = number_from_int(1);
Num_t res = number_div(one, x); // public dispatcher: handles both kinds
return res;
}
if (number_tag(x) == TAG_SMALL) return number_from_ratio64((int64_t)small_den(x), small_num(x));
num_bigrat *p = number_heap(x);
mpq_t r;
mpq_init(r);
mpq_inv(r, p->q); // canonical in, canonical out; may now fit small
return number_from_mpq(r);
}
// Greatest common divisor and least common multiple, generalized to rationals:
// gcd is the largest g >= 0 such that a/g and b/g are both integers; lcm is the
// smallest l >= 0 that is an integer multiple of both. For integer operands
// these are the ordinary gcd/lcm; for reduced fractions pa/qa, pb/qb they are
// gcd = gcd(pa, pb) / lcm(qa, qb) lcm = lcm(pa, pb) / gcd(qa, qb)
// (so e.g. gcd(1/2, 1/3) == 1/6 and lcm(1/2, 1/3) == 1). Both results are
// non-negative regardless of operand signs. Following the usual conventions,
// gcd(0, x) == |x| (gcd(0, 0) == 0) and lcm(0, x) == 0. An irrational operand
// has no such divisor structure -> error; an error operand propagates.
Num_t number_gcd(Num_t a, Num_t b) {
if (number_is_error(a)) return a;
if (number_is_error(b)) return b;
if (!number_is_rational(a) || !number_is_rational(b)) return err(ERR_GCD_IRRATIONAL);
if (number_is_zero(a)) return number_abs(b);
if (number_is_zero(b)) return number_abs(a);
mpq_view va, vb;
mpq_srcptr qa = number_mpq_view(a, &va), qb = number_mpq_view(b, &vb);
mpq_t r;
mpq_init(r);
mpz_gcd(mpq_numref(r), mpq_numref(qa), mpq_numref(qb)); // sign-blind, result >= 0
mpz_lcm(mpq_denref(r), mpq_denref(qa), mpq_denref(qb));
mpq_canonicalize(r); // gcd(na,nb)/lcm(da,db) is in fact already reduced; keep the invariant explicit
return number_from_mpq(r);
}
Num_t number_lcm(Num_t a, Num_t b) {
if (number_is_error(a)) return a;
if (number_is_error(b)) return b;
if (!number_is_rational(a) || !number_is_rational(b)) return err(ERR_GCD_IRRATIONAL);
if (number_is_zero(a) || number_is_zero(b)) return small_zero();
mpq_view va, vb;
mpq_srcptr qa = number_mpq_view(a, &va), qb = number_mpq_view(b, &vb);
mpq_t r;
mpq_init(r);
mpz_lcm(mpq_numref(r), mpq_numref(qa), mpq_numref(qb)); // sign-blind, result >= 0
mpz_gcd(mpq_denref(r), mpq_denref(qa), mpq_denref(qb));
mpq_canonicalize(r);
return number_from_mpq(r);
}
// ---------------------------------------------------------------------------
// Rounding to an integer: floor/ceil/trunc/round, and the floored modulus
// built on floor. The rational tiers round directly (one bigint divmod at
// most); irrational values refine [x - err, x + err] until both interval
// ends round to the same integer; see tier3_to_int.
enum { INT_FLOOR, INT_CEIL, INT_TRUNC, INT_ROUND };
// floor of a rational, non-error x.
static Num_t rat_floor(Num_t x) {
if (number_tag(x) == TAG_SMALL) {
int64_t n = small_num(x), d = (int64_t)small_den(x);
int64_t q = n / d;
if (n % d != 0 && n < 0) q--; // C truncates toward zero; floor doesn't
return number_from_ratio64(q, 1);
}
num_bigrat *p = number_heap(x);
if (bi_is_one(mpq_denref(p->q))) return x; // already an integer
mpq_t r;
mpq_init(r);
mpz_fdiv_q(mpq_numref(r), mpq_numref(p->q), mpq_denref(p->q)); // true floor
return number_from_mpq(r); // den stays 1: canonical
}
// q must be integer-valued (denominator 1; either rational tier).
static bool int_is_even(Num_t q) {
if (number_tag(q) == TAG_SMALL) return (small_num(q) & 1) == 0;
return mpz_even_p(mpq_numref(number_heap(q)->q));
}
// A rational, non-error x rounded to an integer-valued number by mode.
static Num_t rat_to_int(Num_t x, int mode) {
switch (mode) {
case INT_FLOOR: return rat_floor(x);
case INT_CEIL: { // ceil(x) = -floor(-x)
Num_t nx = number_neg(x);
Num_t f = rat_floor(nx);
Num_t res = number_neg(f);
return res;
}
case INT_TRUNC: return rat_to_int(x, number_is_negative(x) ? INT_CEIL : INT_FLOOR);
default: { // INT_ROUND: nearest, ties to even
Num_t q = rat_floor(x);
Num_t frac = number_sub(x, q); // in [0, 1)
int c = number_compare(frac, NUMBER_SMALL(1, 2));
if (c < 0 || (c == 0 && int_is_even(q))) return q;
Num_t q1 = number_add(q, NUMBER_ONE); // the tie's other neighbor is the even one
return q1;
}
}
}
// An irrational x rounded to an integer by mode: refine x's approximation
// until the whole interval it pins x inside rounds to a single integer.
// Terminates as soon as x's distance from the nearest rounding boundary
// exceeds the approximation error, which is guaranteed eventually for any
// value not exactly ON a boundary, so only a general IRRATIONAL node
// sitting unprovably on one (or an unresolvable one) reaches the precision
// cap and gives up.
static Num_t tier3_to_int(Num_t x, int mode) {
for (uint32_t w = 32; w <= IRR_MAX_PREC; w *= 2) {
int s;
bigint *v;
if (!value_fixed(x, w, &s, &v)) break;
// x lies within [s*v - 4, s*v + 4] / 2^w (value_fixed's error bound).
bigint *lom, *him;
int los = signed_add(s, bi_copy(v), -1, bi_from_u64(4), &lom);
int his = signed_add(s, v, 1, bi_from_u64(4), &him);
bigint *unit = bi_from_u64(1); // bi_shl doesn't consume its input
Num_t lo = canon_make(los, lom, bi_shl(unit, w));
Num_t hi = canon_make(his, him, bi_shl(unit, w));
bi_free(unit);
Num_t il = rat_to_int(lo, mode), ih = rat_to_int(hi, mode);
bool decided = number_equal(il, ih);
if (decided) return il;
}
return err(ERR_UNDECIDABLE_INT);
}
static Num_t number_to_int(Num_t x, int mode) {
if (number_is_error(x)) return x;
if (is_real_kind(x) || is_irrational_kind(x)) return tier3_to_int(x, mode);
return rat_to_int(x, mode);
}
Num_t number_floor(Num_t x) {
return number_to_int(x, INT_FLOOR);
}
Num_t number_ceil(Num_t x) {
return number_to_int(x, INT_CEIL);
}
Num_t number_trunc(Num_t x) {
return number_to_int(x, INT_TRUNC);
}
Num_t number_round(Num_t x) {
return number_to_int(x, INT_ROUND);
}
Num_t number_mod(Num_t a, Num_t b) {
if (number_is_error(a)) return a;
if (number_is_error(b)) return b;
if (number_is_zero(b)) return err(ERR_DIV_BY_ZERO);
Num_t q = number_div(a, b);
Num_t qi = number_floor(q);
if (number_is_error(qi)) return qi; // undecidable floor (or IRR division failure)
Num_t prod = number_mul(qi, b);
Num_t res = number_sub(a, prod);
return res;
}
// Decide a < b (-1), a == b (0), or a > b (+1) by comparing a and b each
// rounded to `digits` fractional digits, which is always total and
// terminating. number_min/number_max use this only when exact comparison
// gives up (two general irrationals indistinguishable within the precision
// cap); ties (and the pathological case where even the rounding fails to
// resolve) report 0.
static int compare_rounded(Num_t a, Num_t b, uint32_t digits) {
Num_t scale = number_pow(number_from_int(10), number_from_int((int64_t)digits));
Num_t as = number_mul(a, scale);
Num_t bs = number_mul(b, scale);
Num_t ra = number_round(as);
Num_t rb = number_round(bs);
int c = (number_is_error(ra) || number_is_error(rb)) ? 0 : number_compare(ra, rb);
return c == 2 ? 0 : c; // 2 (unordered) can't arise from two integers, but be safe
}
// The smaller (number_min) / larger (number_max) of a and b, returned as the
// exact original value (not a rounded one), a fresh reference the caller
// owns. Exact comparison decides the common case; only for two general
// irrationals whose order that cannot resolve (see number_compare) does the
// decision fall back to comparing them rounded to `digits` fractional digits,
// which guarantees a definite, terminating answer. On a tie (including an
// exact equality, or indistinguishability at `digits`) the first operand is
// returned. An error operand propagates.
Num_t number_min(Num_t a, Num_t b, uint32_t digits) {
if (number_is_error(a)) return a;
if (number_is_error(b)) return b;
int c = number_compare(a, b);
if (c == 2) c = compare_rounded(a, b, digits);
return c <= 0 ? a : b;
}
Num_t number_max(Num_t a, Num_t b, uint32_t digits) {
if (number_is_error(a)) return a;
if (number_is_error(b)) return b;
int c = number_compare(a, b);
if (c == 2) c = compare_rounded(a, b, digits);
return c >= 0 ? a : b;
}
// ---------------------------------------------------------------------------
// Tier 3: irrational reals as linear forms a + b*F
//
// F is pi or sqrt(n) with n a positive non-square integer. Key facts that
// keep the symbolic algebra exact and total:
// - sqrt(n) and sqrt(m) are commensurable (rational multiples of each
// other) iff n*m is a perfect square; then sqrt(m) = sqrt(nm)/n * sqrt(n).
// - If two forms are NOT commensurable (or mix pi with sqrt), their values
// are provably distinct, so comparisons can fall back to interval
// refinement and are guaranteed to terminate.
// - A form with b != 0 is irrational, hence never zero and never equal to
// any rational.
// If the nonnegative rational x is the square of a rational, produce that
// square root and return true.
static bool rational_sqrt_exact(Num_t x, Num_t *out) {
mpq_view v;
mpq_srcptr q = number_mpq_view(x, &v); // x >= 0: caller checked the sign
if (!mpz_perfect_square_p(mpq_numref(q)) || !mpz_perfect_square_p(mpq_denref(q))) return false;
mpq_t r;
mpq_init(r);
mpz_sqrt(mpq_numref(r), mpq_numref(q));
mpz_sqrt(mpq_denref(r), mpq_denref(q));
*out = number_from_mpq(r); // roots of a coprime pair are coprime: canonical
return true;
}
// Build a + b*F, taking ownership of a, b, and arg. Falls back to just the
// rational a when b is zero (e.g. pi - pi), or when F itself degenerates to
// a rational (ln(1) = 0, exp(0) = 1). For FN_LN, also canonicalizes arg to
// stay > 1 (0 < arg < 1 rewrites to -ln(1/arg)), keeping F = ln(arg) > 0 --
// the invariant factor_appr/dival_of_real rely on for every fn, and the one
// that lets "a real is always nonzero" (number_sign) keep holding.
static Num_t make_real(Num_t a, Num_t b, uint32_t fn, Num_t arg) {
if (number_is_error(a) || number_is_error(b) || number_is_error(arg)) {
Num_t e = number_is_error(a) ? a : (number_is_error(b) ? b : arg);
return e;
}
if (fn == FN_LN) {
int c = number_compare(arg, NUMBER_ONE);
if (c == 0) { // ln(1) = 0: the whole b*F term vanishes
return a;
}
if (c < 0) { // ln(r) = -ln(1/r) for 0 < r < 1
Num_t inv_arg = number_inverse(arg);
arg = inv_arg;
Num_t neg_b = number_neg(b);
b = neg_b;
}
} else if (fn == FN_EXP && number_is_zero(arg)) {
// exp(0) = 1: b*F folds into the rational part.
Num_t sum = number_add(a, b);
return sum;
}
if (number_is_zero(b)) {
return a;
}
num_real *r = xmalloc(sizeof(num_real));
r->head.kind = KIND_REAL;
r->fn = fn & 0x7u; // caller always passes a valid FN_* tag
r->a = a;
r->b = b;
r->arg = arg;
mpz_init(r->appr); // lazy: allocates nothing until a value is stored
r->appr_prec = 0; // 0 = no memo yet
return (Num_t){(uint64_t)(uintptr_t)r};
}
// number_pi/number_tau/number_sqrt2 are process-lifetime cached singletons:
// the underlying heap object (and, once anything refines it, its best-known
// approximation, appr/appr_prec in num_real) is built once and held
// forever, so every call after the first hands back the same object rather
// than a fresh allocation, and callers benefit from each other's prior
// precision work instead of starting cold every time. {0} (bits == 0, the
// never-a-valid-number sentinel; see number-design.md "Tagging") marks
// "not yet built". Plain, non-atomic lazy init, consistent with the
// single-threaded assumption elsewhere in this file, so no locking is
// needed.
Num_t number_pi(void) {
static Num_t cached = {0};
if (cached.bits == 0) cached = make_real(small_zero(), number_from_int(1), FN_PI, small_zero());
return cached;
}
Num_t number_tau(void) {
static Num_t cached = {0};
if (cached.bits == 0) cached = make_real(small_zero(), number_from_int(2), FN_PI, small_zero());
return cached;
}
Num_t number_sqrt2(void) {
static Num_t cached = {0};
if (cached.bits == 0) cached = number_sqrt(number_from_int(2));
return cached;
}
Num_t number_sqrt(Num_t x) {
if (number_is_error(x)) return x;
if (is_real_kind(x) || is_irrational_kind(x)) {
// Already irrational (pi, sqrt(n), or a general CR value): no
// closed symbolic form is closed under nested roots, so this falls
// back to the general engine, same as sin/cos/exp/ln/atan of an
// irrational argument. refine_sign both rejects negative x and
// forces a decision on its sign; an irrational value indistinguish-
// able from zero within IRR_MAX_PREC bits gives up -> error, the
// same "genuinely undecidable" handling used throughout this file.
int s;
if (!refine_sign(x, &s)) return err(ERR_UNDECIDABLE);
if (s < 0) return err(ERR_SQRT_NEGATIVE);
return make_irr(IRR_SQRT, x, small_zero());
}
int s = number_sign(x);
if (s < 0) return err(ERR_SQRT_NEGATIVE);
if (s == 0) return small_zero();
Num_t exact;
if (rational_sqrt_exact(x, &exact)) return exact;
// sqrt(p/q) = (1/q) * sqrt(p*q); p*q is a non-square integer here, since
// p/q is reduced and any square p*q would make sqrt(p/q) rational.
mpq_view v;
mpq_srcptr q = number_mpq_view(x, &v); // x > 0 here
mpq_t rad, co;
mpq_init(rad);
mpq_init(co);
mpz_mul(mpq_numref(rad), mpq_numref(q), mpq_denref(q)); // p*q / 1
mpz_set_ui(mpq_numref(co), 1);
mpz_set(mpq_denref(co), mpq_denref(q)); // 1/q: gcd(1, q) == 1, canonical
Num_t radicand = number_from_mpq(rad);
Num_t coeff = number_from_mpq(co);
return make_real(small_zero(), coeff, FN_SQRT, radicand);
}
// exp(x): 0 -> exact 1; ln(r)'s inverse (exp(ln(r)) == r) -> exact r;
// rational x -> the closed EXP(x) symbolic form; otherwise the general
// IRRATIONAL engine.
Num_t number_exp(Num_t x) {
if (number_is_error(x)) return x;
if (number_is_zero(x)) return number_from_int(1);
if (is_real_kind(x)) {
num_real *r = as_real(x);
if (r->fn == FN_LN && number_is_zero(r->a) && number_equal(r->b, NUMBER_ONE))
return r->arg; // exp(ln(r)) == r, exactly
}
if (number_is_rational(x)) return make_real(small_zero(), number_from_int(1), FN_EXP, x);
return make_irr(IRR_EXP, x, small_zero());
}
// ln(x): x <= 0 -> error; 1 -> exact 0; exp(r)'s inverse (ln(exp(r)) == r)
// -> exact r; rational x -> the closed LN(x) symbolic form; otherwise the
// general engine.
Num_t number_ln(Num_t x) {
if (number_is_error(x)) return x;
if (number_sign(x) <= 0) return err(ERR_LOG_NONPOSITIVE);
Num_t one = number_from_int(1);
bool is_one = number_equal(x, one);
if (is_one) return small_zero();
if (is_real_kind(x)) {
num_real *r = as_real(x);
if (r->fn == FN_EXP && number_is_zero(r->a) && number_equal(r->b, NUMBER_ONE))
return r->arg; // ln(exp(r)) == r, exactly
}
if (number_is_rational(x)) return make_real(small_zero(), number_from_int(1), FN_LN, x);
return make_irr(IRR_LN, x, small_zero());
}
// Whether the word-sized magnitude v is exactly base^k for some k >= 0,
// setting *out = k. Alloc-free counterpart of bi_exact_power for values that
// fit in a machine word (every TAG_SMALL magnitude is < 2^31). base == 2 is a
// single-bit test plus a count-trailing-zeros; other bases walk a short,
// fully branch-predictable multiply chain (<= 10 steps to overshoot 2^31 in
// base 10) with no wider arithmetic than the u64 accumulator.
static bool u64_exact_power(uint64_t v, unsigned base, int64_t *out) {
if (v == 0) return false;
if (base == 2) {
if ((v & (v - 1)) != 0) return false; // more than one bit set
*out = __builtin_ctzll(v);
return true;
}
int64_t k = 0;
uint64_t p = 1;
while (p < v) {
p *= base;
k++;
} // v < 2^31, base >= 2: no u64 overflow
if (p != v) return false;
*out = k;
return true;
}
// If x == base^n for an integer n (any sign), sets *out = n and returns true:
// base^n lands in x's numerator (n >= 0) or denominator (n < 0). Shared by
// number_log10 and number_log2 via number_log_base.
static bool exact_log_of_power(Num_t x, unsigned base, Num_t *out) {
if (number_tag(x) == TAG_SMALL) {
// Immediate small rational, always in lowest terms: an exact power is
// either an integer base^n (den 1, num a power of base) or a unit
// reciprocal base^-n (num 1, den a power of base). Everything fits in
// a word, so this whole branch is allocation-free.
int64_t num = small_num(x);
if (num <= 0) return false; // x <= 0: not a power of base (an error elsewhere)
uint64_t den = small_den(x);
int64_t k;
if (den == 1 && u64_exact_power((uint64_t)num, base, &k)) {
*out = number_from_int(k);
return true;
}
if (num == 1 && u64_exact_power(den, base, &k)) {
*out = number_from_int(-k);
return true;
}
return false;
}
if (!number_is_rational(x)) return false;
mpq_view v;
mpq_srcptr q = number_mpq_view(x, &v);
if (mpq_sgn(q) < 0) return false; // x <= 0: not a power of base (and an error elsewhere)
int64_t k;
if (bi_is_one(mpq_denref(q)) && bi_exact_power(mpq_numref(q), base, &k)) {
*out = number_from_int(k);
return true;
}
if (mpz_cmp_ui(mpq_numref(q), 1) == 0 && bi_exact_power(mpq_denref(q), base, &k)) {
*out = number_from_int(-k);
return true;
}
return false;
}
// log_base(x) = ln(x)/ln(base): same domain as ln (x <= 0 -> error). Exact
// when x is an integer power of base. Shared by number_log10 and number_log2.
static Num_t number_log_base(Num_t x, unsigned base) {
Num_t exact;
if (exact_log_of_power(x, base, &exact)) return exact;
Num_t lnx = number_ln(x);
if (number_is_error(lnx)) return lnx;
Num_t b = number_from_int(base);
Num_t lnb = number_ln(b);
Num_t result = number_div(lnx, lnb);
return result;
}
// log10(x) = ln(x)/ln(10): same domain as ln (x <= 0 -> error). Exact for
// x == 10^n, any integer n (positive, negative, or zero).
Num_t number_log10(Num_t x) {
return number_log_base(x, 10);
}
// log2(x) = ln(x)/ln(2): same domain as ln (x <= 0 -> error). Exact for
// x == 2^n, any integer n (positive, negative, or zero).
Num_t number_log2(Num_t x) {
return number_log_base(x, 2);
}
// sqrt(n)/2 as an exact REAL value.
static Num_t half_sqrt(int64_t n) {
Num_t s = number_sqrt(number_from_int(n));
Num_t two = number_from_int(2);
Num_t result = number_div(s, two);
return result;
}
// sin0/cos0 of local*pi/12 for local in {0,2,3,4,6}, the five standard
// angles 0, pi/6, pi/4, pi/3, pi/2. These are exactly the base angles
// reachable when k's denominator is 1, 2, 3, 4, or 6 (see
// exact_trig_of_pi_multiple): denominators that would need pi/12 itself
// (sin(pi/12) = (sqrt6-sqrt2)/4, a sum of two different sqrt-factors our
// single-F symbolic form can't hold directly) never arise from that set --
// every reachable value of "local" after quadrant reduction is a multiple
// of 2, 3, 4, or 6, and none of those ever equal 1 or 5.
static void base_angle_sin_cos(int64_t local, Num_t *sin0, Num_t *cos0) {
switch (local) {
case 0:
*sin0 = small_zero();
*cos0 = number_from_int(1);
break;
case 2:
*sin0 = number_from_ratio(1, 2);
*cos0 = half_sqrt(3);
break;
case 3:
*sin0 = half_sqrt(2);
*cos0 = half_sqrt(2);
break;
case 4:
*sin0 = half_sqrt(3);
*cos0 = number_from_ratio(1, 2);
break;
case 6:
*sin0 = number_from_int(1);
*cos0 = small_zero();
break;
default: abort(); // unreachable: see comment above
}
}
// If x is exactly k*pi for a rational k whose reduced denominator is 1, 2,
// 3, 4, or 6, sin/cos are exact at the standard special angles (0, pi/6,
// pi/4, pi/3, pi/2, and every reflection/rotation of those around the
// circle: 2pi/3, 3pi/4, pi, 3pi/2, ...). The general engine alone could
// only ever approximate these arbitrarily close to their true value, never
// prove them exactly (e.g. sin(pi) would print as "0.000...0" instead of
// the exact 0 it is).
static bool exact_trig_of_pi_multiple(Num_t x, bool want_sin, Num_t *out) {
if (!is_real_kind(x)) return false;
num_real *r = as_real(x);
if (r->fn != FN_PI || !number_is_zero(r->a)) return false;
mpq_view vk;
mpq_srcptr qk = number_mpq_view(r->b, &vk); // x == k*pi
mpz_srcptr kden = mpq_denref(qk);
uint32_t den_val;
if (bi_is_one(kden)) {
den_val = 1;
} else if (mpz_cmp_ui(kden, 2) == 0 || mpz_cmp_ui(kden, 3) == 0 || mpz_cmp_ui(kden, 4) == 0
|| mpz_cmp_ui(kden, 6) == 0) {
den_val = (uint32_t)mpz_get_ui(kden); // one of 2/3/4/6 (guarded above)
} else {
return false;
}
uint64_t num_mag;
// Bounded well under INT64_MAX/12 so sign*num_mag*(12/den_val) can't
// overflow below. This is a fast path for ordinary angles, not a
// general bignum-multiple-of-pi solver. bi_fits_u64 reads magnitude
// limbs, so it works on the signed numerator directly.
if (!bi_fits_u64(mpq_numref(qk), &num_mag) || num_mag > (uint64_t)INT64_MAX / 12) return false;
int sign = mpq_sgn(qk);
// m = k * 12 (an integer, since den_val divides 12); reduce mod 24 (the
// period of pi/12) into [0, 24), angle and sign together, so negative
// k is handled by the reduction itself, not a separate sign case.
int64_t m = sign * (int64_t)num_mag * (12 / (int64_t)den_val);
int64_t rmod = ((m % 24) + 24) % 24;
int64_t local;
int sin_sign, cos_sign;
if (rmod <= 6) {
local = rmod;
sin_sign = 1;
cos_sign = 1;
} else if (rmod <= 12) {
local = 12 - rmod;
sin_sign = 1;
cos_sign = -1;
} else if (rmod <= 18) {
local = rmod - 12;
sin_sign = -1;
cos_sign = -1;
} else {
local = 24 - rmod;
sin_sign = -1;
cos_sign = 1;
}
Num_t sin0, cos0;
base_angle_sin_cos(local, &sin0, &cos0);
if (want_sin) {
if (sin_sign < 0) {
*out = number_neg(sin0);
} else {
*out = sin0;
}
} else {
if (cos_sign < 0) {
*out = number_neg(cos0);
} else {
*out = cos0;
}
}
return true;
}
// sin(x): 0 -> exact 0; k*pi (k rational with denominator 1 or 2) -> exact;
// otherwise the general engine (range-reduced mod 2pi).
Num_t number_sin(Num_t x) {
if (number_is_error(x)) return x;
if (number_is_zero(x)) return small_zero();
Num_t exact;
if (exact_trig_of_pi_multiple(x, true, &exact)) return exact;
return make_irr(IRR_SIN, x, small_zero());
}
// cos(x): 0 -> exact 1; k*pi (as above) -> exact; otherwise the general engine.
Num_t number_cos(Num_t x) {
if (number_is_error(x)) return x;
if (number_is_zero(x)) return number_from_int(1);
Num_t exact;
if (exact_trig_of_pi_multiple(x, false, &exact)) return exact;
return make_irr(IRR_COS, x, small_zero());
}
// tan(x) = sin(x)/cos(x). Poles (x == pi/2 + k*pi) surface as NUMBER_ERROR
// via division's inability to prove cos(x) nonzero, not a domain check here.
Num_t number_tan(Num_t x) {
if (number_is_error(x)) return x;
Num_t s = number_sin(x);
Num_t c = number_cos(x);
Num_t result = number_div(s, c);
return result;
}
// Shared tail for exact_atan_of_special_value and exact_asin_of_special_value:
// given the pi/12 multiple already identified (twelfths < 0 means "no exact
// value"), applies x's sign and returns twelfths/12 * pi via out.
static bool twelfths_times_pi(Num_t x, int64_t twelfths, Num_t *out) {
if (twelfths < 0) return false;
if (number_is_negative(x)) twelfths = -twelfths;
Num_t pi = number_pi();
Num_t frac = number_from_ratio(twelfths, 12);
*out = number_mul(frac, pi);
return true;
}
// If |x| equals one of the standard tan values (sqrt(3)/3, 1, sqrt(3)),
// atan(x) is an exact multiple of pi/12 (pi/6, pi/4, pi/3), the inverse
// direction of exact_trig_of_pi_multiple. atan(0) is handled by
// number_atan's own escape before this is ever called.
static bool exact_atan_of_special_value(Num_t x, Num_t *out) {
Num_t ax = number_abs(x);
Num_t one = number_from_int(1);
Num_t sqrt3 = number_sqrt(number_from_int(3));
Num_t sqrt3_3 = number_div(sqrt3, number_from_int(3));
int64_t twelfths = -1;
if (number_equal(ax, sqrt3_3)) twelfths = 2;
else if (number_equal(ax, one)) twelfths = 3;
else if (number_equal(ax, sqrt3)) twelfths = 4;
return twelfths_times_pi(x, twelfths, out);
}
// atan(x): 0 -> exact 0; +-sqrt(3)/3, +-1, +-sqrt(3) -> exact (+-pi/6, pi/4,
// pi/3); otherwise the general engine. Defined for all x.
Num_t number_atan(Num_t x) {
if (number_is_error(x)) return x;
if (number_is_zero(x)) return small_zero();
Num_t exact;
if (exact_atan_of_special_value(x, &exact)) return exact;
return make_irr(IRR_ATAN, x, small_zero());
}
// atan2(y, x): the angle in (-pi, pi] of the point (x, y), i.e. atan(y/x)
// placed in the correct quadrant by x's and y's signs. On the y-axis (x == 0)
// the result is exactly +-pi/2; number_atan already yields exact multiples of
// pi/12 where applicable, so e.g. atan2(1, 1) == pi/4 and atan2(1, -1) ==
// 3*pi/4 exactly. atan2(0, 0) has no defined direction -> error (this differs
// from C's atan2, which returns 0). An error operand propagates.
Num_t number_atan2(Num_t y, Num_t x) {
if (number_is_error(y)) return y;
if (number_is_error(x)) return x;
int sx = number_sign(x), sy = number_sign(y);
if (sx == 0 && sy == 0) return err(ERR_ATAN2_ORIGIN);
if (sx == 0) { // straight up (+pi/2) or down (-pi/2)
Num_t pi = number_pi();
Num_t half = number_from_ratio(sy > 0 ? 1 : -1, 2);
Num_t res = number_mul(pi, half);
return res;
}
Num_t q = number_div(y, x);
Num_t at = number_atan(q); // principal value in (-pi/2, pi/2)
if (sx > 0) return at; // right half-plane: atan(y/x) is already correct
// Left half-plane: shift by +pi for y >= 0, -pi for y < 0 (so the branch
// cut sits on the negative x-axis, which maps to +pi).
Num_t pi = number_pi();
Num_t res = sy >= 0 ? number_add(at, pi) : number_sub(at, pi);
return res;
}
// If |x| equals one of the standard sin values (1/2, sqrt(2)/2, sqrt(3)/2),
// asin(x) is an exact multiple of pi/12 (pi/6, pi/4, pi/3), the inverse
// direction of exact_trig_of_pi_multiple. asin(0) and asin(+-1) are handled
// by number_asin's own escapes before this is ever called.
static bool exact_asin_of_special_value(Num_t x, Num_t *out) {
Num_t ax = number_abs(x);
Num_t half = number_from_ratio(1, 2);
Num_t sqrt2_2 = half_sqrt(2);
Num_t sqrt3_2 = half_sqrt(3);
int64_t twelfths = -1;
if (number_equal(ax, half)) twelfths = 2;
else if (number_equal(ax, sqrt2_2)) twelfths = 3;
else if (number_equal(ax, sqrt3_2)) twelfths = 4;
return twelfths_times_pi(x, twelfths, out);
}
// asin(x) = atan(x/sqrt(1-x^2)), domain |x| <= 1. Exact at x == 0, +-1/2,
// +-sqrt(2)/2, +-sqrt(3)/2, +-1 (0, +-pi/6, pi/4, pi/3, pi/2). 1-x^2 goes
// through number_sqrt, so this works for an already-irrational x too (e.g.
// asin(sin(0.5))): number_sqrt falls back to the general engine rather
// than erroring, and so does this.
Num_t number_asin(Num_t x) {
if (number_is_error(x)) return x;
if (number_is_zero(x)) return small_zero();
Num_t one = number_from_int(1);
Num_t neg_one = number_neg(one);
int cmp_hi = number_compare(x, one);
int cmp_lo = number_compare(x, neg_one);
// 2 is "unordered" (see number_compare), not "greater": an in-domain value
// whose comparison against +-1 refinement can't settle is undecidable, not
// a domain violation, and must not be reported as one.
if (cmp_hi == 2 || cmp_lo == 2) return err(ERR_UNDECIDABLE);
if (cmp_hi > 0 || cmp_lo < 0) {
return err(ERR_ASIN_ACOS_DOMAIN); // |x| > 1: outside asin's domain
}
if (cmp_hi == 0 || cmp_lo == 0) {
Num_t pi = number_pi();
Num_t two = number_from_int(2);
Num_t half_pi = number_div(pi, two);
if (cmp_lo == 0) {
Num_t result = number_neg(half_pi);
return result;
}
return half_pi;
}
Num_t exact;
if (exact_asin_of_special_value(x, &exact)) {
return exact;
}
Num_t x2 = number_mul(x, x);
Num_t one_minus_x2 = number_sub(one, x2);
Num_t denom = number_sqrt(one_minus_x2); // 1-x^2 > 0 here (|x| < 1 strictly)
Num_t ratio = number_div(x, denom);
Num_t result = number_atan(ratio);
return result;
}
// acos(x) = pi/2 - asin(x). Same domain as asin.
Num_t number_acos(Num_t x) {
if (number_is_error(x)) return x;
Num_t asinx = number_asin(x);
if (number_is_error(asinx)) return asinx;
Num_t pi = number_pi();
Num_t two = number_from_int(2);
Num_t half_pi = number_div(pi, two);
Num_t result = number_sub(half_pi, asinx);
return result;
}
// sinh/cosh/tanh via exp: no new IRR ops, pure composition. cosh(x) is
// never zero for real x, so tanh has no pole to worry about (unlike tan).
Num_t number_sinh(Num_t x) {
if (number_is_error(x)) return x;
if (number_is_zero(x)) return small_zero();
Num_t ex = number_exp(x);
Num_t negx = number_neg(x);
Num_t enx = number_exp(negx);
Num_t diff = number_sub(ex, enx);
Num_t two = number_from_int(2);
Num_t result = number_div(diff, two);
return result;
}
Num_t number_cosh(Num_t x) {
if (number_is_error(x)) return x;
if (number_is_zero(x)) return number_from_int(1);
Num_t ex = number_exp(x);
Num_t negx = number_neg(x);
Num_t enx = number_exp(negx);
Num_t sum = number_add(ex, enx);
Num_t two = number_from_int(2);
Num_t result = number_div(sum, two);
return result;
}
Num_t number_tanh(Num_t x) {
if (number_is_error(x)) return x;
if (number_is_zero(x)) return small_zero();
Num_t s = number_sinh(x);
Num_t c = number_cosh(x);
Num_t result = number_div(s, c);
return result;
}
// x^n for integer n (any sign), via exact repeated squaring. x != 0.
static Num_t int_pow(Num_t x, int64_t n) {
bool neg = n < 0;
uint64_t un = neg ? (uint64_t)(-(n + 1)) + 1 : (uint64_t)n; // avoid negating INT64_MIN
Num_t base = x;
Num_t result = number_from_int(1);
while (un) {
if (un & 1) {
Num_t next = number_mul(result, base);
result = next;
}
un >>= 1;
if (un) {
Num_t nb = number_mul(base, base);
base = nb;
}
}
if (neg) {
Num_t inv = number_inverse(result);
return inv;
}
return result;
}
// x^(1/q) exactly, if rational x (already checked >= 0 by the caller for
// even q) has a rational q-th root, i.e. its numerator and denominator
// are each a perfect q-th power. Only ever exact or "doesn't apply": there
// is no symbolic cbrt(n)-style form for q > 2 the way there is for
// sqrt(n), so a non-exact root just returns false (the general engine
// still gets the numerically correct answer, just not an exact one).
static bool exact_rational_root(Num_t x, uint32_t q, Num_t *out) {
mpq_view v;
mpq_srcptr xq = number_mpq_view(x, &v);
mpq_t r;
mpq_init(r);
// mpz_root is signed: an odd q keeps a negative numerator's sign, and
// the caller has already excluded negative x for even q.
bool exact_n = mpz_root(mpq_numref(r), mpq_numref(xq), q) != 0;
bool exact_d = mpz_root(mpq_denref(r), mpq_denref(xq), q) != 0;
if (!exact_n || !exact_d) {
mpq_clear(r);
return false;
}
*out = number_from_mpq(r); // roots of a coprime pair are coprime: canonical
return true;
}
// x^y: a rational-escape ladder before falling back to exp(y*ln(x)).
Num_t number_pow(Num_t x, Num_t y) {
if (number_is_error(x)) return x;
if (number_is_error(y)) return y;
if (number_is_zero(y)) return number_from_int(1); // x^0 == 1, including 0^0
if (number_is_zero(x)) return number_sign(y) > 0 ? small_zero() : err(ERR_POW_ZERO_NEGATIVE);
Num_t one = number_from_int(1);
bool y_is_one = number_equal(y, one);
if (y_is_one) return x;
if (number_is_rational(y)) {
bool y_int;
int64_t yi = number_to_int64(y, &y_int);
if (y_int && yi != INT64_MIN) // int_pow negates: exclude INT64_MIN
return int_pow(x, yi);
mpq_view vy;
mpq_srcptr qy = number_mpq_view(y, &vy);
// Non-integer rational exponent p/q (q > 1, since is_int is false):
// q == 2 keeps the existing sqrt path (which also covers the
// *irrational* sqrt(n) symbolic form for non-perfect-squares); q > 2
// only has an exact result when x itself is a rational q-th power
// (no symbolic root form exists for q > 2), otherwise falls through
// to the general engine below.
uint64_t qmag = 0, pmag = 0;
bool q_fits = bi_fits_u64(mpq_denref(qy), &qmag) && qmag <= 1000; // a "nice" root has a small q
bool p_fits = bi_fits_u64(mpq_numref(qy), &pmag) && pmag <= (uint64_t)INT64_MAX;
int psign = mpq_sgn(qy);
int64_t p = psign < 0 ? -(int64_t)pmag : (int64_t)pmag;
if (q_fits && qmag == 2 && p_fits) {
if (number_sign(x) < 0) return err(ERR_SQRT_NEGATIVE);
Num_t root = number_sqrt(x);
Num_t result = int_pow(root, p);
return result;
}
if (q_fits && qmag > 2 && p_fits && number_is_rational(x)) {
bool even_root = qmag % 2 == 0;
if (!(even_root && number_is_negative(x))) {
Num_t root;
if (exact_rational_root(x, (uint32_t)qmag, &root)) {
Num_t result = int_pow(root, p);
return result;
}
}
}
}
if (number_sign(x) < 0) return err(ERR_POW_NEGATIVE_BASE); // complex result
Num_t lnx = number_ln(x);
Num_t ylnx = number_mul(y, lnx);
Num_t result = number_exp(ylnx);
return result;
}
// If sqrt(r2) is a rational multiple of sqrt(r1), set *conv to that multiple.
static bool sqrt_commensurable(Num_t r1, Num_t r2, Num_t *conv) {
if (number_equal(r1, r2)) {
*conv = number_from_int(1);
return true;
}
Num_t m = number_mul(r1, r2);
Num_t s;
bool ok = rational_sqrt_exact(m, &s);
if (!ok) return false;
*conv = number_div(s, r1); // sqrt(r2) = (sqrt(r1*r2)/r1) * sqrt(r1)
return true;
}
// If b1 and b2 are both integers (any sign), sets *out to r1^b1 * r2^b2 (a
// positive rational) and returns true: b1*ln(r1) + b2*ln(r2) then collapses
// to ln(*out) with coefficient 1, e.g. 2*ln(2) - ln(4) combines to ln(1) =
// 0 instead of two unrelated LN terms. False (leaving the two terms
// unrepresentable as one, so the caller falls back to the general engine)
// when either coefficient is non-integer, e.g. ln(2) + (1/2)*ln(3).
static bool ln_combine(Num_t b1, Num_t r1, Num_t b2, Num_t r2, Num_t *out) {
bool ok1, ok2;
int64_t i1 = number_to_int64(b1, &ok1), i2 = number_to_int64(b2, &ok2);
if (!ok1 || !ok2 || i1 == INT64_MIN || i2 == INT64_MIN) // int_pow negates
return false;
Num_t p1 = int_pow(r1, i1);
Num_t p2 = int_pow(r2, i2);
*out = number_mul(p1, p2);
return true;
}
// At least one operand is a real; neither is an error.
static Num_t real_add(Num_t x, Num_t y) {
if (!is_real_kind(x)) {
Num_t t = x;
x = y;
y = t;
}
num_real *rx = as_real(x);
if (!is_real_kind(y)) { // real + rational: shift the rational part
return make_real(number_add(rx->a, y), rx->b, rx->fn, rx->arg);
}
num_real *ry = as_real(y);
if (rx->fn != ry->fn) return NUMBER_ERROR; // pi + sqrt(n): outside every representable form
if (rx->fn == FN_PI) {
return make_real(number_add(rx->a, ry->a), number_add(rx->b, ry->b), FN_PI, small_zero());
}
if (rx->fn == FN_EXP) {
if (!number_equal(rx->arg, ry->arg)) return NUMBER_ERROR; // exp(a) + exp(b), a != b: not representable
return make_real(number_add(rx->a, ry->a), number_add(rx->b, ry->b), FN_EXP, rx->arg);
}
if (rx->fn == FN_LN) {
if (number_equal(rx->arg, ry->arg)) {
return make_real(number_add(rx->a, ry->a), number_add(rx->b, ry->b), FN_LN, rx->arg);
}
// ln(r1)*b1 + ln(r2)*b2 = ln(r1^b1 * r2^b2) when b1, b2 are both
// integers (see ln_combine): folds to a single LN term.
Num_t combined;
if (!ln_combine(rx->b, rx->arg, ry->b, ry->arg, &combined))
return NUMBER_ERROR; // e.g. ln(2) + (1/2)*ln(3): not representable
return make_real(number_add(rx->a, ry->a), number_from_int(1), FN_LN, combined);
}
Num_t conv;
if (!sqrt_commensurable(rx->arg, ry->arg, &conv)) return NUMBER_ERROR; // e.g. sqrt(2) + sqrt(3)
Num_t b2 = number_mul(ry->b, conv);
Num_t a = number_add(rx->a, ry->a);
Num_t b = number_add(rx->b, b2);
return make_real(a, b, FN_SQRT, rx->arg);
}
static Num_t real_mul(Num_t x, Num_t y) {
if (!is_real_kind(x)) {
Num_t t = x;
x = y;
y = t;
}
num_real *rx = as_real(x);
if (!is_real_kind(y)) { // scale by a rational
if (number_is_zero(y)) return small_zero();
return make_real(number_mul(rx->a, y), number_mul(rx->b, y), rx->fn, rx->arg);
}
num_real *ry = as_real(y);
if (rx->fn == FN_EXP && ry->fn == FN_EXP && number_is_zero(rx->a) && number_is_zero(ry->a)) {
// b1*exp(e1) * b2*exp(e2) = b1*b2*exp(e1+e2); only the pure (no
// rational addend) form multiplies out this way; (a1+b1 e^x1)
// (a2+b2 e^x2) with a1 or a2 nonzero expands into terms this
// linear a+b*F form can't hold.
Num_t sum = number_add(rx->arg, ry->arg);
Num_t coeff = number_mul(rx->b, ry->b);
return make_real(small_zero(), coeff, FN_EXP, sum);
}
if (rx->fn != FN_SQRT || ry->fn != FN_SQRT)
return NUMBER_ERROR; // pi*pi, pi*sqrt(n), exp(x)*exp(y) [mixed a!=0]: not representable
Num_t conv;
if (sqrt_commensurable(rx->arg, ry->arg, &conv)) {
// Rewrite y over sqrt(r) with r = rx->arg, then multiply in
// Q(sqrt r): (a1 + b1*s)(a2 + b2*s) = a1a2 + b1b2*r + (a1b2 + a2b1)*s
Num_t b2 = number_mul(ry->b, conv);
Num_t a1a2 = number_mul(rx->a, ry->a);
Num_t b1b2 = number_mul(rx->b, b2);
Num_t b1b2r = number_mul(b1b2, rx->arg);
Num_t a = number_add(a1a2, b1b2r);
Num_t t1 = number_mul(rx->a, b2);
Num_t t2 = number_mul(ry->a, rx->b);
Num_t b = number_add(t1, t2);
return make_real(a, b, FN_SQRT, rx->arg);
}
if (number_is_zero(rx->a) && number_is_zero(ry->a)) {
// b1*sqrt(r1) * b2*sqrt(r2) = b1*b2*sqrt(r1*r2)
Num_t m = number_mul(rx->arg, ry->arg);
Num_t root = number_sqrt(m);
Num_t coeff = number_mul(rx->b, ry->b);
Num_t res = number_mul(root, coeff);
return res;
}
return NUMBER_ERROR; // (a1+b1*sqrt 2)(a2+b2*sqrt 3): three distinct radicals
}
// y is nonzero and at least one operand is a real; neither is an error.
static Num_t real_div(Num_t x, Num_t y) {
if (!is_real_kind(y)) { // real / rational
Num_t inv = number_inverse(y);
Num_t res = real_mul(x, inv);
return res;
}
num_real *ry = as_real(y);
if (is_real_kind(x)) {
num_real *rx = as_real(x);
// (bx*F) / (by*F) = bx/by exactly, for any F. This is the only rule
// that covers FN_PI and FN_LN, which have no general inverse form
// below: without it, pi/pi, tau/pi and ln(4)/ln(2) fall through to an
// opaque IRR_DIV node that no later refinement can prove equal to its
// rational value, so `pi/pi == 1` would answer no (and number_mod,
// number_floor and is_rational would all follow it into the weeds).
// FN_PI ignores arg, so only the other fns need it compared.
if (rx->fn == ry->fn && number_is_zero(rx->a) && number_is_zero(ry->a)
&& (ry->fn == FN_PI || number_equal(rx->arg, ry->arg)))
return number_div(rx->b, ry->b);
}
if (ry->fn == FN_EXP && number_is_zero(ry->a)) {
// 1/(b*exp(e)) = (1/b)*exp(-e): only the pure (no rational addend)
// form inverts inside this closed set, since 1/(a+b*exp(e)) with a
// nonzero isn't again of the a+b*F shape.
Num_t inv_b = number_inverse(ry->b);
Num_t neg_arg = number_neg(ry->arg);
Num_t inv = make_real(small_zero(), inv_b, FN_EXP, neg_arg);
Num_t res = number_mul(x, inv);
return res;
}
if (ry->fn != FN_SQRT) return NUMBER_ERROR; // 1/(a+b*pi), 1/(a+b*ln r), 1/(a+b*exp e) [a!=0]: outside every form
// 1/(a + b*sqrt r) = (a - b*sqrt r) / (a^2 - b^2*r); the denominator is
// nonzero because sqrt(r) is irrational.
Num_t a2 = number_mul(ry->a, ry->a);
Num_t b2 = number_mul(ry->b, ry->b);
Num_t b2r = number_mul(b2, ry->arg);
Num_t norm = number_sub(a2, b2r);
Num_t ca = number_div(ry->a, norm);
Num_t cb_pos = number_div(ry->b, norm);
Num_t cb = number_neg(cb_pos);
Num_t inv = make_real(ca, cb, FN_SQRT, ry->arg);
Num_t res = number_mul(x, inv);
return res;
}
// --- Approximation engine ---
//
// Fixed-point approximations: a (sign, magnitude) pair V approximating
// value * 2^w with |error| <= 4. Callers that need certainty evaluate both
// ends of the +-4 interval and retry at double the precision when the two
// ends disagree; termination is guaranteed whenever the approximated value
// cannot equal the decision boundary (always true for irrationals).
// Moves z (signed) out into a fresh heap bigint magnitude, returning its
// sign; z is cleared. The bridge from a series loop's in-place signed mpz
// locals back to the (sign, magnitude-bigint) protocol the engine speaks.
static int mpz_take(mpz_t z, bigint **out) {
int s = mpz_sgn(z);
mpz_abs(z, z);
bigint *b = bi_new();
mpz_swap(b, z);
mpz_clear(z);
*out = b;
return s;
}
// The series loops below run on in-place mpz_t locals: each iteration
// reuses the same term/sum/power buffers (GMP grows them once and then
// they're steady-state), where the old bigint value style allocated and
// freed several temporaries per term. Signs ride in the mpz's themselves --
// mpz division truncates toward zero, which matches the old
// magnitude-truncate-then-apply-sign arithmetic exactly.
// arctan(1/x) * 2^w by truncated Taylor series; x*x must fit in uint32.
static bigint *arctan_recip(uint32_t x, uint32_t w) {
mpz_t p, term, sum;
mpz_init_set_ui(p, 1);
mpz_mul_2exp(p, p, w);
mpz_tdiv_q_ui(p, p, x); // x^-(2k+1) * 2^w, k = 0
mpz_init(term);
mpz_init(sum);
for (uint32_t k = 0; mpz_sgn(p) != 0; k++) {
mpz_tdiv_q_ui(term, p, 2 * k + 1);
if (k % 2 == 0) mpz_add(sum, sum, term);
else mpz_sub(sum, sum, term);
mpz_tdiv_q_ui(p, p, x * x);
}
mpz_clear(p);
mpz_clear(term);
bigint *res;
mpz_take(sum, &res); // sum > 0: the k=0 term dominates
return res;
}
// pi * 2^prec with |error| <= 2, via Machin's formula:
// pi = 16*arctan(1/5) - 4*arctan(1/239). The 24 guard bits absorb the
// per-term truncation slop (~5 * work-bits) for any precision below ~10^6.
static bigint *machin_pi(uint32_t prec) {
uint32_t w = prec + 24;
bigint *a5 = arctan_recip(5, w);
bigint *a239 = arctan_recip(239, w);
bigint *t16 = bi_shl(a5, 4);
bigint *t4 = bi_shl(a239, 2);
bigint *diff = bi_sub(t16, t4);
bi_free(a5);
bi_free(a239);
bi_free(t16);
bi_free(t4);
bigint *res = bi_shr(diff, 24);
bi_free(diff);
return res;
}
static bigint *g_pi_appr = NULL; // global memo for the shared constant pi
static uint32_t g_pi_prec = 0;
// pi * 2^prec with |error| <= 2, memoized globally. Standalone (not just
// factor_appr's FN_PI case) so irr_fixed's SIN/COS range reduction can reach
// it without a num_real to hang off of.
static bigint *pi_appr(uint32_t prec) {
if (g_pi_appr == NULL || g_pi_prec < prec) {
uint32_t p = prec + 64; // overshoot so nearby requests hit the memo
bigint *fresh = machin_pi(p);
bi_free(g_pi_appr);
g_pi_appr = fresh;
g_pi_prec = p;
}
return bi_shr(g_pi_appr, g_pi_prec - prec);
}
// F * 2^prec with |error| <= 2, memoized (globally for pi, per-object for
// sqrt/ln/exp). Returns a fresh bigint owned by the caller. The 64-bit
// overshoot every branch uses before storing into r->appr makes whatever
// error the underlying computation has (<=1 for isqrt, <=2 for pi_appr,
// <=4 for exp_fixed_core/ln_fixed_core) utterly negligible once shifted
// back down by 64 bits to the requested prec, so the <=2 contract holds
// regardless of which branch produced it.
static bigint *factor_appr(num_real *r, uint32_t prec) {
if (r->fn == FN_PI) return pi_appr(prec);
if (r->appr_prec == 0 || r->appr_prec < prec) {
uint32_t p = prec + 64; // overshoot so nearby requests hit the memo
bigint *fresh;
if (r->fn == FN_LN) {
// r->arg > 1 (make_real's canonicalization), so ln(arg) > 0:
// always succeeds here, unlike the general IRR_LN case.
int sign;
if (!ln_fixed_core(r->arg, p, &sign, &fresh)) abort();
} else if (r->fn == FN_EXP) {
int sign;
if (!exp_fixed_core(r->arg, p, &sign, &fresh)) abort();
} else { // FN_SQRT of a positive non-square integer
mpq_view vr;
mpq_srcptr rq = number_mpq_view(r->arg, &vr);
mpz_t shifted;
mpz_init(shifted);
mpz_mul_2exp(shifted, mpq_numref(rq), 2 * p);
fresh = bi_new();
mpz_sqrt(fresh, shifted); // floor(sqrt(n) * 2^p): error <= 1
mpz_clear(shifted);
}
mpz_swap(r->appr, fresh);
bi_free(fresh); // now holds the previous memo
r->appr_prec = p;
}
return bi_shr(r->appr, r->appr_prec - prec);
}
// (s1, m1) + (s2, m2) -> (sign, *out); consumes m1 and m2.
static int signed_add(int s1, bigint *m1, int s2, bigint *m2, bigint **out) {
int sign;
if (s1 == s2) {
*out = bi_add(m1, m2);
sign = s1;
} else {
int c = bi_cmp(m1, m2);
if (c >= 0) {
*out = bi_sub(m1, m2);
sign = s1;
} else {
*out = bi_sub(m2, m1);
sign = s2;
}
if (bi_is_zero(*out)) sign = 0;
}
bi_free(m1);
bi_free(m2);
return sign;
}
// (a + b*F) * 2^w as sign/magnitude with |error| <= 4. a and b are passed
// explicitly so digit generation can fold exact powers of ten into them;
// r supplies F (fn/arg) and its memo.
static void real_fixed(num_real *r, Num_t a, Num_t b, uint32_t w, int *sign_out, bigint **mag_out) {
// T = b * F * 2^w, error <= 2: evaluate F with enough extra precision
// to absorb both F's error scaled by |b| and the final truncation. F is
// positive (every factor's invariant), so T's sign is b's own, and
// truncation toward zero matches the old magnitude arithmetic.
mpq_view vb, va;
mpq_srcptr qb = number_mpq_view(b, &vb);
uint32_t hb = (uint32_t)mpz_sizeinbase(mpq_numref(qb), 2); // b != 0
bigint *F = factor_appr(r, w + hb + 4);
mpz_t acc, t;
mpz_init(acc);
mpz_init(t);
mpz_mul(acc, mpq_numref(qb), F);
bi_free(F);
mpz_mul_2exp(t, mpq_denref(qb), hb + 4);
mpz_tdiv_q(acc, acc, t);
// + a * 2^w, error <= 1, truncated toward zero independently (same
// rounding the old separate-magnitudes signed_add produced)
mpq_srcptr qa = number_mpq_view(a, &va);
mpz_mul_2exp(t, mpq_numref(qa), w);
mpz_tdiv_q(t, t, mpq_denref(qa));
mpz_add(acc, acc, t);
mpz_clear(t);
*sign_out = mpz_take(acc, mag_out);
}
// Any non-error number as fixed-point at precision w, |error| <= 4. False
// if the approximation can't be resolved (see IRR_MAX_PREC).
static bool value_fixed(Num_t x, uint32_t w, int *sign_out, bigint **mag_out) {
if (is_real_kind(x)) {
num_real *r = as_real(x);
real_fixed(r, r->a, r->b, w, sign_out, mag_out);
return true;
}
if (is_irrational_kind(x)) return irr_fixed(as_irr(x), w, sign_out, mag_out);
mpq_view v;
mpq_srcptr q = number_mpq_view(x, &v);
mpz_t t;
mpz_init(t);
mpz_mul_2exp(t, mpq_numref(q), w);
mpz_tdiv_q(t, t, mpq_denref(q));
*sign_out = mpz_take(t, mag_out);
return true;
}
// ---------------------------------------------------------------------------
// Hardware double-interval fast path (number-design.md "Hardware floating-point
// fast path"). Before any scaled-bigint evaluation, a value can be bounded
// by a hardware-double interval [lo, hi] computed with outward rounding: a
// recursive descent over the same rational/real/DAG structure value_fixed
// walks, each step widening its result by enough ulps to make the bounds
// rigorous. If the interval is tight enough to decide the question being
// asked (a sign for refine_sign, or a k-digit decimal rounding for
// real_to_string / irr_to_string_digits; number_to_double is deliberately
// excluded, see refine_to_double), the answer is served at hardware speed and
// is still exact; if not (cancellation, overflow, genuinely more precision
// demanded), evaluation falls through to the bigint path unchanged, so a wide
// interval costs one cheap failed attempt, never a wrong answer.
//
// Rigor budget per operation:
// - IEEE + - * / and sqrt are correctly rounded: 1-ulp outward widening
// makes their interval extensions rigorous outright.
// - libm's exp/ln/atan/sin/cos are NOT guaranteed correctly rounded.
// glibc documents worst-case errors of 1-2 ulp for these functions on
// every mainstream target (see its "Known Maximum Errors" manual
// section); DIVAL_LIBM_ULPS widens by 8, a 4x margin over that. A libm
// exceeding 8 ulps of error would erode this margin silently, so build
// with -DNUMBER_NO_DOUBLE_FILTER to remove the fast path entirely (and
// with it any reliance on libm quality) at some cost to
// approximation-heavy workloads; every result then comes from the
// bigint path alone.
// - sin/cos additionally bound by Lipschitz continuity (|sin'| <= 1)
// around the interval midpoint rather than by monotonicity analysis,
// so they're valid for any argument width; a huge argument just yields
// a uselessly wide (but still correct) interval that falls through.
//
// The descent is unmemoized (doubles are too cheap to be worth caching), so
// a heavily-shared DAG could be visited exponentially often; a fixed visit
// budget cuts the attempt off long before that costs anything real and
// falls back to the (memoized) bigint path.
#ifndef NUMBER_NO_DOUBLE_FILTER
typedef struct {
double lo, hi;
} dival;
static inline double ival_prev(double v) {
return nextafter(v, -(double)INFINITY);
}
static inline double ival_next(double v) {
return nextafter(v, (double)INFINITY);
}
#define DIVAL_LIBM_ULPS 8
static double ival_down_k(double v, int k) {
for (int i = 0; i < k; i++)
v = ival_prev(v);
return v;
}
static double ival_up_k(double v, int k) {
for (int i = 0; i < k; i++)
v = ival_next(v);
return v;
}
static dival dival_add(dival a, dival b) {
return (dival){ival_prev(a.lo + b.lo), ival_next(a.hi + b.hi)};
}
static dival dival_sub(dival a, dival b) {
return (dival){ival_prev(a.lo - b.hi), ival_next(a.hi - b.lo)};
}
static dival dival_mul(dival a, dival b) {
double p1 = a.lo * b.lo, p2 = a.lo * b.hi, p3 = a.hi * b.lo, p4 = a.hi * b.hi;
return (dival){ival_prev(fmin(fmin(p1, p2), fmin(p3, p4))), ival_next(fmax(fmax(p1, p2), fmax(p3, p4)))};
}
// The correctly rounded double nearest pi; the true pi is strictly inside
// [ival_prev, ival_next] of it. (This is written in hex rather than M_PI
// because M_PI is a POSIX extension, not ISO C, and this file builds with -Wpedantic.)
#define PI_DOUBLE 0x1.921fb54442d18p+1
// Every dival_of call site's descent shares one visit budget: the DAG is
// walked without memoization, so sharing could otherwise blow up the visit
// count exponentially (e.g. pow's repeated squaring reuses each node
// twice per level). Generous for any expression a fast path should serve;
// a DAG that exhausts it belongs on the memoized bigint path anyway.
#define DIVAL_VISIT_BUDGET 512
static bool dival_of_real(const num_real *r, Num_t a, Num_t b, dival *out, int *budget);
// Rigorous double bounds on any non-error number: *out straddles the true
// value on success. False when bounds can't be established (overflow to
// infinity, a domain edge, or the visit budget running out), never wrong,
// just unavailable.
static bool dival_of(Num_t x, dival *out, int *budget) {
if (--*budget < 0) return false;
if (number_is_error(x)) return false;
if (number_is_rational(x)) {
double v = number_to_double(x); // correctly rounded for both rational tiers
if (!isfinite(v)) return false;
*out = (dival){ival_prev(v), ival_next(v)};
return true;
}
if (is_real_kind(x)) {
num_real *r = as_real(x);
return dival_of_real(r, r->a, r->b, out, budget);
}
num_irr *n = as_irr(x);
dival a, res;
if (!dival_of(n->x, &a, budget)) return false;
switch (n->op) {
case IRR_ADD:
case IRR_SUB:
case IRR_MUL:
case IRR_DIV: {
dival b;
if (!dival_of(n->y, &b, budget)) return false;
if (n->op == IRR_ADD) res = dival_add(a, b);
else if (n->op == IRR_SUB) res = dival_sub(a, b);
else if (n->op == IRR_MUL) res = dival_mul(a, b);
else {
if (b.lo <= 0 && b.hi >= 0) return false; // can't bound across a pole
double q1 = a.lo / b.lo, q2 = a.lo / b.hi, q3 = a.hi / b.lo, q4 = a.hi / b.hi;
res = (dival){ival_prev(fmin(fmin(q1, q2), fmin(q3, q4))), ival_next(fmax(fmax(q1, q2), fmax(q3, q4)))};
}
break;
}
case IRR_EXP:
res = (dival){ival_down_k(exp(a.lo), DIVAL_LIBM_ULPS), ival_up_k(exp(a.hi), DIVAL_LIBM_ULPS)};
if (res.lo < 0) res.lo = 0; // exp > 0; widening below the axis helps nobody
break;
case IRR_LN:
if (a.lo <= 0) return false; // interval dips into ln's domain edge
res = (dival){ival_down_k(log(a.lo), DIVAL_LIBM_ULPS), ival_up_k(log(a.hi), DIVAL_LIBM_ULPS)};
break;
case IRR_ATAN:
res = (dival){ival_down_k(atan(a.lo), DIVAL_LIBM_ULPS), ival_up_k(atan(a.hi), DIVAL_LIBM_ULPS)};
break;
case IRR_SQRT:
if (a.lo < 0) a.lo = 0; // x >= 0 was proven at construction; the interval may still dip
if (a.hi < 0) return false;
res = (dival){ival_prev(sqrt(a.lo)), ival_next(sqrt(a.hi))}; // IEEE sqrt: correctly rounded
break;
case IRR_SIN:
case IRR_COS: {
// Lipschitz bound around the midpoint: |sin(v) - sin(m)| <= |v - m|
// (same for cos), so [f(m) - h, f(m) + h] with h an upper bound on
// the half-width is rigorous with no monotonicity analysis, for
// arguments of any size.
double m = a.lo + (a.hi - a.lo) / 2;
if (!isfinite(m)) return false;
double v = n->op == IRR_SIN ? sin(m) : cos(m);
double h = fmax(ival_next(m - a.lo), ival_next(a.hi - m));
res = (dival){ival_down_k(v - h, DIVAL_LIBM_ULPS), ival_up_k(v + h, DIVAL_LIBM_ULPS)};
if (res.lo < -1) res.lo = -1; // range clamp is always sound for sin/cos
if (res.hi > 1) res.hi = 1;
break;
}
default: return false; // unreachable: every IRR_* op is handled above
}
if (!isfinite(res.lo) || !isfinite(res.hi)) return false;
*out = res;
return true;
}
// Bounds for the linear form a + b*F. a and b are parameters (not read
// from r) for the same reason real_fixed's are: digit generation folds an
// exact power of ten into them first.
static bool dival_of_real(const num_real *r, Num_t a, Num_t b, dival *out, int *budget) {
dival ia, ib, f;
if (!dival_of(a, &ia, budget) || !dival_of(b, &ib, budget)) return false;
if (r->fn == FN_PI) {
f = (dival){ival_prev(PI_DOUBLE), ival_next(PI_DOUBLE)};
} else if (r->fn == FN_SQRT) {
double av = number_to_double(r->arg); // correctly rounded
if (!isfinite(av)) return false;
f = (dival){ival_prev(sqrt(ival_prev(av))), ival_next(sqrt(ival_next(av)))};
} else if (r->fn == FN_LN) {
// r->arg > 1 (make_real's canonicalization), so ln(arg) > 0, and no
// domain edge to guard, unlike dival_of's general IRR_LN case.
double av = number_to_double(r->arg);
if (!isfinite(av)) return false;
f = (dival){ival_down_k(log(ival_prev(av)), DIVAL_LIBM_ULPS), ival_up_k(log(ival_next(av)), DIVAL_LIBM_ULPS)};
} else { // FN_EXP
double av = number_to_double(r->arg);
if (!isfinite(av)) return false;
f = (dival){ival_down_k(exp(ival_prev(av)), DIVAL_LIBM_ULPS), ival_up_k(exp(ival_next(av)), DIVAL_LIBM_ULPS)};
if (f.lo < 0) f.lo = 0; // exp > 0; widening below the axis helps nobody
}
dival res = dival_add(ia, dival_mul(ib, f));
if (!isfinite(res.lo) || !isfinite(res.hi)) return false;
*out = res;
return true;
}
// Decide N = round(v) for the (irrational) value bounded by iv, exactly:
// succeeds only when every value in the interval rounds to the same
// integer, which also rules out ever resolving a tie here (a half-integer
// boundary inside the widened interval always straddles). Matches the
// bigint digit loops' round-half-away-from-zero-on-the-magnitude exactly
// on every decidable case, since the cases where the two rules differ
// (exact ties) are never decidable from a widened interval.
static bool dival_round_int(dival iv, int *sign_out, bigint **mag_out) {
double flo = floor(ival_prev(iv.lo + 0.5));
double fhi = floor(ival_next(iv.hi + 0.5));
if (flo != fhi) return false;
if (fabs(flo) >= 9007199254740992.0) return false; // 2^53: past exact-integer doubles
*sign_out = flo < 0 ? -1 : flo > 0 ? 1 : 0;
*mag_out = bi_from_u64((uint64_t)fabs(flo));
return true;
}
#endif // NUMBER_NO_DOUBLE_FILTER
// Floating-point filter (the computational-geometry pattern): when the
// hardware interval already excludes zero, the sign is decided in a few
// nanoseconds and no bigint is ever touched. False means "not decided here",
// which includes the filter being compiled out entirely.
static bool sign_from_interval(Num_t x, int *sign_out) {
#ifndef NUMBER_NO_DOUBLE_FILTER
dival iv;
int budget = DIVAL_VISIT_BUDGET;
if (dival_of(x, &iv, &budget)) {
if (iv.lo > 0) {
NSTAT(interval_hits);
*sign_out = 1;
return true;
}
if (iv.hi < 0) {
NSTAT(interval_hits);
*sign_out = -1;
return true;
}
NSTAT(interval_fallbacks); // straddles zero at double width: refine for real
}
#else
(void)x, (void)sign_out;
#endif
return false;
}
// The expensive half of refine_sign: widen the fixed-point approximation
// until it excludes zero, giving up past max_prec bits. A value that IS zero
// can never be excluded, so it always runs the loop to the cap before
// failing. Callers with a cheaper way to recognize zero should try it
// first (see the tier 3 comparison path), and callers that will discard
// the answer past some precision anyway should say so via max_prec rather
// than pay for every doubling up to IRR_MAX_PREC.
//
// The final step is clamped to max_prec instead of overshooting it, so a cap
// that isn't a power of two still gets tried at exactly its own width.
static bool refine_sign_exact(Num_t x, int *sign_out, uint32_t max_prec) {
for (uint32_t w = 32;; w *= 2) {
if (w > max_prec) w = max_prec; // the last step lands exactly on the cap
int s;
bigint *v;
if (!value_fixed(x, w, &s, &v)) return false;
bigint *four = bi_from_u64(4);
bool decided = bi_cmp(v, four) > 0;
bi_free(four);
bi_free(v);
if (decided) {
*sign_out = s;
return true;
}
if (w >= max_prec) return false;
}
}
// Sign of a real/irrational value. Always succeeds for a real (b != 0
// guarantees it's irrational, hence nonzero); an irrational node might not
// resolve; see IRR_MAX_PREC's doc comment.
static bool refine_sign(Num_t x, int *sign_out) {
return sign_from_interval(x, sign_out) || refine_sign_exact(x, sign_out, IRR_MAX_PREC);
}
// floor(log2(|x|)), approximately (+-1); can be negative for |x| < 1. Used
// to budget extra precision in irr_fixed's MUL/DIV cases: how much a
// *sibling* operand needs (MUL) or how much absolute precision an operand
// itself needs (DIV, when its magnitude is small). Cheap and approximate on
// purpose, since callers pad with guard bits. 0 on failure (a safe, if
// possibly too-small, estimate, since the real evaluation call downstream
// will itself fail and propagate correctly).
static int32_t approx_exponent(Num_t x) {
int s;
bigint *v;
if (!value_fixed(x, 64, &s, &v)) return 0;
int32_t bits = (int32_t)bi_bitlen(v); // v approximates |x| * 2^64
bi_free(v);
return bits - 64;
}
// exp(sign * |rmag|/2^p) * 2^p via out, returning its sign; |rmag| < 2^p
// (i.e. |r| < 1). Self-terminating Taylor series: exp(r) = sum r^k/k!.
// term carries its sign natively (mpz), so the alternation for negative r
// is just multiplying by the signed r each step.
static int exp_series(int sign, const bigint *rmag, uint32_t p, bigint **out) {
mpz_t term, sum;
mpz_init_set_ui(term, 1);
mpz_mul_2exp(term, term, p); // term_0 = 1 * 2^p
mpz_init_set(sum, term);
for (uint32_t k = 1; mpz_sgn(term) != 0; k++) {
mpz_mul(term, term, rmag);
if (sign < 0) mpz_neg(term, term);
mpz_tdiv_q_2exp(term, term, p);
mpz_tdiv_q_ui(term, term, k);
mpz_add(sum, sum, term);
}
mpz_clear(term);
return mpz_take(sum, out);
}
// atanh(|zmag|/2^p) * 2^p, |zmag| < 2^p (|z| < 1), z >= 0. Self-terminating
// series like arctan_recip, but multiplicative (z is a general fixed-point
// fraction here, not a reciprocal integer) and without sign alternation:
// atanh(z) = z + z^3/3 + z^5/5 + ..., where every term is nonnegative, so
// (unlike exp_series) plain bi_add accumulation is fine.
static bigint *atanh_fixed(const bigint *zmag, uint32_t p) {
mpz_t z2p, pk, term, sum;
mpz_init(z2p);
mpz_mul(z2p, zmag, zmag);
mpz_tdiv_q_2exp(z2p, z2p, p); // z^2 * 2^p
mpz_init_set(pk, zmag); // p_0 = z^1 * 2^p
mpz_init(term);
mpz_init(sum);
for (uint32_t k = 0; mpz_sgn(pk) != 0; k++) {
mpz_tdiv_q_ui(term, pk, 2 * k + 1);
mpz_add(sum, sum, term);
mpz_mul(pk, pk, z2p);
mpz_tdiv_q_2exp(pk, pk, p); // p_{k+1} = p_k * z^2, still scaled by 2^p
}
mpz_clear(z2p);
mpz_clear(pk);
mpz_clear(term);
bigint *res;
mpz_take(sum, &res); // every term nonnegative
return res;
}
static bigint *g_ln2_appr = NULL; // global memo for the shared constant ln(2)
static uint32_t g_ln2_prec = 0;
// ln(2) * 2^prec, memoized (same pattern as g_pi_appr). ln(2) = 2*atanh(1/3):
// unlike every other x, ln(2) can't go through the general mantissa/exponent
// reduction below (extracting 2's own exponent needs ln(2) already), so it's
// computed directly here.
static bigint *ln2_appr(uint32_t prec) {
if (g_ln2_appr == NULL || g_ln2_prec < prec) {
uint32_t p = prec + 32;
bigint *one = bi_from_u64(1);
bigint *scaled = bi_shl(one, p);
bi_free(one);
bigint *third = bi_new();
mpz_tdiv_q_ui(third, scaled, 3); // (1/3) * 2^p; unsigned long is a full limb here
bi_free(scaled);
bigint *at = atanh_fixed(third, p); // atanh(1/3) * 2^p
bi_free(third);
bigint *fresh = bi_shl(at, 1); // ln(2) = 2*atanh(1/3)
bi_free(at);
bi_free(g_ln2_appr);
g_ln2_appr = fresh;
g_ln2_prec = p;
}
return bi_shr(g_ln2_appr, g_ln2_prec - prec);
}
// sin(sign*|rmag|/2^p) * 2^p via out, returning its sign; |rmag| <= pi*2^p
// (i.e. |r| <= pi, NOT reduced further to a tiny range, so this needs more
// terms than exp_series/atanh_fixed for the same precision, but still
// converges: the factorial denominator beats any fixed |r|). Self-
// terminating, alternating: sin(r) = r - r^3/3! + r^5/5! - ...
static int sin_series(int sign, const bigint *rmag, uint32_t p, bigint **out) {
mpz_t r2p, term, sum;
mpz_init(r2p);
mpz_mul(r2p, rmag, rmag);
mpz_tdiv_q_2exp(r2p, r2p, p); // r^2 * 2^p
mpz_init_set(term, rmag); // term_0 = sign * |r|
if (sign < 0) mpz_neg(term, term);
mpz_init_set(sum, term);
for (uint32_t k = 1; mpz_sgn(term) != 0; k++) {
mpz_mul(term, term, r2p);
mpz_neg(term, term); // alternating series
mpz_tdiv_q_2exp(term, term, p);
mpz_tdiv_q_ui(term, term, 2 * k);
mpz_tdiv_q_ui(term, term, 2 * k + 1);
mpz_add(sum, sum, term);
}
mpz_clear(term);
mpz_clear(r2p);
return mpz_take(sum, out);
}
// cos(|rmag|/2^p) * 2^p via out, returning its sign; |rmag| <= pi*2^p. cos
// is even, so (unlike sin) the sign of r doesn't matter, only |r| does.
// Self-terminating, alternating: cos(r) = 1 - r^2/2! + r^4/4! - ...
static int cos_series(const bigint *rmag, uint32_t p, bigint **out) {
mpz_t r2p, term, sum;
mpz_init(r2p);
mpz_mul(r2p, rmag, rmag);
mpz_tdiv_q_2exp(r2p, r2p, p); // r^2 * 2^p
mpz_init_set_ui(term, 1);
mpz_mul_2exp(term, term, p); // term_0 = 1 * 2^p
mpz_init_set(sum, term);
for (uint32_t k = 1; mpz_sgn(term) != 0; k++) {
mpz_mul(term, term, r2p);
mpz_neg(term, term); // alternating series
mpz_tdiv_q_2exp(term, term, p);
mpz_tdiv_q_ui(term, term, 2 * k - 1);
mpz_tdiv_q_ui(term, term, 2 * k);
mpz_add(sum, sum, term);
}
mpz_clear(term);
mpz_clear(r2p);
return mpz_take(sum, out);
}
// Reduces x into r in (-pi,pi] (sign + magnitude at precision P) via
// x mod 2*pi. Needs pi at boosted precision that scales with x's own
// magnitude, not just the output precision P: a coarse pi lets the quotient
// floor(x/2pi), which grows with x's magnitude, amplify pi's own
// truncation error. This is the classic large-argument range-reduction
// trap (see the caller's guard-bit comment for the actual budget).
static bool reduce_mod_2pi(Num_t x, uint32_t P, int *sign_out, bigint **rmag_out) {
int sx;
bigint *X;
if (!value_fixed(x, P, &sx, &X)) return false;
bigint *pi_p = pi_appr(P);
bigint *two_pi = bi_shl(pi_p, 1);
bi_free(pi_p);
bigint *Q, *R;
bi_divmod(X, two_pi, &Q, &R); // R in [0, two_pi): |x| mod 2pi
bi_free(X);
bi_free(Q);
bigint *pi_val = bi_shr(two_pi, 1);
int rsign = sx;
bigint *rmag;
if (bi_cmp(R, pi_val) > 0) {
// Shorter representative: sx*R === sx*R - sx*2pi = -sx*(2pi-R) (mod 2pi).
rmag = bi_sub(two_pi, R);
bi_free(R);
rsign = -sx;
} else {
rmag = R;
}
bi_free(pi_val);
bi_free(two_pi);
*sign_out = bi_is_zero(rmag) ? 0 : rsign;
*rmag_out = rmag;
return true;
}
// atan(sign*|zmag|/2^p) * 2^p via out, returning its sign; |zmag| < 2^p
// (|z| < 1), already reduced small by the caller. Self-terminating,
// alternating, the same shape as atanh_fixed (undivided running power pk,
// term computed fresh each step), just with sign flipping each term:
// atan(z) = z - z^3/3 + z^5/5 - ...
static int atan_series(int sign, const bigint *zmag, uint32_t p, bigint **out) {
mpz_t z2p, pk, term, sum;
mpz_init(z2p);
mpz_mul(z2p, zmag, zmag);
mpz_tdiv_q_2exp(z2p, z2p, p); // z^2 * 2^p
mpz_init_set(pk, zmag); // p_0 = sign * z^1 * 2^p (undivided power)
if (sign < 0) mpz_neg(pk, pk);
mpz_init(term);
mpz_init(sum);
for (uint32_t k = 0; mpz_sgn(pk) != 0; k++) {
mpz_tdiv_q_ui(term, pk, 2 * k + 1);
if (k % 2 == 0) mpz_add(sum, sum, term);
else mpz_sub(sum, sum, term);
mpz_mul(pk, pk, z2p);
mpz_tdiv_q_2exp(pk, pk, p); // p_{k+1} = p_k * z^2
}
mpz_clear(z2p);
mpz_clear(pk);
mpz_clear(term);
return mpz_take(sum, out);
}
// Builds a general IRRATIONAL node combining x and y (owned; for unary ops
// y is small_zero(), an immediate, so owning it is free). For IRR_DIV,
// first establishes y is nonzero via refine_sign, where giving up (see
// IRR_MAX_PREC) makes the whole division NUMBER_ERROR rather than building
// a node that could silently fail to converge later.
static Num_t make_irr(int op, Num_t x, Num_t y) {
if (number_is_error(x) || number_is_error(y)) {
Num_t e = number_is_error(x) ? x : y;
return e;
}
if (op == IRR_DIV) {
int s;
if (!refine_sign(y, &s)) {
return err(ERR_UNDECIDABLE);
}
}
num_irr *n = xmalloc(sizeof(num_irr));
n->head.kind = KIND_IRRATIONAL;
// op is always one of the 9 IRR_* constants above, a closed, entirely
// internal enum, unlike appr_prec's caller-facing unbounded range, so
// masking here is a legitimate narrowing, not the silent-truncation
// hazard that ruled out masking for appr_prec earlier.
n->op = (uint32_t)op & 0xFu;
n->appr_sign = 0;
n->x = x;
n->y = y;
mpz_init(n->appr); // lazy: allocates nothing until a value is stored
n->appr_prec = 0; // 0 = no memo yet
return (Num_t){(uint64_t)(uintptr_t)n};
}
// exp(x) * 2^w with |error| <= 4, sign always +1 (exp > 0). Range-reduces by
// repeated halving until |r| < 1 (r = x/2^k), runs the self-terminating
// series on r, then squares k times to undo the reduction: exp(x) =
// exp(r)^(2^k). Keeps ONE constant working precision (w + guard) through
// every squaring step rather than truncating between them. Each squaring
// costs about 1 bit of relative precision, so truncating early would
// compound that loss; truncate once, at the very end, down to w. Shared by
// irr_fixed's IRR_EXP case and factor_appr's FN_EXP case.
static bool exp_fixed_core(Num_t x, uint32_t w, int *sign_out, bigint **mag_out) {
// A single low-precision (64-bit) probe of |x| serves two purposes: k
// (how many squarings undo the range reduction) below, and, see `extra`,
// how many guard bits those squarings cost.
int s0;
bigint *v0;
if (!value_fixed(x, 64, &s0, &v0)) return false;
int32_t ex = (int32_t)bi_bitlen(v0) - 64; // ~floor(log2(|x|)); matches approx_exponent
uint32_t k = ex > 0 ? (uint32_t)ex + 1 : 0;
// Each of the k squarings below roughly DOUBLES exp(r)'s *relative*
// error, so after k of them the ABSOLUTE error at exp(x)'s own scale is
// roughly (exp(r)'s absolute error) * exp(x), not amplified by k
// alone, but by exp(x) itself, which for x > 0 is exponentially larger
// than the O(1) scale exp(r) starts at. The |error|<=4 contract is
// stated at the OUTPUT's scale (2^w), so exp(x)'s own bit width,
// roughly x*log2(e) and nowhere close to k (~log2(x)), has to be
// budgeted as guard bits up front: precision lost partway through a
// chain of squarings can't be recovered by truncating at the very end.
// Concretely, without this, number_to_string(number_exp(number_from_int(120)),
// 0, ...) is wrong starting around its 49th digit (checked against an
// independent 100-digit reference), and number_to_double of the same
// value is off in its last few bits. Only matters for x > 0: for x < 0,
// exp(x) < 1 shrinks through the squarings rather than growing, so the
// final absolute error is smaller than exp(r)'s, not larger.
uint32_t extra = 0;
if (ex > 0) {
// LOG2E_Q31 = ceil(log2(e) * 2^31): a uint32_t fixed-point constant
// (log2(e) > 1, so a Q32 one would overflow uint32_t). v0 is
// |x|*2^64 (error <= 4), so v0*LOG2E_Q31 is |x|*log2(e) scaled by
// 2^95, exp(x)'s bit width at that scale.
const uint32_t LOG2E_Q31 = 3098164010u; // ceil(log2(e) * 2^31)
bigint *scaled = bi_mul_u32(v0, LOG2E_Q31);
bigint *bits = bi_shr(scaled, 95);
bi_free(scaled);
uint64_t needed;
if (!bi_fits_u64(bits, &needed) || needed > (1u << 24)) {
// exp(x) would need more than 16M bits (2MB) just to state its
// magnitude, infeasible to compute exactly regardless of this
// budget, so give up rather than attempt an allocation this size.
bi_free(bits);
bi_free(v0);
return false;
}
bi_free(bits);
// +64: slack for v0's own +-4 error, LOG2E_Q31's rounding, the
// floor (not ceil) shift above, and the k squarings' own
// truncation (each bi_shr(sq, P) below loses <1 count).
extra = (uint32_t)needed + 64;
}
bi_free(v0);
uint32_t P = w + 32 + k + extra;
int sx;
bigint *X;
if (!value_fixed(x, P, &sx, &X)) return false;
bigint *R = bi_shr(X, k); // |r| * 2^P, r = x/2^k, |r| < 1
bi_free(X);
bigint *E;
exp_series(sx, R, P, &E);
bi_free(R);
for (uint32_t i = 0; i < k; i++) {
bigint *sq = bi_mul(E, E);
bi_free(E);
E = bi_shr(sq, P); // constant precision P through the chain
bi_free(sq);
}
*mag_out = bi_shr(E, P - w);
bi_free(E);
*sign_out = 1;
return true;
}
// ln(x) * 2^w with |error| <= 4. ln(x) = ln(m) + e*ln(2), m = x/2^e in
// [1,2), ln(m) = 2*atanh(z) with z = (m-1)/(m+1) (|z| <= 1/3, fast
// convergence). e is x's own bit-exponent, read off the fixed-point X this
// function fetches anyway (exact, unlike approx_exponent's separate
// precision-64 probe; see below). False if x <= 0 (shouldn't happen:
// every caller has already checked positivity, number_ln directly, or
// make_real's arg > 1 canonicalization for factor_appr's FN_EXP case) or
// if x's magnitude can't be resolved within IRR_MAX_PREC bits (see that
// macro's doc comment). Shared by irr_fixed's IRR_LN case and factor_appr's
// FN_LN case.
static bool ln_fixed_core(Num_t x, uint32_t w, int *sign_out, bigint **mag_out) {
uint32_t P = w + 32;
int sx;
bigint *X;
if (!value_fixed(x, P, &sx, &X)) return false;
if (sx < 0) { // ln(x<=0): shouldn't reach here
bi_free(X);
return false;
}
// X == 0 here does NOT mean x <= 0, since value_fixed already ruled that
// out via sx. It means P wasn't enough to resolve x's magnitude at all: a
// sufficiently tiny x (e.g. ln(sqrt(exp(-750))), whose operand is
// ~2^-542) needs P past a thousand bits before its leading bit is even
// visible. approx_exponent can't help size that P up front, since it
// probes at a fixed precision of 64 bits, so for an x this tiny it always
// sees 0 too and returns its own capped floor. Treating a 0 magnitude as
// a hard failure here (as this function used to) doesn't just mishandle
// this case: it makes every caller (irr_to_string_digits, refine_sign,
// refine_to_double) that retries value_fixed at doubled precision give up
// on the FIRST attempt, because irr_fixed's IRR_LN case just forwards
// this function's false straight out, indistinguishable from a
// genuinely-undecidable value. So widen P here and retry directly,
// instead of reporting a failure a wider P could have resolved.
while (bi_is_zero(X)) {
bi_free(X);
if (P > IRR_MAX_PREC) return false; // gave up: see IRR_MAX_PREC's doc comment
P *= 2;
if (!value_fixed(x, P, &sx, &X)) return false;
if (sx < 0) { // shouldn't happen (x's sign can't flip as P grows)
bi_free(X);
return false;
}
}
int32_t e = (int32_t)bi_bitlen(X) - (int32_t)P; // exact: X's own leading bit
bigint *M = e >= 0 ? bi_shr(X, (uint32_t)e) : bi_shl(X, (uint32_t)-e);
bi_free(X);
int32_t blen = (int32_t)bi_bitlen(M);
while (blen > (int32_t)P + 1) {
bigint *t = bi_shr(M, 1);
bi_free(M);
M = t;
e++;
blen--;
}
while (blen < (int32_t)P + 1) {
bigint *t = bi_shl(M, 1);
bi_free(M);
M = t;
e--;
blen++;
}
bigint *one = bi_from_u64(1);
bigint *one_p = bi_shl(one, P); // 2^P, i.e. "1" at scale P
bi_free(one);
bigint *num = bi_sub(M, one_p); // m-1, scaled: M >= one_p since bitlen(M) == P+1
bigint *den = bi_add(M, one_p); // m+1, scaled
bi_free(M);
bi_free(one_p);
bigint *num_scaled = bi_shl(num, P);
bi_free(num);
bigint *z, *zrem;
bi_divmod(num_scaled, den, &z, &zrem);
bi_free(num_scaled);
bi_free(den);
bi_free(zrem);
bigint *at = atanh_fixed(z, P);
bi_free(z);
bigint *ln_m = bi_shl(at, 1); // ln(m) = 2*atanh(z); ln(m) >= 0 since m >= 1
bi_free(at);
bigint *total;
int sign;
if (e == 0) {
total = ln_m;
sign = bi_is_zero(total) ? 0 : 1;
} else {
bigint *ln2 = ln2_appr(P);
bigint *e_ln2 = bi_mul_u32(ln2, e < 0 ? (uint32_t)-e : (uint32_t)e);
bi_free(ln2);
sign = signed_add(1, ln_m, e < 0 ? -1 : 1, e_ln2, &total);
}
*mag_out = bi_shr(total, P - w);
bi_free(total);
*sign_out = sign;
return true;
}
// Evaluates a DAG node to w bits, memoizing the best-known approximation
// like factor_appr does for pi/sqrt. Each op requests enough extra
// precision from its operand(s) to guarantee |error| <= 4 at w, the same
// contract value_fixed makes for every other kind.
static bool irr_fixed(num_irr *n, uint32_t w, int *sign_out, bigint **mag_out) {
if (n->appr_prec != 0 && n->appr_prec >= w) {
NSTAT(irr_memo_hits);
*sign_out = n->appr_sign;
*mag_out = bi_shr(n->appr, n->appr_prec - w);
return true;
}
NSTAT(irr_recompute[n->op]);
uint32_t p = w + 64; // overshoot so nearby requests hit the memo, as factor_appr does
int sign;
bigint *mag;
switch (n->op) {
case IRR_ADD:
case IRR_SUB: {
// Both operands at p+2 bits (each with its own <=4 error), combine,
// shift back down by 2: combined error <=8, /4 plus <1 truncation
// keeps the total comfortably <=4.
int sx, sy;
bigint *vx, *vy;
if (!value_fixed(n->x, p + 2, &sx, &vx)) return false;
if (!value_fixed(n->y, p + 2, &sy, &vy)) {
bi_free(vx);
return false;
}
if (n->op == IRR_SUB) sy = -sy;
bigint *sum;
sign = signed_add(sx, vx, sy, vy, &sum);
mag = bi_shr(sum, 2);
bi_free(sum);
break;
}
case IRR_MUL: {
// Each operand needs extra precision to cover the OTHER's
// magnitude, same trick real_fixed already uses for b*F.
int32_t ex = approx_exponent(n->x), ey = approx_exponent(n->y);
uint32_t px = p + (uint32_t)(ey > 0 ? ey : 0) + 16;
uint32_t py = p + (uint32_t)(ex > 0 ? ex : 0) + 16;
int sx, sy;
bigint *vx, *vy;
if (!value_fixed(n->x, px, &sx, &vx)) return false;
if (!value_fixed(n->y, py, &sy, &vy)) {
bi_free(vx);
return false;
}
bigint *prod = bi_mul(vx, vy);
bi_free(vx);
bi_free(vy);
mag = bi_shr(prod, px + py - p);
bi_free(prod);
sign = sx * sy;
break;
}
case IRR_DIV: {
// y was already proven nonzero when this node was built (make_irr).
// py must scale with p (the target precision), not just with y's own
// magnitude: y's RELATIVE error is ~4/(|y|*2^py), and that needs to
// shrink below 2^-p as p grows, or the quotient's error stops
// improving past a fixed absolute bit count no matter how much
// precision is requested (silently wrong low digits at any p beyond
// that). py = p + 32 (+ extra if |y| < 1, so its magnitude is still
// resolved) keeps that relative error tiny regardless of p; x then
// needs px = p + py bits so the quotient lands at scale p.
int32_t ey = approx_exponent(n->y);
uint32_t py = p + 32 + (uint32_t)(ey < 0 ? -ey : 0);
uint32_t px = p + py;
int sx, sy;
bigint *vx, *vy;
if (!value_fixed(n->x, px, &sx, &vx)) return false;
if (!value_fixed(n->y, py, &sy, &vy)) {
bi_free(vx);
return false;
}
if (bi_is_zero(vy)) { // shouldn't happen: y was proven nonzero at construction
bi_free(vx);
bi_free(vy);
return false;
}
bigint *q, *rem;
bi_divmod(vx, vy, &q, &rem);
bi_free(vx);
bi_free(vy);
bi_free(rem);
mag = q;
sign = sx * sy;
break;
}
case IRR_EXP:
if (!exp_fixed_core(n->x, p, &sign, &mag)) return false;
break;
case IRR_LN:
if (!ln_fixed_core(n->x, p, &sign, &mag)) return false;
break;
case IRR_SIN:
case IRR_COS: {
// Range reduction's error scales with x's own magnitude (see
// reduce_mod_2pi), not just with p: 2 extra guard bits per bit of
// x's magnitude, matching the "amplified by the quotient" analysis.
int32_t ex = approx_exponent(n->x);
uint32_t P = p + 2 * (uint32_t)(ex > 0 ? ex : 0) + 32;
int rsign;
bigint *rmag;
if (!reduce_mod_2pi(n->x, P, &rsign, &rmag)) return false;
bigint *raw;
if (n->op == IRR_SIN) sign = sin_series(rsign, rmag, P, &raw);
else sign = cos_series(rmag, P, &raw);
bi_free(rmag);
mag = bi_shr(raw, P - p);
bi_free(raw);
break;
}
case IRR_ATAN: {
// |x| >= 1: reduce via atan(x) = sign(x)*pi/2 - atan(1/x) first, so
// the halving phase below always starts from |z| <= 1. Unlike
// SIN/COS's range reduction, this doesn't depend on x's magnitude
// at all (a single reciprocal, not a magnitude-scaled quotient).
int32_t ex = approx_exponent(n->x);
bool recip = ex >= 0;
uint32_t H = 4; // fixed halving steps; enough for any |z| <= 1 to start
uint32_t P = p + 32 + H;
int sx;
bigint *Z;
if (recip) {
int xsign;
bigint *X;
if (!value_fixed(n->x, P + 32, &xsign, &X)) return false;
bigint *one = bi_from_u64(1);
bigint *num = bi_shl(one, 2 * P + 32); // so num/X lands at scale 2^P
bi_free(one);
bigint *rem;
bi_divmod(num, X, &Z, &rem);
bi_free(num);
bi_free(X);
bi_free(rem);
sx = xsign;
} else {
if (!value_fixed(n->x, P, &sx, &Z)) return false;
}
// Halving: z' = z/(1+sqrt(1+z^2)), applied to the magnitude (the map
// is odd, so this and tracking sign separately is equivalent to
// applying it to signed z directly). Same "constant working
// precision through the chain, truncate once at the end" rule as
// EXP's squaring.
for (uint32_t i = 0; i < H; i++) {
bigint *z2 = bi_mul(Z, Z);
bigint *one_a = bi_from_u64(1);
bigint *one_2p = bi_shl(one_a, 2 * P);
bi_free(one_a);
bigint *sum = bi_add(z2, one_2p);
bi_free(z2);
bi_free(one_2p);
bigint *sq = bi_isqrt(sum); // sqrt(1+z^2) * 2^P
bi_free(sum);
bigint *one_b = bi_from_u64(1);
bigint *onep = bi_shl(one_b, P);
bi_free(one_b);
bigint *den = bi_add(onep, sq); // (1+sqrt(1+z^2)) * 2^P
bi_free(sq);
bi_free(onep);
bigint *num = bi_shl(Z, P);
bi_free(Z);
bigint *rem;
bi_divmod(num, den, &Z, &rem); // z/(1+sqrt(1+z^2)) * 2^P
bi_free(num);
bi_free(den);
bi_free(rem);
}
bigint *series;
int rsign = atan_series(sx, Z, P, &series);
bi_free(Z);
bigint *raw = bi_shl(series, H); // undo the H halvings: atan(z) = 2^H * atan(z_H)
bi_free(series);
bigint *total;
if (recip) {
bigint *pip = pi_appr(P);
bigint *half_pi = bi_shr(pip, 1); // pi/2 * 2^P
bi_free(pip);
sign = signed_add(sx, half_pi, -rsign, raw, &total);
} else {
total = raw;
sign = rsign;
}
mag = bi_shr(total, P - p);
bi_free(total);
break;
}
case IRR_SQRT: {
// sqrt(x) via bi_isqrt (exact floor integer sqrt): fetch x at scale
// 2*P so bi_isqrt's output lands directly at scale P (bi_isqrt(v *
// 2^(2P)) == sqrt(v) * 2^P, the same identity IRR_ATAN's halving
// step already relies on). bi_isqrt's own rounding error is <1;
// the dominant error comes from x's own approximation error
// propagating through sqrt's derivative 1/(2*sqrt(x)), which grows
// as x shrinks toward 0, so pad P by |exponent(x)| (the same
// magnitude-scaled guard-bit idea EXP/LN/ATAN use above) so the
// down-scaled result still meets the <=4 error contract.
int32_t ex = approx_exponent(n->x);
uint32_t guard = ex < 0 ? (uint32_t)(-ex) : 0;
uint32_t P = p + 32 + guard;
int sx;
bigint *X;
if (!value_fixed(n->x, 2 * P, &sx, &X)) return false;
if (sx < 0) { // domain error; shouldn't reach here (checked at construction)
bi_free(X);
return false;
}
bigint *root = bi_isqrt(X);
bi_free(X);
mag = bi_shr(root, P - p);
bi_free(root);
sign = 1; // sqrt(x) > 0 always here (x > 0 was proven at construction)
break;
}
default: abort(); // unreachable: every IRR_* op is handled above
}
mpz_swap(n->appr, mag);
bi_free(mag); // now holds the previous memo
n->appr_prec = p;
n->appr_sign = sign < 0 ? -1 : (sign > 0 ? 1 : 0);
*sign_out = sign;
*mag_out = bi_shr(n->appr, p - w);
return true;
}
// ---------------------------------------------------------------------------
// Conversion to double
// Correctly rounded sign * n/d, d > 0. n may carry a sign of its own (an
// mpq numref): only |n| is used, since bit length, limb reads, and
// truncating division are all magnitude-correct, and the result's sign comes
// from `sign` alone.
static double rat_to_double(int sign, const bigint *n, const bigint *d) {
int64_t nb = bi_bitlen(n), db = bi_bitlen(d);
// Scale so the integer quotient has 54 or 55 bits.
int64_t shift = db + 54 - nb;
bigint *ns, *ds;
if (shift >= 0) {
ns = bi_shl(n, (uint32_t)shift);
ds = bi_copy(d);
} else {
ns = bi_copy(n);
ds = bi_shl(d, (uint32_t)-shift);
}
bigint *q, *r;
bi_divmod(ns, ds, &q, &r);
uint64_t qv = 1;
bi_fits_u64(q, &qv); // q is 54-55 bits by construction, so this always fits
bool sticky = !bi_is_zero(r);
bi_free(ns);
bi_free(ds);
bi_free(q);
bi_free(r);
// value = qv * 2^e2, with qv having 54 or 55 bits
int64_t e2 = -shift;
int qbits = 64 - __builtin_clzll(qv);
int64_t exp_of_msb = qbits - 1 + e2; // floor(log2(value))
if (exp_of_msb > 1024) return sign < 0 ? -(double)INFINITY : (double)INFINITY;
if (exp_of_msb < -1075) return sign < 0 ? -0.0 : 0.0; // below half of min subnormal
int64_t prec = 53;
if (exp_of_msb < -1022) // subnormal range: fewer significand bits
prec = 1075 + exp_of_msb;
int drop = (int)(qbits - prec); // >= 1, since qbits >= 54 and prec <= 53
uint64_t keep = qv >> drop;
uint64_t guard = (qv >> (drop - 1)) & 1;
bool lower = (qv & (((uint64_t)1 << (drop - 1)) - 1)) != 0 || sticky;
if (guard && (lower || (keep & 1))) keep++; // may carry to prec+1 bits: a power of two, still exact
double result = ldexp((double)keep, (int)(e2 + drop));
return sign < 0 ? -result : result;
}
// Correctly rounded double for a real/irrational value: round both ends of
// the +-4 interval as exact rationals V/2^w; when they round identically
// the answer is certain. A real always terminates (never exactly a double
// or a tie); an irrational node might give up (NAN) past IRR_MAX_PREC bits.
// Deliberately NOT served by the double-interval fast path: deciding a
// correctly rounded double means telling values less than half an ulp
// apart, and a same-precision interval (whose two ends are themselves
// doubles, so it is at least a full ulp wide after any outward widening)
// can never fit strictly inside one rounding interval. Serving this at
// hardware speed would take double-double (compensated) interval
// arithmetic, ~106 effective bits, which is future work; measured, the
// always-failing attempt only made this path slower.
static double refine_to_double(Num_t x) {
for (uint32_t w = 64; w <= IRR_MAX_PREC; w *= 2) {
int s;
bigint *v;
if (!value_fixed(x, w, &s, &v)) return (double)NAN;
bigint *four = bi_from_u64(4);
// |value| <= 8*2^-w (v's own +-4 error band). Once w is past 1078
// bits, that bound is below 2^-1075, half the smallest positive
// subnormal double and the round-to-nearest-even threshold below
// which every double rounds to (signed) zero, so the correctly
// rounded result is certain without ever computing rat_to_double on a
// value this close to its own error band. Without this, a value
// that's genuinely nonzero but far smaller than any double (e.g.
// exp(-20000), or pi times a googol-scale negative power of ten)
// never gets a decisive d1==d2 verdict below and the loop exhausts
// IRR_MAX_PREC still undecided, wrongly reporting NAN ("the error
// value") for an ordinary, if tiny, exact number. real_to_string and
// irr_to_string_digits (this function's siblings for the decimal
// path) already special-case exactly this; this mirrors them.
if (bi_cmp(v, four) <= 0 && w > 1078) {
bi_free(four);
bi_free(v);
return s < 0 ? -0.0 : 0.0;
}
if (bi_cmp(v, four) > 0) {
bigint *lo = bi_sub(v, four);
bigint *hi = bi_add(v, four);
bigint *one = bi_from_u64(1);
bigint *den = bi_shl(one, w);
bi_free(one);
double d1 = rat_to_double(s, lo, den);
double d2 = rat_to_double(s, hi, den);
bi_free(lo);
bi_free(hi);
bi_free(den);
if (d1 == d2) {
bi_free(four);
bi_free(v);
return d1;
}
}
bi_free(four);
bi_free(v);
}
return (double)NAN;
}
double number_to_double(Num_t x) {
if (number_is_error(x)) return (double)NAN;
if (number_tag(x) == TAG_SMALL) {
// Both operands are exact in double, and IEEE division is correctly
// rounded, so a single hardware divide gives the right answer.
return (double)small_num(x) / (double)small_den(x);
}
if (is_real_kind(x) || is_irrational_kind(x)) return refine_to_double(x);
mpq_srcptr q = number_heap(x)->q;
return rat_to_double(mpq_sgn(q), mpq_numref(q), mpq_denref(q));
}
int64_t number_to_int64(Num_t x, bool *ok) {
bool valid = false;
int64_t result = 0;
if (number_tag(x) == TAG_SMALL && small_den(x) == 1) {
valid = true;
result = small_num(x);
} else if (number_tag(x) == TAG_POINTER && x.bits != 0 && number_head(x)->kind == KIND_BIGRAT) {
num_bigrat *p = number_heap(x);
int sign = mpq_sgn(p->q);
uint64_t mag;
if (bi_is_one(mpq_denref(p->q)) && bi_fits_u64(mpq_numref(p->q), &mag)) {
if (sign > 0 && mag <= (uint64_t)INT64_MAX) {
valid = true;
result = (int64_t)mag;
} else if (sign < 0 && mag <= (uint64_t)INT64_MAX + 1) {
valid = true;
// -mag, avoiding signed overflow at exactly INT64_MIN.
result = mag == (uint64_t)INT64_MAX + 1 ? INT64_MIN : -(int64_t)mag;
}
}
}
if (ok) *ok = valid;
return result;
}
// ---------------------------------------------------------------------------
// Conversion to string
// Formats N (a decimal magnitude representing round(value * 10^max_frac_digits))
// with a decimal point max_frac_digits from the right. Consumes N.
static char *format_scaled_decimal(bigint *N, int sign, uint32_t max_frac_digits) {
char *dec = bi_to_decimal(N);
bool neg = sign < 0 && !bi_is_zero(N);
bi_free(N);
size_t len = strlen(dec);
uint32_t fd = max_frac_digits;
size_t ilen = len > fd ? len - fd : 1; // integer digits ("0" when none)
char *out = xmalloc(neg + ilen + (fd ? 1 + fd : 0) + 1);
size_t pos = 0;
if (neg) out[pos++] = '-';
if (len > fd) {
memcpy(out + pos, dec, len - fd);
pos += len - fd;
} else {
out[pos++] = '0';
}
if (fd) {
out[pos++] = '.';
size_t have = len > fd ? fd : len;
memset(out + pos, '0', fd - have); // leading fractional zeros
memcpy(out + pos + (fd - have), dec + (len - have), have);
pos += fd;
}
out[pos] = '\0';
return out;
}
// Decimal digits of a real: N = round(value * 10^d), correctly rounded by
// interval refinement (irrationals never land exactly on a rounding
// boundary). The power of ten is folded exactly into the rational
// coefficients so only one irrational factor approximation is needed.
static char *real_to_string(num_real *r, uint32_t max_frac_digits) {
Num_t scale = canon_make(1, bi_pow10(max_frac_digits), bi_from_u64(1));
Num_t a = number_mul(r->a, scale);
Num_t b = number_mul(r->b, scale);
int sign = 0;
bigint *N = NULL;
#ifndef NUMBER_NO_DOUBLE_FILTER
// 10^d is already folded exactly into a and b, so the hardware interval
// of a + b*F directly bounds value*10^d; if it rounds decisively, the
// digits are done with no bigint evaluation of F at all.
{
dival iv;
int budget = DIVAL_VISIT_BUDGET;
if (dival_of_real(r, a, b, &iv, &budget) && dival_round_int(iv, &sign, &N)) {
NSTAT(interval_hits);
return format_scaled_decimal(N, sign, max_frac_digits);
}
NSTAT(interval_fallbacks);
}
#endif
for (uint32_t w = 64; N == NULL; w *= 2) {
int s;
bigint *v;
real_fixed(r, a, b, w, &s, &v);
bigint *four = bi_from_u64(4);
if (bi_cmp(v, four) <= 0) {
// |value*10^d| <= 8*2^-w < 1/2: rounds to zero, certainly.
N = bi_new();
sign = 0;
} else {
bigint *one = bi_from_u64(1);
bigint *half = bi_shl(one, w - 1);
bi_free(one);
bigint *lo = bi_sub(v, four);
bigint *hi = bi_add(v, four);
bigint *lo_h = bi_add(lo, half);
bigint *hi_h = bi_add(hi, half);
bigint *n1 = bi_shr(lo_h, w);
bigint *n2 = bi_shr(hi_h, w);
if (bi_cmp(n1, n2) == 0) {
N = n1;
sign = s;
bi_free(n2);
} else {
bi_free(n1);
bi_free(n2);
}
bi_free(half);
bi_free(lo);
bi_free(hi);
bi_free(lo_h);
bi_free(hi_h);
}
bi_free(four);
bi_free(v);
}
return format_scaled_decimal(N, sign, max_frac_digits);
}
// Decimal digits of a general irrational value. Unlike real_to_string, there
// is no exact a/b to pre-scale by 10^d (no closed form), so the error bound
// itself is scaled by 10^d instead of the value: N = round(value * 10^d),
// same half-up bracket-agreement loop, just with a wider (still exact)
// error margin. Gives up (returns NULL) past IRR_MAX_PREC bits.
static bigint *irr_to_string_digits(Num_t x, uint32_t max_frac_digits, int *sign_out) {
#ifndef NUMBER_NO_DOUBLE_FILTER
// Interval pre-pass, like real_to_string's. There are no exact rational
// coefficients to fold 10^d into here, so scale the interval by 10^d in
// double arithmetic instead, which is exact for d <= 22 (10^22 = 2^22 *
// 5^22 is the largest power of ten a double represents exactly, and every
// smaller power along the way is exact too), and a request that deep
// needs more precision than a double interval resolves anyway.
if (max_frac_digits <= 22) {
dival iv;
int budget = DIVAL_VISIT_BUDGET;
if (dival_of(x, &iv, &budget)) {
double s10 = 1.0;
for (uint32_t i = 0; i < max_frac_digits; i++)
s10 *= 10.0;
dival scaled = {ival_prev(iv.lo * s10), ival_next(iv.hi * s10)};
bigint *N;
if (dival_round_int(scaled, sign_out, &N)) {
NSTAT(interval_hits);
return N;
}
NSTAT(interval_fallbacks);
}
}
#endif
bigint *scale = bi_pow10(max_frac_digits);
bigint *four = bi_from_u64(4); // value_fixed's error bound, scaled by 10^d below
bigint *err = bi_mul(four, scale);
bi_free(four);
bigint *N = NULL;
for (uint32_t w = 64; N == NULL && w <= IRR_MAX_PREC; w *= 2) {
int s;
bigint *v;
if (!value_fixed(x, w, &s, &v)) break;
bigint *sv = bi_mul(v, scale); // ~ |value| * 2^w * 10^d, error <= err
bi_free(v);
if (bi_cmp(sv, err) <= 0) {
// |value*10^d| <= 2*err*2^-w, tiny for any w in this loop: rounds to zero.
bi_free(sv);
N = bi_new();
*sign_out = 0;
} else {
bigint *one = bi_from_u64(1);
bigint *half = bi_shl(one, w - 1);
bi_free(one);
bigint *lo = bi_sub(sv, err);
bigint *hi = bi_add(sv, err);
bi_free(sv);
bigint *lo_h = bi_add(lo, half);
bigint *hi_h = bi_add(hi, half);
bigint *n1 = bi_shr(lo_h, w);
bigint *n2 = bi_shr(hi_h, w);
if (bi_cmp(n1, n2) == 0) {
N = n1;
*sign_out = s;
bi_free(n2);
} else {
bi_free(n1);
bi_free(n2);
}
bi_free(half);
bi_free(lo);
bi_free(hi);
bi_free(lo_h);
bi_free(hi_h);
}
}
bi_free(scale);
bi_free(err);
return N;
}
static char *irr_to_string(Num_t x, uint32_t max_frac_digits) {
int sign = 0;
bigint *N = irr_to_string_digits(x, max_frac_digits, &sign);
if (N == NULL) return xstrdup("(error)"); // gave up within the precision cap
return format_scaled_decimal(N, sign, max_frac_digits);
}
// TAG_SMALL fast path for number_to_string: den <= SMALL_DEN_MAX (2^30 - 1)
// keeps every intermediate here comfortably inside uint64_t with room to
// spare (r < den, so r*10^9 < 2^30*10^9 ~= 1.07e18, vs. UINT64_MAX ~=
// 1.84e19). Unlike number_from_string_fast, there's no overflow case to
// bail out of; this always applies when x is TAG_SMALL. Mirrors the
// general bigint-based algorithm below digit for digit (same 9-digit
// batching, same round-half-even, same carry cascade), just entirely off
// the heap for the digit generation itself.
static char *number_to_string_small(Num_t x, uint32_t max_frac_digits, bool *is_exact) {
int64_t num = small_num(x);
uint64_t den = small_den(x);
bool neg = num < 0;
uint64_t n = neg ? (uint64_t)(-num) : (uint64_t)num;
uint64_t Q = n / den, r = n % den;
char int_digits[20]; // a uint64_t magnitude is at most 20 decimal digits
size_t ilen = 0;
if (Q == 0) {
int_digits[ilen++] = '0';
} else {
char tmp[20];
size_t tn = 0;
while (Q) {
tmp[tn++] = (char)('0' + Q % 10);
Q /= 10;
}
while (tn)
int_digits[ilen++] = tmp[--tn];
}
static const uint64_t POW10_U64[10] = {
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000,
};
char *frac = xmalloc(max_frac_digits ? max_frac_digits : 1);
uint32_t fn = 0;
while (fn < max_frac_digits && r != 0) {
uint32_t batch = max_frac_digits - fn < 9 ? max_frac_digits - fn : 9;
uint64_t scaled = r * POW10_U64[batch];
uint64_t q = scaled / den;
r = scaled % den;
for (uint32_t i = 0; i < batch; i++)
frac[fn + i] = (char)('0' + (q / POW10_U64[batch - 1 - i]) % 10);
fn += batch;
if (r == 0) // see number_to_string's identical trim: terminated mid-batch
while (frac[fn - 1] == '0')
fn--;
}
bool exact = r == 0;
// Round half-even on the last emitted digit.
bool round_up = false;
if (!exact) {
if (r * 2 > den) {
round_up = true;
} else if (r * 2 == den) {
char last = fn ? frac[fn - 1] : int_digits[ilen - 1];
round_up = ((last - '0') & 1) != 0;
}
}
// One contiguous digit buffer with a spare leading '0' to absorb carries.
char *digits = xmalloc(1 + ilen + fn);
digits[0] = '0';
memcpy(digits + 1, int_digits, ilen);
memcpy(digits + 1 + ilen, frac, fn);
if (round_up) {
size_t i = ilen + fn;
while (digits[i] == '9')
digits[i--] = '0';
digits[i]++;
}
bool carried = digits[0] != '0';
// A negative value whose printed digits all round down to zero (e.g.
// -1/1000000 at 2 fractional digits) must print "0", not "-0": the
// rounding, not the sign, decides what's shown, and a value that reads
// as zero shouldn't display a sign it doesn't have. format_scaled_decimal
// (the irrational path) already guards this the same way; mirror it here.
if (neg && !carried) {
neg = false;
for (size_t i = 1; i <= ilen + fn; i++) {
if (digits[i] != '0') {
neg = true;
break;
}
}
}
char *out = xmalloc(neg + carried + ilen + (fn ? 1 + fn : 0) + 1);
size_t pos = 0;
if (neg) out[pos++] = '-';
if (carried) out[pos++] = digits[0];
memcpy(out + pos, digits + 1, ilen);
pos += ilen;
if (fn) {
out[pos++] = '.';
memcpy(out + pos, digits + 1 + ilen, fn);
pos += fn;
}
out[pos] = '\0';
if (is_exact) *is_exact = exact;
return out;
}
char *number_to_string(Num_t x, uint32_t max_frac_digits, bool *is_exact) {
if (is_exact) *is_exact = true;
if (number_is_error(x)) return xstrdup("(error)");
if (is_real_kind(x)) {
if (is_exact) *is_exact = false; // reals are irrational: never exact
return real_to_string(as_real(x), max_frac_digits);
}
if (is_irrational_kind(x)) {
if (is_exact) *is_exact = false;
return irr_to_string(x, max_frac_digits);
}
if (number_tag(x) == TAG_SMALL) return number_to_string_small(x, max_frac_digits, is_exact);
mpq_srcptr xq = number_heap(x)->q; // heap bigrat: TAG_SMALL handled above
mpz_srcptr den = mpq_denref(xq);
bool neg = mpq_sgn(xq) < 0; // zero is never heap
mpz_t Q, r, qd;
mpz_init(Q);
mpz_init(r);
mpz_init(qd);
mpz_tdiv_qr(Q, r, mpq_numref(xq), den);
mpz_abs(Q, Q); // O(1) sign strips: digit generation runs on magnitudes,
mpz_abs(r, r); // with `neg` captured above
char *int_digits = bi_to_decimal(Q);
size_t ilen = strlen(int_digits);
// Extract up to 9 fractional digits per bigint divmod instead of 1 --
// r < u.den is the loop invariant (r is always a remainder), so
// r*10^batch < u.den*10^batch, i.e. the quotient below always has at
// most `batch` digits: zero-padding it out to `batch` digits is exact,
// not just a bound. 9 is the most digits a uint32 quotient can safely
// hold (10^9 < 2^32) while still fitting bi_mul_u32's uint32 multiplier.
static const uint32_t POW10_U32[10] = {
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000,
};
char *frac = xmalloc(max_frac_digits ? max_frac_digits : 1);
uint32_t fn = 0;
while (fn < max_frac_digits && mpz_sgn(r) != 0) {
uint32_t batch = max_frac_digits - fn < 9 ? max_frac_digits - fn : 9;
mpz_mul_ui(r, r, POW10_U32[batch]);
mpz_tdiv_qr(qd, r, r, den); // in-place: buffers reused every batch
uint32_t q = (uint32_t)mpz_get_ui(qd); // < 10^batch <= 10^9, fits u32
for (uint32_t i = 0; i < batch; i++)
frac[fn + i] = (char)('0' + (q / POW10_U32[batch - 1 - i]) % 10);
fn += batch;
// The fraction terminated exactly inside this batch: q's low digits
// are correct trailing-zero padding *past* the true last digit
// (single-digit-at-a-time division would have stopped there), not
// significant output, so trim back to it. There's always a nonzero
// digit to stop at: if the whole batch were zero, r would already
// have been zero going in, contradicting the loop condition above.
if (mpz_sgn(r) == 0)
while (frac[fn - 1] == '0')
fn--;
}
bool exact = mpz_sgn(r) == 0;
// Round half-even on the last emitted digit.
bool round_up = false;
if (!exact) {
mpz_mul_2exp(r, r, 1);
int c = mpz_cmp(r, den);
if (c > 0) {
round_up = true;
} else if (c == 0) {
char last = fn ? frac[fn - 1] : int_digits[ilen - 1];
round_up = ((last - '0') & 1) != 0;
}
}
mpz_clear(Q);
mpz_clear(r);
mpz_clear(qd);
// One contiguous digit buffer with a spare leading '0' to absorb carries.
char *digits = xmalloc(1 + ilen + fn);
digits[0] = '0';
memcpy(digits + 1, int_digits, ilen);
memcpy(digits + 1 + ilen, frac, fn);
if (round_up) {
size_t i = ilen + fn;
while (digits[i] == '9')
digits[i--] = '0';
digits[i]++;
}
bool carried = digits[0] != '0';
// See number_to_string_small's identical guard: a negative value whose
// printed digits all round down to zero prints "0", not "-0".
if (neg && !carried) {
neg = false;
for (size_t i = 1; i <= ilen + fn; i++) {
if (digits[i] != '0') {
neg = true;
break;
}
}
}
char *out = xmalloc(neg + carried + ilen + (fn ? 1 + fn : 0) + 1);
size_t pos = 0;
if (neg) out[pos++] = '-';
if (carried) out[pos++] = digits[0];
memcpy(out + pos, digits + 1, ilen);
pos += ilen;
if (fn) {
out[pos++] = '.';
memcpy(out + pos, digits + 1 + ilen, fn);
pos += fn;
}
out[pos] = '\0';
if (is_exact) *is_exact = exact;
return out;
}
// ---------------------------------------------------------------------------
// Symbolic form
// A rational as "42", "-7/2", ...
// ---------------------------------------------------------------------------
// Shared subexpressions in the symbolic form
//
// A value's structure is a DAG: `y = x + x` refers to x twice rather than
// copying it. Printing it as a plain expression unfolds that DAG into a tree,
// which is exponential in the worst case: doubling an irrational n times
// prints 2^n copies of it.
//
// So: print plainly while the unfolded form stays small (the overwhelmingly
// common case, and the readable one), and past a threshold switch to a form
// that names each repeated subexpression once:
//
// sin(1) + sin(1) stays as it is
// {#1 = <big>; #1 + #1} once unfolding would be too large
//
// The labelled form is still exact, still readable, and linear in the number
// of distinct nodes rather than exponential. number_from_symbolic parses both.
// Past this many nodes in the unfolded tree, switch to the labelled form.
// Well above any expression a person would write out, far below the point
// where unfolding costs real memory.
#define SYM_UNFOLD_LIMIT 1000
typedef struct {
Num_t node;
size_t refs; // how many times the DAG reaches this node
size_t size; // its unfolded size, saturating at SYM_UNFOLD_LIMIT
int label; // -1 until assigned
} sym_entry;
typedef struct {
sym_entry *entries;
size_t len, cap;
} sym_table;
// Only IRRATIONAL nodes can be shared in a way that matters: every other kind
// prints in a bounded amount of text (a rational's digits, or a real's
// a + b*F with rational parts), so unfolding one costs nothing.
static bool sym_shareable(Num_t x) {
return is_irrational_kind(x);
}
static sym_entry *sym_find(sym_table *t, Num_t x) {
for (size_t i = 0; i < t->len; i++)
if (t->entries[i].node.bits == x.bits) return &t->entries[i];
return NULL;
}
// Records a reference to x and returns the size of its unfolded form,
// saturating at SYM_UNFOLD_LIMIT. Each distinct node is expanded once and
// its size remembered, so a DAG whose unfolded form is astronomical is still
// measured in time linear in the number of distinct nodes.
static size_t sym_survey(sym_table *t, Num_t x) {
if (!sym_shareable(x)) return 1;
sym_entry *existing = sym_find(t, x);
if (existing) {
existing->refs++;
return existing->size; // seen before: same size, one more reference
}
if (t->len == t->cap) {
size_t new_cap = t->cap ? t->cap * 2 : 16;
sym_entry *grown = xmalloc(new_cap * sizeof(sym_entry));
if (t->entries) memcpy(grown, t->entries, t->len * sizeof(sym_entry));
t->entries = grown;
t->cap = new_cap;
}
size_t index = t->len++;
t->entries[index] = (sym_entry){.node = x, .refs = 1, .size = 0, .label = -1};
num_irr *n = as_irr(x);
// Both children always, even once the size has saturated: the reference
// counts are what decide which nodes get named, and skipping a subtree
// would undercount everything in it.
size_t size = 1 + sym_survey(t, n->x);
if (n->y.bits != 0) size += sym_survey(t, n->y);
if (size > SYM_UNFOLD_LIMIT) size = SYM_UNFOLD_LIMIT;
t->entries[index].size = size; // by index: the array may have moved above
return size;
}
// The label table in force during a shared-form render. number.c is
// single-threaded (see number_pi's note), so a static is consistent with how
// the rest of this file already caches.
static struct {
bool active;
sym_table *table;
Num_t defining; // the node whose binding is being written right now
} sym_share = {.active = false, .table = NULL, .defining = {0}};
static char *symbolic_render(Num_t x);
static int sym_label_of(Num_t x) {
if (!sym_share.table) return -1;
sym_entry *e = sym_find(sym_share.table, x);
return e ? e->label : -1;
}
static char *rational_symbolic(Num_t x) {
if (number_tag(x) == TAG_SMALL) {
// Small rationals are already canonical (number-design.md) and fit an
// int64/uint32 outright, so format directly instead of routing
// through mpz just to convert straight back to decimal.
int64_t num = small_num(x);
uint64_t den = small_den(x);
return den == 1 ? xsprintf("%lld", (long long)num)
: xsprintf("%lld/%llu", (long long)num, (unsigned long long)den);
}
mpq_srcptr q = number_heap(x)->q; // heap bigrat: TAG_SMALL handled above
char *num = mpz_mag_decimal(mpq_numref(q));
const char *minus = mpq_sgn(q) < 0 ? "-" : ""; // zero is never heap
char *result;
if (bi_is_one(mpq_denref(q))) {
result = xsprintf("%s%s", minus, num);
} else {
char *den = bi_to_decimal(mpq_denref(q));
result = xsprintf("%s%s/%s", minus, num, den);
}
return result;
}
static const char *irr_op_name(int op) {
switch (op) {
case IRR_SIN: return "sin";
case IRR_COS: return "cos";
case IRR_EXP: return "exp";
case IRR_LN: return "ln";
case IRR_ATAN: return "atan";
case IRR_SQRT: return "sqrt";
default: return NULL; // ADD/SUB/MUL/DIV print as infix, not a function call
}
}
#ifdef NUMBER_STATS
void number_stats_dump(FILE *out) {
static const char *const op_names[STAT_OP_COUNT] = {
[STAT_ADD] = "add", [STAT_SUB] = "sub", [STAT_MUL] = "mul", [STAT_DIV] = "div", [STAT_COMPARE] = "compare",
};
fprintf(out, "number_stats:\n calls:");
for (int i = 0; i < STAT_OP_COUNT; i++)
fprintf(out, " %s=%llu", op_names[i], (unsigned long long)number_stats.ops[i]);
uint64_t dispatched =
number_stats.tier1_fastpath + number_stats.tier1_general + number_stats.tier2 + number_stats.tier3;
fprintf(out,
"\n tier1: fastpath=%llu general=%llu\n tier2 (bigrat): %llu (u128 fast path: %llu)\n"
" tier3 (real/irrational): %llu\n dispatched total: %llu\n"
" promotions (tier1 -> tier2): %llu\n bi_allocs: %llu\n"
" irr nodes: memo_hits=%llu recompute:",
(unsigned long long)number_stats.tier1_fastpath, (unsigned long long)number_stats.tier1_general,
(unsigned long long)number_stats.tier2, (unsigned long long)number_stats.tier2_u128_fast,
(unsigned long long)number_stats.tier3, (unsigned long long)dispatched,
(unsigned long long)number_stats.promotions, (unsigned long long)number_stats.bi_allocs,
(unsigned long long)number_stats.irr_memo_hits);
for (int op = 0; op < IRR_DIV + 1; op++) {
const char *name = irr_op_name(op);
if (!name) name = op == IRR_ADD ? "add" : op == IRR_SUB ? "sub" : op == IRR_MUL ? "mul" : "div";
fprintf(out, " %s=%llu", name, (unsigned long long)number_stats.irr_recompute[op]);
}
fprintf(out, "\n double-interval filter: hits=%llu fallbacks=%llu\n",
(unsigned long long)number_stats.interval_hits, (unsigned long long)number_stats.interval_fallbacks);
}
#endif
// Whether a and b certainly denote the same value, decided cheaply and
// syntactically: bitwise-identical words (the same immediate, or the same
// shared node, e.g. every pi is the cached singleton), equal rationals
// (exact and cheap, since both rational tiers are canonical), or
// same-shaped real/irrational nodes over such parts (two separately-built
// sin(2) nodes are distinct pointers but identical shapes). Never refines
// approximations and never loops, unlike full number_equal on two
// irrationals; "false" just means "not provably the same by inspection",
// which the power-collapsing display below treats as distinct factors.
static bool same_displayed_value(Num_t a, Num_t b) {
if (a.bits == b.bits) return true;
if (is_real_kind(a) && is_real_kind(b)) {
num_real *ra = as_real(a), *rb = as_real(b);
return ra->fn == rb->fn && same_displayed_value(ra->a, rb->a) && same_displayed_value(ra->b, rb->b)
&& (ra->fn == FN_PI || same_displayed_value(ra->arg, rb->arg));
}
if (is_irrational_kind(a) && is_irrational_kind(b)) {
num_irr *na = as_irr(a), *nb = as_irr(b);
return na->op == nb->op && same_displayed_value(na->x, nb->x) && same_displayed_value(na->y, nb->y);
}
if (number_is_rational(a) && number_is_rational(b)) return number_equal(a, b);
return false;
}
// The leaves of a multiplication chain: nested IRR_MUL nodes flattened in
// left-to-right order, so ((x*x)*x) and (x*(x*x)) both yield [x, x, x].
static size_t count_mul_leaves(Num_t x) {
if (is_irrational_kind(x) && as_irr(x)->op == IRR_MUL)
return count_mul_leaves(as_irr(x)->x) + count_mul_leaves(as_irr(x)->y);
return 1;
}
static void fill_mul_leaves(Num_t x, Num_t *leaves, size_t *pos) {
if (is_irrational_kind(x) && as_irr(x)->op == IRR_MUL) {
fill_mul_leaves(as_irr(x)->x, leaves, pos);
fill_mul_leaves(as_irr(x)->y, leaves, pos);
return;
}
leaves[(*pos)++] = x;
}
// Whether every leaf of the multiplication chain under x matches ref --
// i.e. the chain displays as a single power like "pi^3".
static bool mul_leaves_all_same(Num_t x, Num_t ref) {
if (is_irrational_kind(x) && as_irr(x)->op == IRR_MUL)
return mul_leaves_all_same(as_irr(x)->x, ref) && mul_leaves_all_same(as_irr(x)->y, ref);
return same_displayed_value(x, ref);
}
// How loosely the printed form of x binds at its top level, for deciding
// whether an infix context must parenthesize it as an operand. Ordered
// from tightest to loosest:
// RENDER_ATOM: a single token or self-delimited form, e.g. "42", "pi",
// "sqrt(6)", "sin(2)", "pi^3"
// RENDER_PRODUCT: a top-level '*' or '/', e.g. "1/3", "2*pi", "sqrt(5)/2",
// "pi^2*sin(2)", "x/y"
// RENDER_SUM: a top-level '+'/'-' or a leading sign, e.g. "1 + pi", "-pi",
// "-2", "x + y"
enum { RENDER_ATOM, RENDER_PRODUCT, RENDER_SUM };
static int render_level(Num_t x) {
// A labelled node prints as "#1": a single token, never needing parens,
// whatever the expression underneath it would have needed.
if (sym_share.active && x.bits != sym_share.defining.bits && sym_label_of(x) >= 0) return RENDER_ATOM;
if (is_irrational_kind(x)) {
num_irr *n = as_irr(x);
switch (n->op) {
case IRR_ADD:
case IRR_SUB: return RENDER_SUM;
case IRR_DIV:
return RENDER_PRODUCT; // "x/y" (TeX \frac never
// needs wrapping, and PRODUCT
// never triggers one there)
case IRR_MUL: {
// "pi^3" binds like an atom; "pi^2*sin(2)" is a product
Num_t ref = n->x;
while (is_irrational_kind(ref) && as_irr(ref)->op == IRR_MUL)
ref = as_irr(ref)->x;
return mul_leaves_all_same(x, ref) ? RENDER_ATOM : RENDER_PRODUCT;
}
default: return RENDER_ATOM; // function calls: "sin(2)", "exp(2)"
}
}
if (is_real_kind(x)) {
num_real *r = as_real(x);
if (!number_is_zero(r->a) || number_is_negative(r->b)) return RENDER_SUM;
// A bare positive term: "pi"/"sqrt(n)" when b == 1, else "2*pi",
// "sqrt(5)/2", ...
return number_equal(r->b, NUMBER_ONE) ? RENDER_ATOM : RENDER_PRODUCT;
}
// Rational tier
if (number_is_negative(x)) return RENDER_SUM;
if (number_tag(x) == TAG_SMALL) return small_den(x) == 1 ? RENDER_ATOM : RENDER_PRODUCT;
return bi_is_one(mpq_denref(number_heap(x)->q)) ? RENDER_ATOM : RENDER_PRODUCT;
}
// Wrap an already-malloc'd rendering in parentheses, consuming it.
static char *wrap_parens(char *s) {
char *wrapped = xsprintf("(%s)", s);
return wrapped;
}
// Returns whether base's rendering must be wrapped in parentheses before
// an exponent is attached to it. Anything looser than an atom, plus two
// TeX-only cases: e^{x}^{2} is a double-superscript error and
// \frac{...}{...}^{2} parses wrong (the symbolic "exp(x)" and
// "x/y" are fine as-is).
static bool pow_base_needs_parens(Num_t base, bool tex) {
if (tex && is_irrational_kind(base)) {
uint32_t op = as_irr(base)->op;
if (op == IRR_EXP || op == IRR_DIV) return true;
}
return render_level(base) != RENDER_ATOM;
}
// A multiplication chain with repeated factors collapsed into powers:
// x*x*x displays as "x^3" (or "x^{3}" in TeX) rather than "x*x*x".
// Factors are grouped by same_displayed_value in first-appearance order
// because commutativity makes the reordering sound. No outer parens: contexts
// that embed this as an operand add them per render_level.
static char *mul_chain_str(num_irr *n, bool tex) {
size_t total = count_mul_leaves(n->x) + count_mul_leaves(n->y);
Num_t *leaves = xmalloc(total * sizeof(Num_t));
size_t pos = 0;
fill_mul_leaves(n->x, leaves, &pos);
fill_mul_leaves(n->y, leaves, &pos);
char *acc = NULL;
for (size_t i = 0; i < total; i++) {
if (leaves[i].bits == 0) continue; // folded into an earlier factor
size_t power = 1;
for (size_t j = i + 1; j < total; j++) {
if (leaves[j].bits != 0 && same_displayed_value(leaves[i], leaves[j])) {
leaves[j].bits = 0; // no real number has word 0 (that's a NULL pointer)
power++;
}
}
char *base = tex ? number_to_tex(leaves[i]) : symbolic_render(leaves[i]);
char *piece = base;
if (power > 1) {
bool parens = pow_base_needs_parens(leaves[i], tex);
if (tex) piece = parens ? xsprintf("(%s)^{%zu}", base, power) : xsprintf("%s^{%zu}", base, power);
else piece = parens ? xsprintf("(%s)^%zu", base, power) : xsprintf("%s^%zu", base, power);
} else if (render_level(leaves[i]) == RENDER_SUM) {
// An additive or sign-leading factor misreads bare in a
// product: (1+pi)*x is "(1 + pi)*x", not "1 + pi*x"
piece = wrap_parens(base);
}
if (!acc) {
acc = piece;
} else {
// '*' prints tight, matching the closed forms ("2*pi"); TeX
// keeps spaces around \cdot only to delimit the macro name
// (math mode ignores them)
char *joined = tex ? xsprintf("%s \\cdot %s", acc, piece) : xsprintf("%s*%s", acc, piece);
acc = joined;
}
}
return acc;
}
// A general IRRATIONAL node has no closed algebraic form (that's why it's
// here), but number_to_symbolic's contract is "always exact", satisfied
// by printing an exact expression-tree description instead of a decimal
// approximation, e.g. "sin(2)", "pi + sqrt(2)", "pi^2".
static char *irr_symbolic(num_irr *n) {
if (n->op == IRR_MUL) return mul_chain_str(n, false);
char *xs = symbolic_render(n->x);
const char *fname = irr_op_name(n->op);
if (fname) {
char *result = xsprintf("%s(%s)", fname, xs);
return result;
}
char *ys = symbolic_render(n->y);
// A right operand that renders as a sum misreads after any of these
// operators ("x - 1 + pi"); after '/' even a product does ("x/2*pi").
// Left operands only misread when a sum meets '/' ("1 + pi/x").
if (render_level(n->y) >= (n->op == IRR_DIV ? RENDER_PRODUCT : RENDER_SUM)) ys = wrap_parens(ys);
if (n->op == IRR_DIV && render_level(n->x) == RENDER_SUM) xs = wrap_parens(xs);
// '/' prints tight and '+'/'-' spaced, matching the closed forms
// ("pi/3", "1/2 + sqrt(5)/2")
char *result =
n->op == IRR_DIV ? xsprintf("%s/%s", xs, ys) : xsprintf("%s %s %s", xs, n->op == IRR_ADD ? "+" : "-", ys);
return result;
}
static char *symbolic_render(Num_t x) {
// In shared mode, a subexpression that appears more than once prints as
// its label everywhere except the binding that defines it.
if (sym_share.active && x.bits != sym_share.defining.bits) {
int label = sym_label_of(x);
if (label >= 0) return xsprintf("#%d", label);
}
if (number_is_error(x)) return xstrdup("(error)");
if (is_irrational_kind(x)) return irr_symbolic(as_irr(x));
if (!is_real_kind(x)) return rational_symbolic(x);
// a + b*F prints as e.g. "pi", "2*pi", "-sqrt(2)", "3 - sqrt(2)",
// "sqrt(5)/2", "1/2 + sqrt(5)/2": the coefficient b = +-p/q wraps the
// factor as [p*]F[/q], and a nonzero rational part is printed in front.
num_real *r = as_real(x);
char *factor;
if (r->fn == FN_PI) {
factor = xstrdup("pi");
} else if (r->fn == FN_SQRT) {
char *rad = rational_symbolic(r->arg);
factor = xsprintf("sqrt(%s)", rad);
} else if (r->fn == FN_LN) {
char *arg_s = rational_symbolic(r->arg);
factor = xsprintf("ln(%s)", arg_s);
} else { // FN_EXP
char *arg_s = rational_symbolic(r->arg);
factor = xsprintf("exp(%s)", arg_s);
}
mpq_view vb;
mpq_srcptr qb = number_mpq_view(r->b, &vb);
bool bneg = mpq_sgn(qb) < 0; // b is nonzero by the real-form invariant
char *term;
if (mpz_cmpabs_ui(mpq_numref(qb), 1) == 0) { // |num| == 1
term = factor;
} else {
char *p = mpz_mag_decimal(mpq_numref(qb));
term = xsprintf("%s*%s", p, factor);
}
if (!bi_is_one(mpq_denref(qb))) {
char *q = bi_to_decimal(mpq_denref(qb));
char *t = xsprintf("%s/%s", term, q);
term = t;
}
char *result;
if (number_is_zero(r->a)) {
result = bneg ? xsprintf("-%s", term) : xstrdup(term);
} else {
char *a_str = rational_symbolic(r->a);
result = xsprintf("%s %c %s", a_str, bneg ? '-' : '+', term);
}
return result;
}
char *number_to_symbolic(Num_t x) {
sym_table table = {.entries = NULL, .len = 0, .cap = 0};
size_t unfolded = sym_survey(&table, x);
if (unfolded < SYM_UNFOLD_LIMIT) return symbolic_render(x); // the common case
// Too big to unfold: name every subexpression that appears more than
// once, so each is written exactly one time.
int next_label = 1;
for (size_t i = 0; i < table.len; i++)
if (table.entries[i].refs > 1) table.entries[i].label = next_label++;
if (next_label == 1) return symbolic_render(x); // big, but nothing repeats
sym_share.active = true;
sym_share.table = &table;
// Bindings in dependency order: sym_survey visits a node before its
// children, so walking the table backwards defines each label after
// everything it refers to.
char *out = xstrdup("{");
for (size_t i = table.len; i-- > 0;) {
sym_entry *e = &table.entries[i];
if (e->label < 0) continue;
sym_share.defining = e->node;
char *body = symbolic_render(e->node);
sym_share.defining = (Num_t){0};
char *joined = xsprintf("%s#%d = %s; ", out, e->label, body);
out = joined;
}
char *root = symbolic_render(x);
char *result = xsprintf("%s%s}", out, root);
sym_share.active = false;
sym_share.table = NULL;
return result;
}
// A rational as TeX: "42", "-\frac{7}{2}" (the sign stays outside the
// fraction, where TeX convention puts it).
static char *rational_tex(Num_t x) {
if (number_tag(x) == TAG_SMALL) {
int64_t num = small_num(x);
uint64_t den = small_den(x);
if (den == 1) return xsprintf("%lld", (long long)num);
return xsprintf("%s\\frac{%lld}{%llu}", num < 0 ? "-" : "", (long long)(num < 0 ? -num : num),
(unsigned long long)den);
}
mpq_srcptr q = number_heap(x)->q; // heap bigrat: TAG_SMALL handled above
char *num = mpz_mag_decimal(mpq_numref(q));
const char *minus = mpq_sgn(q) < 0 ? "-" : ""; // zero is never heap
char *result;
if (bi_is_one(mpq_denref(q))) {
result = xsprintf("%s%s", minus, num);
} else {
char *den = bi_to_decimal(mpq_denref(q));
result = xsprintf("%s\\frac{%s}{%s}", minus, num, den);
}
return result;
}
// Get the TeX representation for an irrational node, e.g. "\frac{\ln(5)}{\ln(10)}"
static char *irr_tex(num_irr *n) {
if (n->op == IRR_MUL) return mul_chain_str(n, true);
char *xs = number_to_tex(n->x);
char *result;
switch (n->op) {
case IRR_SQRT: result = xsprintf("\\sqrt{%s}", xs); break;
case IRR_EXP: result = xsprintf("e^{%s}", xs); break;
case IRR_SIN: result = xsprintf("\\sin(%s)", xs); break;
case IRR_COS: result = xsprintf("\\cos(%s)", xs); break;
case IRR_LN: result = xsprintf("\\ln(%s)", xs); break;
case IRR_ATAN: result = xsprintf("\\arctan(%s)", xs); break;
case IRR_DIV: {
char *ys = number_to_tex(n->y);
result = xsprintf("\\frac{%s}{%s}", xs, ys);
break;
}
default: { // ADD/SUB: infix, same operand rule as irr_symbolic
char *ys = number_to_tex(n->y);
// A sum-rendering right operand misreads bare ("x - 1 + \pi").
// \frac division needs no wrapping.
if (render_level(n->y) == RENDER_SUM) ys = wrap_parens(ys);
const char *op_str = n->op == IRR_ADD ? "+" : "-";
result = xsprintf("%s %s %s", xs, op_str, ys);
break;
}
}
return result;
}
char *number_to_tex(Num_t x) {
if (number_is_error(x)) return xstrdup("\\text{error}");
if (is_irrational_kind(x)) return irr_tex(as_irr(x));
if (!is_real_kind(x)) return rational_tex(x);
// a + b*F, same structure as number_to_symbolic: the coefficient
// b = +-p/q wraps the factor as \frac{[p]F}{q} (juxtaposition, no '*':
// "2\pi", "3\sqrt{2}"), and a nonzero rational part goes in front.
num_real *r = as_real(x);
char *factor;
switch (r->fn) {
case FN_PI: {
factor = xstrdup("\\pi");
break;
}
case FN_SQRT: {
char *rad = rational_symbolic(r->arg); // a positive integer: plain digits
factor = xsprintf("\\sqrt{%s}", rad);
break;
}
case FN_LN: {
char *arg_s = rational_tex(r->arg);
factor = xsprintf("\\ln(%s)", arg_s);
break;
}
case FN_EXP: {
char *arg_s = rational_tex(r->arg);
factor = xsprintf("e^{%s}", arg_s);
break;
}
default: {
factor = xsprintf("???");
break;
}
}
mpq_view vb;
mpq_srcptr qb = number_mpq_view(r->b, &vb);
bool bneg = mpq_sgn(qb) < 0; // b is nonzero by the real-form invariant
char *term;
if (mpz_cmpabs_ui(mpq_numref(qb), 1) == 0) { // |num| == 1
term = factor;
} else {
char *p = mpz_mag_decimal(mpq_numref(qb));
term = xsprintf("%s%s", p, factor);
}
if (!bi_is_one(mpq_denref(qb))) {
char *q = bi_to_decimal(mpq_denref(qb));
char *t = xsprintf("\\frac{%s}{%s}", term, q);
term = t;
}
char *result;
if (number_is_zero(r->a)) {
result = bneg ? xsprintf("-%s", term) : xstrdup(term);
} else {
char *a_str = rational_tex(r->a);
result = xsprintf("%s %c %s", a_str, bneg ? '-' : '+', term);
}
return result;
}
// ---------------------------------------------------------------------------
// Parsing
#define MAX_PARSE_EXPONENT 999999L
// Fast path for number_from_string: parses a literal directly into int64
// accumulators, mirroring the immediate-tier fast paths every arithmetic
// operator already takes (see number_addsub/number_mul/number_div) instead
// of unconditionally building through the bigint accumulator below. The
// exact path still handles every case correctly on its own, so this only
// needs to be conservative, never exhaustive: on any accumulator overflow,
// unexpected character, or malformed input, it bails out (false, no side
// effects via *out) and the caller re-parses with the exact path, which
// produces the right value (or the right parse error) either way.
static bool number_from_string_fast(const char *str, Num_t *out) {
const char *s = str;
while (isspace((unsigned char)*s))
s++;
int sign = 1;
if (*s == '+' || *s == '-') {
if (*s == '-') sign = -1;
s++;
}
int64_t mant = 0;
bool any_digits = false;
while (isdigit((unsigned char)*s)) {
if (__builtin_mul_overflow(mant, (int64_t)10, &mant)
|| __builtin_add_overflow(mant, (int64_t)(*s - '0'), &mant))
return false;
any_digits = true;
s++;
}
if (*s == '/') { // rational form: digits '/' digits
s++;
int64_t den = 0;
bool any_den = false;
while (isdigit((unsigned char)*s)) {
if (__builtin_mul_overflow(den, (int64_t)10, &den)
|| __builtin_add_overflow(den, (int64_t)(*s - '0'), &den))
return false;
any_den = true;
s++;
}
while (isspace((unsigned char)*s))
s++;
if (!any_digits || !any_den || *s != '\0') return false; // malformed/empty: let the exact path report it
*out = number_from_ratio64(sign * mant, den);
return true;
}
long frac_digits = 0;
if (*s == '.') {
s++;
while (isdigit((unsigned char)*s)) {
if (__builtin_mul_overflow(mant, (int64_t)10, &mant)
|| __builtin_add_overflow(mant, (int64_t)(*s - '0'), &mant))
return false;
frac_digits++;
any_digits = true;
s++;
}
}
long expo = 0;
if (any_digits && (*s == 'e' || *s == 'E')) {
s++;
int esign = 1;
if (*s == '+' || *s == '-') {
if (*s == '-') esign = -1;
s++;
}
bool any_exp = false;
while (isdigit((unsigned char)*s)) {
if (expo > MAX_PARSE_EXPONENT) return false; // large exponent: let the exact path scale it (or error)
expo = expo * 10 + (*s - '0');
any_exp = true;
s++;
}
if (!any_exp) return false; // malformed: let the exact path report it
expo *= esign;
}
while (isspace((unsigned char)*s))
s++;
if (!any_digits || *s != '\0') return false; // malformed/empty: let the exact path report it
// value = mant * 10^(expo - frac_digits); scale (a single power of 10)
// must itself fit in int64, same as mant. Past ~18 digits of combined
// magnitude this overflows and bails, same as the digit loops above.
long p10 = expo - frac_digits;
long mag = p10 < 0 ? -p10 : p10;
int64_t scale = 1;
for (long i = 0; i < mag; i++) {
if (__builtin_mul_overflow(scale, (int64_t)10, &scale)) return false;
}
if (p10 >= 0) {
int64_t num;
if (__builtin_mul_overflow(mant, scale, &num)) return false;
*out = number_from_ratio64(sign * num, 1);
} else {
*out = number_from_ratio64(sign * mant, scale);
}
return true;
}
// Appends a run of decimal digits to *acc, batching up to 9 digits into a
// uint32 chunk per bi_muladd_u32 pass (10^9 is the largest power of ten
// whose chunk still fits a uint32) instead of one pass per digit, the same
// batching bi_to_decimal uses in the other direction. Advances *sp past the
// run and returns how many digits it consumed (0 if *sp starts on a
// non-digit).
// Appends the digit run at *sp to *acc (acc = acc*10^n + run), advancing
// *sp past it; returns the digit count. The run parses via mpz_set_str
// (subquadratic divide-and-conquer, same family as mpz_get_str on output),
// so a huge literal costs what GMP's own conversion costs, not O(n^2).
static long parse_digit_run(bigint **acc, const char **sp) {
const char *start = *sp, *s = start;
while (isdigit((unsigned char)*s))
s++;
long count = s - start;
*sp = s;
if (count == 0) return 0;
char *buf = xmalloc((size_t)count + 1);
memcpy(buf, start, (size_t)count);
buf[count] = '\0';
mpz_t run;
mpz_init(run);
mpz_set_str(run, buf, 10);
if (!bi_is_zero(*acc)) { // continuing across a '.': shift what's there
mpz_t p;
mpz_init(p);
mpz_ui_pow_ui(p, 10, (unsigned long)count);
mpz_mul(*acc, *acc, p);
mpz_clear(p);
}
mpz_add(*acc, *acc, run);
mpz_clear(run);
return count;
}
Num_t number_from_string(const char *str) {
if (!str) return err(ERR_PARSE);
Num_t fast;
if (number_from_string_fast(str, &fast)) return fast;
const char *s = str;
while (isspace((unsigned char)*s))
s++;
int sign = 1;
if (*s == '+' || *s == '-') {
if (*s == '-') sign = -1;
s++;
}
bigint *mant = bi_new();
bool any_digits = parse_digit_run(&mant, &s) > 0;
if (*s == '/') { // rational form: digits '/' digits
s++;
bigint *den = bi_new();
bool any_den = parse_digit_run(&den, &s) > 0;
while (isspace((unsigned char)*s))
s++;
if (!any_digits || !any_den || *s != '\0') {
bi_free(mant);
bi_free(den);
return err(ERR_PARSE);
}
return canon_make(sign, mant, den);
}
long frac_digits = 0;
if (*s == '.') {
s++;
frac_digits = parse_digit_run(&mant, &s);
if (frac_digits > 0) any_digits = true;
}
long expo = 0;
if (any_digits && (*s == 'e' || *s == 'E')) {
s++;
int esign = 1;
if (*s == '+' || *s == '-') {
if (*s == '-') esign = -1;
s++;
}
bool any_exp = false;
while (isdigit((unsigned char)*s)) {
if (expo <= MAX_PARSE_EXPONENT) expo = expo * 10 + (*s - '0');
any_exp = true;
s++;
}
if (!any_exp || expo > MAX_PARSE_EXPONENT) {
bi_free(mant);
return err(ERR_PARSE);
}
expo *= esign;
}
while (isspace((unsigned char)*s))
s++;
if (!any_digits || *s != '\0') {
bi_free(mant);
return err(ERR_PARSE);
}
// value = mant * 10^(expo - frac_digits)
long p10 = expo - frac_digits;
bigint *num, *den;
if (p10 >= 0) {
bigint *scale = bi_pow10((uint32_t)p10);
num = bi_mul(mant, scale);
bi_free(mant);
bi_free(scale);
den = bi_from_u64(1);
} else {
num = mant;
den = bi_pow10((uint32_t)-p10);
}
return canon_make(sign, num, den);
}
// The named, exact decimal-literal constructor (see number.h): the answer to
// "how do I turn the literal 3.15 into a number without number_from_double's
// silent double round-trip." Deliberately delegates to number_from_string's
// exact decimal parse, since the exact machinery already lives there and this
// is the discoverable name for it, but rejects the "22/7" ratio form up
// front, so "decimal" is honest (a ratio isn't decimal notation; callers who
// want one reach for number_from_ratio or number_from_string).
Num_t number_from_decimal(const char *str) {
if (!str) return err(ERR_PARSE);
for (const char *p = str; *p; p++)
if (*p == '/') return err(ERR_PARSE);
return number_from_string(str);
}
// ---------------------------------------------------------------------------
// Parsing the symbolic form
//
// The inverse of number_to_symbolic: reads back the exact expression it
// prints, so an exact value (irrationals included) can round-trip through
// text. The grammar is exactly what that printer emits:
//
// expr := term ((' + ' | ' - ') term)*
// term := unary (('*' | '/') unary)*
// unary := '-' unary | power
// power := atom ('^' digits)?
// atom := '(' expr ')' | digits | 'pi' | fn '(' expr ')'
// fn := 'sin' | 'cos' | 'exp' | 'ln' | 'atan' | 'sqrt'
//
// A rational prints as "p/q", which parses as a division of two integers and
// evaluates back to exactly p/q, so it needs no separate rule. Whitespace is
// skipped rather than required, so the spacing the printer chooses per
// operator ("1/2 + sqrt(5)/2") is not something a reader has to reproduce.
//
// Every production builds its result with the ordinary public constructors,
// so the parse is exact by the same rules the original computation was.
#define SYM_MAX_LABELS 4096
// The grammar recurses (a parenthesized group, a function's argument, a chain
// of unary minuses), and the text being parsed is untrusted, since Num
// deserialization feeds this whatever bytes it was handed. Each level costs a
// C stack frame, so "((((((..." (one byte per level) would otherwise walk off
// the stack. Far deeper than any nesting the printer emits, which stays flat.
#define SYM_MAX_DEPTH 256
typedef struct {
const char *pos;
bool failed;
int depth;
Num_t labels[SYM_MAX_LABELS]; // labels[i] is #(i+1); bits 0 means unbound
int label_count;
} sym_parser;
static Num_t sym_expr(sym_parser *p);
static void sym_skip_space(sym_parser *p) {
while (*p->pos == ' ' || *p->pos == '\t')
p->pos++;
}
// Consumes `word` only when it isn't a prefix of a longer identifier, so
// "sin" doesn't match inside a hypothetical "sinh".
static bool sym_word(sym_parser *p, const char *word) {
size_t n = strlen(word);
if (strncmp(p->pos, word, n) != 0) return false;
char after = p->pos[n];
if (isalnum((unsigned char)after) || after == '_') return false;
p->pos += n;
return true;
}
static Num_t sym_fail(sym_parser *p) {
p->failed = true;
return NUMBER_ERROR;
}
// The two productions that can recurse once per input character, a '('
// group and a '-' chain, go through this, so a hostile input hits a parse
// error instead of a stack overflow. See SYM_MAX_DEPTH.
#define SYM_RECURSE(body_fn) \
if (++p->depth > SYM_MAX_DEPTH) { \
p->depth--; \
return sym_fail(p); \
} \
Num_t sym_recurse_result = body_fn(p); \
p->depth--; \
return sym_recurse_result
static Num_t sym_atom_body(sym_parser *p);
static Num_t sym_atom(sym_parser *p) {
SYM_RECURSE(sym_atom_body);
}
static Num_t sym_atom_body(sym_parser *p) {
sym_skip_space(p);
if (*p->pos == '(') {
p->pos++;
Num_t inner = sym_expr(p);
sym_skip_space(p);
if (*p->pos != ')') return sym_fail(p);
p->pos++;
return inner;
}
if (isdigit((unsigned char)*p->pos)) {
const char *start = p->pos;
while (isdigit((unsigned char)*p->pos))
p->pos++;
size_t len = (size_t)(p->pos - start);
char *digits = xmalloc(len + 1);
memcpy(digits, start, len);
digits[len] = '\0';
Num_t value = number_from_decimal(digits);
if (number_is_error(value)) return sym_fail(p);
return value;
}
// A label reference from the shared form: "#1"
if (*p->pos == '#') {
p->pos++;
if (!isdigit((unsigned char)*p->pos)) return sym_fail(p);
int index = 0;
while (isdigit((unsigned char)*p->pos)) {
if (index > SYM_MAX_LABELS) return sym_fail(p);
index = index * 10 + (*p->pos++ - '0');
}
if (index < 1 || index > p->label_count) return sym_fail(p);
Num_t bound = p->labels[index - 1];
if (bound.bits == 0) return sym_fail(p);
return bound;
}
if (sym_word(p, "pi")) return number_pi();
static const struct {
const char *name;
Num_t (*fn)(Num_t);
} fns[] = {
{"sqrt", number_sqrt}, {"sin", number_sin}, {"cos", number_cos},
{"exp", number_exp}, {"ln", number_ln}, {"atan", number_atan},
};
for (size_t i = 0; i < sizeof(fns) / sizeof(fns[0]); i++) {
if (!sym_word(p, fns[i].name)) continue;
sym_skip_space(p);
if (*p->pos != '(') return sym_fail(p);
p->pos++;
Num_t arg = sym_expr(p);
sym_skip_space(p);
if (*p->pos != ')') return sym_fail(p);
p->pos++;
if (p->failed) return NUMBER_ERROR;
Num_t value = fns[i].fn(arg);
if (number_is_error(value)) return sym_fail(p);
return value;
}
return sym_fail(p);
}
static Num_t sym_power(sym_parser *p) {
Num_t base = sym_atom(p);
if (p->failed) return NUMBER_ERROR;
sym_skip_space(p);
if (*p->pos != '^') return base;
p->pos++;
sym_skip_space(p);
if (!isdigit((unsigned char)*p->pos)) return sym_fail(p);
// The printer only ever emits a small unsigned exponent (a repeat count
// from a product chain), so this doesn't need the general atom rule.
uint64_t exponent = 0;
while (isdigit((unsigned char)*p->pos)) {
// Bounded by INT64_MAX, not UINT64_MAX: the cast to int64_t below
// would otherwise wrap a larger exponent into a negative one and
// silently accept it ("2^9223372036854775808" would parse as 2^-2^63).
if (exponent > ((uint64_t)INT64_MAX - 9) / 10) return sym_fail(p);
exponent = exponent * 10 + (uint64_t)(*p->pos++ - '0');
if (exponent > (uint64_t)INT64_MAX) return sym_fail(p);
}
Num_t value = number_pow(base, number_from_int((int64_t)exponent));
if (number_is_error(value)) return sym_fail(p);
return value;
}
static Num_t sym_unary_body(sym_parser *p);
static Num_t sym_unary(sym_parser *p) {
SYM_RECURSE(sym_unary_body);
}
static Num_t sym_unary_body(sym_parser *p) {
sym_skip_space(p);
if (*p->pos == '-') {
p->pos++;
Num_t inner = sym_unary(p);
return p->failed ? NUMBER_ERROR : number_neg(inner);
}
return sym_power(p);
}
static Num_t sym_term(sym_parser *p) {
Num_t acc = sym_unary(p);
for (;;) {
if (p->failed) return NUMBER_ERROR;
sym_skip_space(p);
char op = *p->pos;
if (op != '*' && op != '/') return acc;
p->pos++;
Num_t rhs = sym_unary(p);
if (p->failed) return NUMBER_ERROR;
Num_t value = op == '*' ? number_mul(acc, rhs) : number_div(acc, rhs);
if (number_is_error(value)) return sym_fail(p);
acc = value;
}
}
static Num_t sym_expr(sym_parser *p) {
Num_t acc = sym_term(p);
for (;;) {
if (p->failed) return NUMBER_ERROR;
sym_skip_space(p);
char op = *p->pos;
if (op != '+' && op != '-') return acc;
p->pos++;
Num_t rhs = sym_term(p);
if (p->failed) return NUMBER_ERROR;
Num_t value = op == '+' ? number_add(acc, rhs) : number_sub(acc, rhs);
if (number_is_error(value)) return sym_fail(p);
acc = value;
}
}
// The labelled form: "{#1 = <expr>; #2 = <expr>; <expr>}". Each binding is
// available to the bindings after it and to the final expression, so a shared
// subexpression is written, and built, exactly once.
static Num_t sym_bindings(sym_parser *p) {
p->pos++; // '{'
for (;;) {
sym_skip_space(p);
if (*p->pos != '#') break; // no more bindings: the result expression
const char *save = p->pos;
p->pos++;
if (!isdigit((unsigned char)*p->pos)) return sym_fail(p);
int index = 0;
while (isdigit((unsigned char)*p->pos)) {
if (index > SYM_MAX_LABELS) return sym_fail(p);
index = index * 10 + (*p->pos++ - '0');
}
sym_skip_space(p);
if (*p->pos != '=') { // "#1 + #1" as the result, not a binding
p->pos = save;
break;
}
p->pos++;
if (index < 1 || index > SYM_MAX_LABELS) return sym_fail(p);
Num_t bound = sym_expr(p);
if (p->failed) return NUMBER_ERROR;
p->labels[index - 1] = bound;
if (index > p->label_count) p->label_count = index;
sym_skip_space(p);
if (*p->pos != ';') return sym_fail(p);
p->pos++;
}
Num_t result = sym_expr(p);
if (p->failed) return NUMBER_ERROR;
sym_skip_space(p);
if (*p->pos != '}') return sym_fail(p);
p->pos++;
return result;
}
Num_t number_from_symbolic(const char *str) {
if (!str) return err(ERR_PARSE);
sym_parser p = {.pos = str, .failed = false, .labels = {{0}}, .label_count = 0};
sym_skip_space(&p);
Num_t value = *p.pos == '{' ? sym_bindings(&p) : sym_expr(&p);
if (p.failed) return err(ERR_PARSE);
sym_skip_space(&p);
if (*p.pos != '\0') return err(ERR_PARSE); // trailing garbage
return value;
}
// ---------------------------------------------------------------------------
// Fuzz harness: the checks that still carry independent information now
// that the arithmetic substrate is GMP. Two things survive: the GCD
// cross-check (a plain Euclidean gcd built on division, an algorithm
// genuinely distinct from mpz_gcd's HGCD, over a size sweep dense around
// the word-size boundaries our fits-checks and views sit on), and
// number-level round trips through the public API (construction, string
// conversion, arithmetic identities) that exercise this library's own
// glue: tier demotion, canonicalization, sign handling, parsing/printing.
// The old primitive property tests (divmod identity, mul commutativity,
// shift round trips, isqrt bounds) verified GMP against GMP once the
// backend switched, so they were dropped, since GMP's own test suite owns
// that. Not part of the library build; run via `make fuzz` (builds with
// -DFUZZ_MAIN and ASan/UBSan).
#ifdef FUZZ_MAIN
static long g_total = 0, g_failed = 0;
// Full-width random 64-bit limb (rand() yields only ~31 bits, so stitch
// four draws). This matters so the fuzz checks actually exercise the high
// half of each limb, not just the low 32 bits.
static bi_limb rand_limb(void) {
return (bi_limb)(uint32_t)rand() | ((bi_limb)(uint32_t)rand() << 16) | ((bi_limb)(uint32_t)rand() << 32)
| ((bi_limb)(uint32_t)rand() << 48);
}
static bigint *bi_rand(uint32_t max_limbs) {
uint32_t len = max_limbs == 0 ? 0 : 1u + (uint32_t)(rand() % (int)max_limbs);
bigint *b = bi_new();
for (uint32_t i = 0; i < len; i++) { // most-significant limb first
mpz_mul_2exp(b, b, GMP_LIMB_BITS);
mpz_add_ui(b, b, rand_limb());
}
return b;
}
static void bi_print(const char *label, const bigint *b) {
char *s = bi_to_decimal(b);
fprintf(stderr, "%s = %s (limbs=%zu)\n", label, s, mpz_size(b));
}
// Records a check's outcome, printing a,b (and any extra bigints) on
// failure. `extra` is a NULL-terminated array of (label, bigint*) pairs.
static bool report(bool ok, const char *tag, const bigint *a, const bigint *b, ...) {
g_total++;
if (ok) return true;
g_failed++;
fprintf(stderr, "MISMATCH (%s):\n", tag);
bi_print(" a", a);
if (b) bi_print(" b", b);
va_list ap;
va_start(ap, b);
for (;;) {
const char *label = va_arg(ap, const char *);
if (!label) break;
bi_print(label, va_arg(ap, const bigint *));
}
va_end(ap);
return false;
}
// ---- GCD: cross-checked against a plain Euclidean gcd built on bi_divmod,
// simple enough to trust directly and, crucially, an algorithm distinct
// from mpz_gcd's HGCD, so it is a real independent oracle.
static bigint *fuzz_gcd_ref(const bigint *a, const bigint *b) {
bigint *x = bi_copy(a), *y = bi_copy(b);
while (!bi_is_zero(y)) {
bigint *q, *r;
bi_divmod(x, y, &q, &r); // x mod y (bi_divmod handles x < y)
bi_free(q);
bi_free(x);
x = y;
y = r;
}
bi_free(y);
return x; // gcd in x; zero-operand cases fall out correctly (gcd(a,0)=a)
}
static bool gcd_check(const bigint *a, const bigint *b, const char *tag) {
bigint *expect = fuzz_gcd_ref(a, b);
bigint *got = bi_new();
mpz_gcd(got, a, b);
bool ok = report(bi_cmp(expect, got) == 0, tag, a, b, " expect (euclid)", expect, " got (hybrid)", got,
(const char *)NULL);
bi_free(expect);
bi_free(got);
return ok;
}
static void run_gcd_fuzz(void) {
for (uint32_t max_limbs = 1; max_limbs <= 80; max_limbs++) {
for (int trial = 0; trial < 300; trial++) {
bigint *a = bi_rand(max_limbs);
bigint *b = bi_rand(max_limbs);
gcd_check(a, b, "gcd-random");
bi_free(a);
bi_free(b);
}
}
// Dense coverage of small limb counts, where word-size boundary bugs
// would hide.
for (uint32_t limbs = 1; limbs <= 15; limbs++) {
for (int trial = 0; trial < 3000; trial++) {
bigint *a = bi_new(), *b = bi_new();
for (uint32_t i = 0; i < limbs; i++) {
mpz_mul_2exp(a, a, GMP_LIMB_BITS);
mpz_add_ui(a, a, ((uint32_t)rand() << 16) ^ (uint32_t)rand());
mpz_mul_2exp(b, b, GMP_LIMB_BITS);
mpz_add_ui(b, b, ((uint32_t)rand() << 16) ^ (uint32_t)rand());
}
gcd_check(a, b, "gcd-boundary");
bi_free(a);
bi_free(b);
}
}
// Adversarial: consecutive Fibonacci-like numbers are the classic
// Euclidean-algorithm worst case (every quotient is 1).
{
bigint *f0 = bi_from_u64(1), *f1 = bi_from_u64(1);
for (int i = 0; i < 400; i++) {
gcd_check(f1, f0, "gcd-fibonacci");
bigint *next = bi_add(f0, f1);
bi_free(f0);
f0 = f1;
f1 = next;
}
bi_free(f0);
bi_free(f1);
}
// Adversarial: equal values, one a multiple of the other, coprime-ish
// (differ by 1), and powers of two with various offsets.
for (uint32_t max_limbs = 1; max_limbs <= 60; max_limbs += 2) {
bigint *a = bi_rand(max_limbs);
if (bi_is_zero(a)) {
bi_free(a);
continue;
}
gcd_check(a, a, "gcd-equal");
bigint *m = bi_mul_u32(a, (uint32_t)(2 + rand() % 50));
gcd_check(m, a, "gcd-multiple");
bi_free(m);
bigint *one = bi_from_u64(1);
bigint *aplus1 = bi_add(a, one);
gcd_check(aplus1, a, "gcd-differ-by-1");
bi_free(one);
bi_free(aplus1);
bi_free(a);
}
for (uint32_t bits = 1; bits <= 2000; bits += 17) {
bigint *one = bi_from_u64(1);
bigint *pa = bi_shl(one, bits);
bigint *pb = bi_shl(one, bits > 11 ? bits - 11 : 1);
gcd_check(pa, pb, "gcd-powers-of-two");
bi_free(one);
bi_free(pa);
bi_free(pb);
}
// Zero / tiny edge cases.
{
bigint *zero = bi_new();
bigint *five = bi_from_u64(5);
gcd_check(zero, five, "gcd-zero-a");
gcd_check(five, zero, "gcd-zero-b");
bi_free(zero);
bi_free(five);
}
// Large-operand stress test, well beyond anything the benchmarks/
// workloads actually reach, to catch any scale-dependent issue.
for (int trial = 0; trial < 200; trial++) {
bigint *a = bi_rand(600), *b = bi_rand(600);
gcd_check(a, b, "gcd-large");
bi_free(a);
bi_free(b);
}
}
// ---- number-level round trips through the public API: exercises
// canon_make, number_addsub, number_mul, and number_div together, on top
// of the bigint-layer checks above.
static void run_number_fuzz(void) {
for (int trial = 0; trial < 20000; trial++) {
int64_t an = (int64_t)(rand() - RAND_MAX / 2), ad = 1 + rand() % 100000;
int64_t bn = (int64_t)(rand() - RAND_MAX / 2), bd = 1 + rand() % 100000;
Num_t a = number_from_ratio(an, ad);
Num_t b = number_from_ratio(bn, bd);
if (!number_is_zero(b)) {
Num_t q = number_div(a, b);
Num_t back = number_mul(q, b);
g_total++;
if (number_compare(back, a) != 0) {
g_failed++;
fprintf(stderr, "MISMATCH (number-div-mul-inverse): a=%lld/%lld b=%lld/%lld\n", (long long)an,
(long long)ad, (long long)bn, (long long)bd);
}
}
Num_t sum = number_add(a, b);
Num_t back2 = number_sub(sum, b);
g_total++;
if (number_compare(back2, a) != 0) {
g_failed++;
fprintf(stderr, "MISMATCH (number-add-sub-inverse): a=%lld/%lld b=%lld/%lld\n", (long long)an,
(long long)ad, (long long)bn, (long long)bd);
}
}
}
int main(void) {
srand(67890);
run_gcd_fuzz();
run_number_fuzz();
fprintf(stderr, "%ld/%ld passed (%ld failed)\n", g_total - g_failed, g_total, g_failed);
return g_failed == 0 ? 0 : 1;
}
#endif // FUZZ_MAIN
|