I concede that my Python version is not fully general. I should have put in an arg for the result type, Fraction. The C++ version is fully general, however, since it is a template function.

I used rather simple pivoting, checking for being nonzero, but for numerical work, one needs the highest magnitude. What I did was OK, however, since I was doing exact calculations, not approximate numerical ones.

Wikipedia has a lot of articles on linear algebra, like:

Nicely done Ipetrich, as always . I worked through multiplication and I am still wading through determinants.
For finding matrix inverses, I used Gaussian elimination with pivoting. I started with (original matrix, identity matrix) and I ended up with (identity matrix, inverse matrix).

Pivoting is a way of avoiding dividing by zero or small numbers. Instead of selecting the next row as one proceeds, one looks for the best row for the next step and then makes it the next row.

An often-recommended algorithm, LU decomposition, is essentially Gaussian elimination with construction of a matrix in place of the original one for quick calculation of linear-equation solutions. Gauss/LU requires O(n3) calculations for size n, but LU makes two triangular matrices that require only O(n2) operations for a solution.

LU refers to lower-triangular and upper-triangular matrices, and these matrices are easy to invert.

But I didn't bother with LU, because I wanted less code complexity.

Turning to determinants, they are easy to compute with Gaussian elimination. Pivoting makes sign flips, so that is easy to take into account.

Put it all into a static library or dll and you have the beginnings of a native open source C++ math library.

The major math libraries seem to be legacy FORTRAN.

I've found
Boost C++ Libraries
with
Boost Basic Linear Algebra - 1.82.0
among numerous other C++ libraries.

If you want to take your chances with Github's contributors, search GitHub
Back in the 90s there was a book and disk Numeral Recopies In C. It was notoriously buggy.

Intel's math library is free. MS Visual Studio supports it as an option. But its in FOTRAN so its takes some work to use it from C.

Array addition and subtraction are straightforward. There is o need to access arrays by row and column.

For multiplication the number of columns of a must equal the number of rows of b. See the link.

Mult unctions with and without pointers.

Code:
void mprint(char *s,int n,int m,double *x){
int i,col,row;
for(i = 0;i < n;i++){
row = i*m;
for(col=0;col<m;col++)printf(s,x[row+col]);
printf("\n");
}
printf("\n\n");
}//mprintf()

#define SUB 1
#define MUL 2
#define DIV 3

int mat_add_sub(int op,int n,double *x,double *y,double *s){
int i;
switch(op){
case ADD:for(i=0;i<n*n;i++)s[i] = x[i] + y[i];break;
case SUB:for(i=0;i<n*n;i++)s[i] = x[i] - y[i];break;
default:return 1;
}
return 0;

int mat_mult(int arows,int acols,int brows, int bcols,
int crows,int ccols, double *a,double *b,double *c){
if(acols != brows)return 1;
int i,j,k,arow,brow,crow;
for(i = 0;i<crows*ccols;i++)c[i] = 0.;
for(i=0;i<arows;i++){
crow = i*ccols;
arow = i*acols;
for(j=0;j<bcols;j++){
for(k=0;k<acols;k++){
//c[i][j] += a[i][k]*b[k][j];
brow = k*bcols;
c[crow + j] += a[arow + k]*b[brow + j];
}//k
}//j
}//i
return 0;
}// mat_mult()

void mat_multp(int arows,int acols,int brows, int bcols,
int crows,int ccols, double *a,double *b,double *c){
int i,j,k;
for(i = 0;i<crows*ccols;i++)c[i] = 0.;
//arrays of pointers to array rows
double *crow[crows];for(i=0;i<crows;i++)crow[i] = &c[i*ccols];
double *brow[brows];for(i=0;i<brows;i++)brow[i] = &b[i*bcols];
double *arow[arows];for(i=0;i<arows;i++)arow[i] = &a[i*acols];

for(i=0;i<arows;i++){
for(j=0;j<bcols;j++)
for(k=0;k<acols;k++)
*(crow[i]+j) += *(arow[i]+k) * *(brow[k]+j);
}//i

}// mat_multp()

int main(){

int crow = 2,ccol = 3,arow = 2,acol = 2,brow = 2,bcol = 3;
double cc[crow][ccol];
double aa[arow][acol] = {{1,2},{3,4}};
double bb[brow][bcol] = {{1,2,3},{3,4,5}};
//mat_mult(arow,acol,brow,bcol,crow,ccol,&aa[0][0],&bb[0][0],&cc[0][0]);
mat_multp(arow,acol,brow,bcol,crow,ccol,&aa[0][0],&bb[0][0],&cc[0][0]);

mprint(fmt,arow,acol,&aa[0][0]);
mprint(fmt,brow,bcol,&bb[0][0]);
mprint(fmt,crow,ccol,&cc[0][0]);

return 0;
}

LINPAK has been around for a long time.

The Intel libraries. Intel also has a free C/C++ compiler.

In the 80s I went through the books and wrote my own math routines. Then I went to Mathcad in the 90s. When it would no longer install on Windows I went to Scilab as my math tool.

If I were doing serious work I'd use MS Visual Studio and a math library.

Tools like Scilab and Matlab are very good, but are very slow as size grows. You can write C code and call it from math tools.

I puzzled over how to generate random numbers from a distribution. Did not have enough theory so I went through it.

The quantile of distribution yields a variable given the mean,standard deviation, and probability of the point. The derivation ofthe normal distribution quantile is in the link.

The error function erf() is easy to calculate with a simple integration. I haven't gotten the inverse error function working so the code is in Scilab not C.

Note that what appears to be erf^-1 is the inverse error function not 1/erf().

The function and the Sclab builtn do not generate the exact numbers but the curves are close. Sckab randomizes both the numbers and the mean value.

Code:
clear
clc

function [sum] = erfx(x)
//error function
n = 200
dt = x/n
sum = 0
t = 0
for i = 1:n
sum = sum + (%e^(-t*t))*dt
t = t  + dt
end
sum = sum  *2/sqrt(%pi)
endfunction

function [var] = norm_var(u,sigma,n)
//genrates a rndom array from a normal distribution
//uniform probabilities >0 -> <=1
p = grand(n,1,"unf",.0001,1.)
for i = 1:n-1
var(i) = u + erfinv(2*p(i) -1)*sqrt(2)*sigma
end
endfunction

u = 100
sigma = 3
n = 1000
[var] = norm_var(u,sigma,n)
vars = gsort(var,"g","i")
y= grand(n,1,"nor",u,sigma)
ys = gsort(y,"g","i")

w2 = scf(2)
clf(w2)
subplot(1,2,1)
plot2d(ys)
plot2d(vars)
xgrid
subplot(1,2,2)
histplot(10,var)
xgrid

https://en.wikipedia.org/wiki/Error_functionnormal

The inverse error function is required to generate normal random variables. I scnned several papers from the early 70s. It appears appropriating the error function and inverse error functions was a hot topic. A lot of work on solutions and approximations. Back in the day when people still used slide rules, paper, and pencil for calculations.

I coded the the inverse algorithm from the wiki page. I don't think there is an integral for erf() so it is ether an approximation or a numeral integration.

As inverses erf(erfinv(x)) = x

for x = .1,.2,.3 … .9 erf(erfinv(x))

Scilab functions
0.100002
0.200016
0.300055
0.400134
0.500273
0.600500
0.700858
0.801430
0.902431

My functions
0.100002
0.200016
0.300055
0.400134
0.500273
0.600500
0.700858
0.801429
0.902223

For 20 series terms in erf() and the integral resolution in erfinv() of 200 the results are almost identical.

erfinv() has the random number generator and errinv() functions built in. That eliminates function call time delays in repetitive iterations. Rather than iterate the series coefficients on each call the coefficients are hard coded into erfinv();

If you sort ascending and plot the normal variable array generated you will get the characteristic S curve of the normal distribution. Plot a histogram and it will be normal.

Using URAD I increased the max random number from the rand() 16 bits to 32 bits. This had the effect of increasing lower value decimals and improving the exponential distribution tail which asymptotically goes to zero.

Code:
int comp_doub_ascend(const void * a, const void * b) {
if (*(double*)a > *(double*)b) return 1;
else if (*(double*)a < *(double*)b) return -1;
else return 0;
}

double erfinv(double x){
int k,m,n = 20;
double s = 0,y = 0,q;
double c[n];
c[0] = 1;
for(k=1;k<n;k++){
s = 0;
for(m=0;m<k;m++)s += (c[m]*c[k-1-m])/((m+1)*(2*m+1));
c[k] = s;
}//k
q = sqrt(_PI)*x/2.;
for(k=0;k<n;k++) y += ((c[k])/(2*k+1))*pow(q,2*k+1);
return y;
}//erfinv()

double erf(double x){
int i,n = 200;
double t = 0,s = 0,dt = x/double(n);
for(i=0;i<n;i++){
s += exp(-t*t)*dt;
t += dt;
}
return s * 2./sqrt(_PI);
}//erf()

void exp_vars(int n,double *y,double u,unsigned long long seed){
// array of random numbers from an exonentual distribution
// URAND random number algorithm
// p represents random probabilities >0 <1
double p;
int i;
static unsigned long long s = 0,IY = 1;
if(s != seed){IY = seed; s = seed;}
unsigned long long m  = pow(2,32)-1;
unsigned long long  IC = 2*floor(m*(.5 - sqrt(3)/6.)) + 1;
unsigned long long  IA = 8*m*ceil(atan(1.)/8.) + 5;
IY = IY * IA + IC;
for(i=0;i<n;i++){
p = (double)(IY%(m+1))/(double)(m+1);
IY = IY * IA + IC;
// quantile - probability to  variable for a given mean
y[i] = -1.* log(1. - p) * u;
}
}//exp_vars()

void norm_vars(long long n,double *y,double u,double sigma,unsigned long long seed){
// array of random numbers from a normal distribution
// URAND random number algorithm
// p represents random probabilities >0 <1
double q,p,einv,k1;
int i,k,nc = 20;
static unsigned long long s = 0,IY = 1;
if(s != seed){IY = seed; s = seed;}
// random number gnerator constants
unsigned long long  m  = pow(2,32);
unsigned long long  IC = 2*floor(m*(.5 - sqrt(3)/6.)) + 1;
unsigned long long  IA = 8*m*ceil(atan(1.)/8.) + 5;
IY = IY * IA + IC; //first random number

//inverse error function coefficients
double c[nc] =
{1.,1.,1.16667,1.41111,1.73373,2.14858,
2.67717,3.34815,4.9149,5.27542,6.639,
8.3655,10.5518,13.3208,16.8285,21.2729,
26.9053,34.0446,43.0957,54.5727};

k1 = sqrt(2)*sigma;
for(i=0;i<n;i++){
//probability >0 < 1
p = (double)(IY%(m+1))/(double)(m+1);
IY = IY * IA + IC; //next random number
// inverse error function
p = 2.*p-1.;
q = sqrt(_PI)*p/2.;
einv = 0.;
for(k=0;k<nc;k++) einv += ((c[k])/(2*k+1))*pow(q,2*k+1);
//normal distribution quantile
y[i] = u + einv*k1;
}
}//norm_vars()

int save(int n,double x[]){
FILE *ptr = fopen("data.txt","w");
if(!ptr)return(1);
fprintf(ptr,"%d\n",n);
for(int i = 0;i <n;i++)
fprintf(ptr,"%.8f\n",x[i]);
fclose(ptr);
cout<<"SAVE DONE"<<endl;
return(0);
}//save()

void ave(int n,double y[],double *av,double *std){
// mean and standard deviation
int i;
double asum = 0, ssum = 0;
for(i=0;i<n;i++)asum += y[i];
*av = asum/double(n);
for(i=0;i<n;i++)ssum += pow(*av-y[i],2);
*std = sqrt(ssum/double(n));
}//ave()

double min(int n,double y[]){
int i;
double miny = y[0];
for(i=0;i<n;i++)if(y[i]<miny)miny = y[i];
return miny;
}//min()

double max(int n,double y[]){
int i;
double maxy = y[0];
for(i=0;i<n;i++)if(y[i]>maxy)maxy = y[i];
return maxy;
}//max()

int main() {
unsigned seed =  (time(NULL));
srand(seed);
int i, n = 1000;
double u = 10.,av,std = 3.,lo,hi;
double y[n];// = new double[n];
//exp_vars(n,y,u,seed);
norm_vars(n,y,u,std,seed);
ave(n,y,&av,&std);
qsort(y, n, sizeof(double), comp_doub_ascend);
//for(i=0;i<n;i++)printf("%10.6f\n",y[i]);
save(n,y);
lo = min(n,y);
hi = max(n,y);
printf("min  %10.4f \tmax %10.4f\n",lo,hi);
printf("ave  %10.4f \tstd %10.4f\n",av,std);
double z1,z2;
for(i=1;i<10;i++){
z1 = double(i)/10.;
z2 = erf(erfinv(z1));
printf("%.6f   %.6f\n",z1,z2);
}

return(0);
}//main()

Fond a problem in the distribution functions I did not account for.

The IY value can overflow if the function is called enough times especially if started with a high seed value If IY the random number equals the max value of an unassigned long long int reseed and restart the sequence.

This sgould be inserted in the distribution functions.

unsigned long long IYMAX = pow(2,64)-1;
if(IY == IYMAX)IY = seed;

Code reuse and libraries.

Creating a library is easy.

A project can unly have one main(). A blank .c or .cpp file to your project, Put yiur code in the fike. IDEs will usually automatically compile it to an obj file and link it to the main() code.

To use the code use an extern reference.

If double foo(double a,double b){ } is in the file add extern double foo(double,double) above main();

or use an include file.

foo.h
extern double foo(double,double);

If you want to use foo() in a project add foo.o or foo.obj to the project file list and the foo.h to the code.

The standard c libs are just compiled code for which you add include files to use.

When writing a large program as you get code working you can move it over to another file in the project to reduce clutter in your main() file. On larger programs putting debugged code into .obj files and linking gets rid of having to rebuild the entire project when writing code.

My random number object. Compiled to a .obj I can easily use it writing code.

Code:
class URAND{
private:
unsigned long long seed = 1,rand_int = 1;
unsigned long long max_int = 0x0fffffff;
unsigned long long RANDMAX = 0xffffffffffffffff;
unsigned long long k1 = 1701812597,k2 = 32212254717;
int flag = 1;
void next_num(void){
if(rand_int == RANDMAX)rand_int = seed;
rand_int = rand_int * k2 + k1;
}
public:
int init(unsigned long long xseed);
int rint(unsigned long long sf,unsigned long long os);
double rdec(void);
int rdeca(unsigned long long n,double *y);
int rinta(unsigned long long n,int *y,unsigned long long sf,unsigned long long os);
double rdoub(unsigned long long sf,unsigned long long os);
int rdouba(unsigned long long n,double *y,unsigned long long sf,unsigned long long os);
};//URAND

int URAND::init(unsigned long long xseed){
if(xseed < 1 || xseed >= RANDMAX){flag = 1;return 1;}
flag = 0;
seed = xseed;
rand_int = seed;
return 0;
}//init()

int URAND::rint(unsigned long long sf,unsigned long long os){
//single integer
if(flag)return -1;
next_num();
if(sf>1)return (rand_int%max_int)%sf +os;
return rand_int%max_int;
}//rint();

double URAND::rdec(void){
// single decimal 0 - < 1
if(flag)return -1;
next_num();
return (double)(rand_int%max_int)/(double)(max_int+1);
}//rdec();

int URAND::rdeca(unsigned long long n,double *y){
// array 0 - <1 of decimals
unsigned long long i;
if(flag)return -1;
for(i=0;i<n;i++){
next_num();
y[i] = (double)(rand_int%max_int)/(double)(max_int+1);
}
return 0;
}//rdeca()

int URAND::rinta(unsigned long long n,int *y,unsigned long long sf,unsigned long long os){
// array of ints
unsigned long long i;
if(flag)return -1;
for(i=0;i<n;i++){
next_num();
if(sf>1)y[i] = (rand_int%max_int)%sf +os;
else y[i] = rand_int%max_int;
}
return 0;
}//rinta();

double URAND::rdoub(unsigned long long sf,unsigned long long os){
// single double
if(flag)return -1;
unsigned long long intg;
double dec;
next_num();
if(sf>1)intg = (rand_int%max_int)%sf +os;
else intg = rand_int%max_int;
next_num();
dec = (double)(rand_int%max_int)/(double)(max_int+1);
return double(intg) + dec;0;
}//rdoub();

int URAND::rdouba(unsigned long long n,double *y,
unsigned long long sf,unsigned long long os){
// array of doubles
if(flag)return -1;
unsigned long long intg,i;
double dec;
for(i=0;i<n;i++){
next_num();
if(sf>1)intg = (rand_int%max_int)%sf +os;
else intg = rand_int%max_int;
next_num();
dec = (double)(rand_int%max_int)/(double)(max_int+1);
y[i] = double(intg) + dec;0;
}
return 0;
}//rdouba();

Include file.

Code:
//urand.h
#pragma once

class URAND{
private:
unsigned long long seed = 1,rand_int = 1;
unsigned long long max_int = 0x0fffffff;
unsigned long long RANDMAX = 0xffffffffffffffff;
unsigned long long k1 = 1701812597,k2 = 32212254717;
int flag = 1;
void next_num(void){
if(rand_int == RANDMAX)rand_int = seed;
rand_int = rand_int * k2 + k1;
}
public:
int init(unsigned long long xseed);
int rint(unsigned long long sf,unsigned long long os);
double rdec(void);
int rdeca(unsigned long long n,double *y);
int rinta(unsigned long long n,int *y,unsigned long long sf,unsigned long long os);
double rdoub(unsigned long long sf,unsigned long long os);
int rdouba(unsigned long long n,double *y,unsigned long long sf,unsigned long long os);

};//URAND

Code:
#include "urand.h"

int main(){
cout<<"RUNNING"<<endl;
URAND rn;
char f1[20] = "dec.txt";
char f2[20] = "int.txt";
unsigned seed = unsigned (time(NULL));
rn.init(seed);
unsigned long int  i, nbins = 20;
unsigned long long n = (unsigned long)pow(10,3);
cout<<n<<endl;
int *yd = new int[n];
double *yf = new double[n];
int bins1[nbins],bins2[nbins];
double limits[nbins+1];
int mini =200,maxi  = 0;
double minf,maxf;
double yfx;int ydx;
rn.rdeca(n,yf);
rn.rdouba(n,yf,10,0);
rn.rinta(n,yd,10,50);
cout<<"LOOP DONE"<<endl;
for(i=0;i<100;i++)printf("%5.5f\n",yf[i]);

Fond a problem in the distribution functions I did not account for.

The IY value can overflow if the function is called enough times especially if started with a high seed value If IY the random number equals the max value of an unassigned long long int reseed and restart the sequence.

This sgould be inserted in the distribution functions.

unsigned long long IYMAX = pow(2,64)-1;
if(IY == IYMAX)IY = seed;
I don't see how this would prevent overflow, because IY could easily overflow even if it is less than IYMAX. And overflow is probably not a problem. The extra bits are just ignored and IY loops around.

Good question.

The compiler generates an overflow warning if I exceed the size of a variable.

I thought about just letting it run, but my policy based on experience is 'an ounce of prevention is worth a pound of cure'.

Now that you raised the question I'll go back and se what happens.

What I think will happen is the IY or random number out will max out and stay there as the generator iterates.

I used the variable names from the FOTRAN code in the URAND paper I linked to earlier. IY is the random integer.

Now that I think of it it was incorrect to say overflow. One IY reaches max number the algorithm can not generate a higher number.

The limimg term is the multiplication in IY = IY*k1 + k2. For this algorithm it sets the max random numer for a given variable size.

Using a high value of seed I did check that the sequence restarts when IY maxes out.

.

I had to retrace my thinking. Should have put it in the posts.

An 8x8 bit multiply has in a 16 bit result.

I used a 64 bit long long int to increase the max random number from rand(). To avoid possible overflow of the 64 bit rand_int I limited the max random number to <32 bits as the max number in the equations for k1 and k2 . I did not work out the max possible rand_int for 64 bits based on rand_int = rand_int+int*k1 + k2.

RANDMAX was added to prevent the results of rand_int * k1 + k2 causing a 64 bit overflow. I did not consider it would roll over to zero. It did not come to mind the ints are up-down counters

int main(){
unsigned long long x =(unsigned long long)(pow(2,64)-1);
int i;
for(i=0;i<10;i++){
printf("%llx\n",x);
x = x + 1;
}
}

results in
0xffffffffffffffff
0
1
2
3
4
5
6
7
8

so, it should work fine without resetting the sequence. It would take a lot of iterations to test it. The numbers are random not linear, so the probability of the max number would be low.

Good catch. At least one person is reading my code....

AlphaDev uncovered new sorting algorithms that led to improvements in the LLVM libc++ sorting library that were up to 70% faster for shorter sequences and about 1.7% faster for sequences exceeding 250,000 elements.

Faster sorting algorithms discovered using deep reinforcement learning | Nature

The algorithms were programmed in a sort of assembly language.

Assembly language dates back to the early 1950's, and it is a thin layer of abstraction atop a CPU's workings. An assembly-language instruction has the form

(label) (opcode) (operands)

The label is optional, "opcode" is short for "operation code", and there can be zero or one operands. Labels are used for destinations of transfer of control, and also for data locations. For the latter, one uses assembly instructions for declaring data, though these instructions are not translated into CPU instructions.

High-level languages proved more convenient than assembly language, but assembly language is still used for using special features of CPU's and for certain optimizations.

AlphaDev worked with a simplified assembly language:

mov a b -- move a to b
cmp a b -- compare a to b
cmovX a b -- if condition X is satisfied, move a to be
In that paper's code samples, X is either l (less than) or g (greater than) -- cmovl, cmovg

The software then experimented with sequences of instructions until it found the best one that gives correct results. For n values to sort, that means n! cases to check, but that is not very large for the small n values used here. The theoretical limit of the number of operations is some multiple of n*log(n), and I compare to n*log(2,n). For "Sort n":

Sort 1000-
Sort 23301.5
Sort 3171813.58
Sort 4282803.5
Sort 5424643.62

The paper's authors also tried 6, 7, and 8 values, giving:

1: 0 -- 2: 0 -- 3: 1 -- 4: 0 -- 5:1 -- 6: 3 -- 7: 2 -- 8: 1

I've added 1 (no operations), and 2 (compare, move if greater, move if less) for completeness.

They also tested the hypothesis that 17 instructions is the absolute minimum for 3 values to sort, so they checked every possible program with length 16 or less, going through 1032 programs, and taking 3 days. They found none, so 17 is the minimum. This approach is obviously too difficult to generalize for more variable values.

The authors also considered using different lengths together, and they found a Sort 4 that uses Sort 3 as a partial result.

VarSort 2330
VarSort 32133121830
VarSort 43766291633
VarSort 563115522649

The authors also worked on optimizing hashing algorithms. These algorithms prepare scrambled summaries of data, and they are useful in authentication and high-speed searching.

For example, passwords are often stored in hashed form, because that guarantees their security unless a password cracker has extreme brute force available. To check a user's entered password, the software uses that hash algorithm and then compares this hashed version to the hashed stored password.

The instruction set for the old 6502. First processor I learned.

The complex PC processors do the same basic functions.

Finding primes in assembly language.

Bakus Naur Form is used to define instructions.

In computer science, Backus–Naur form (/ˌbækəs ˈnaʊər/) or Backus normal form (BNF) is a metasyntax notation for context-free grammars, often used to describe the syntax of languages used in computing, such as computer programming languages, document formats, instruction sets and communication protocols. It is applied wherever exact descriptions of languages are needed: for instance, in official language specifications, in manuals, and in textbooks on programming language theory.

Many extensions and variants of the original Backus–Naur notation are used; some are exactly defined, including extended Backus–Naur form (EBNF) and augmented Backus–Naur form (ABNF).

I think this is the first book I read on processors. It will take you through a very basic prcesoor that I think is still around. An easy read if you already know programming.

My first computer in 1979 was the SYM 1 with a 6502 processor. It had a simple monitor program that allowed you to read and write hex values into memory. It had a 7 segment displays.

You would write out the code and look up the binary numbers for the instruction in hex, and enter the sequence into memory in hex with a keypad. That was called manual assembly.

The price back then for that simple board was equivalent to the price of a PC today. That is one off the positives of free market competition.

All processors are basically the same. If you go through the book you will undestand how a PC works.

6502 Assembly Language Programming Paperback – Import, January 1, 1979

I puzzled over how my Scilab tool creates uniform int and float distributions with negative endpoints.

Find the number of ints to be created from lo to high. Add lo to rand()nnums, 0-nnums -1.

rand() can be used but the URAND algorithm has a max number equal to a signed int.

The precision of the float can be adjusted.

Code:
class UNI_DIST{
private:
unsigned long long randll = 1;
unsigned long long m = 0x7fffffff;
unsigned long long k1 = 2*floor(m*(.5 - sqrt(3)/6.)) + 1;
unsigned long long k2 = 8*ceil(m*atan(1.)/8.) + 5;

public:
void init(unsigned long long seed);
int uni_float(int n,double *y,int lo,int hi,int ndp);
int uni_int(int n,int *y,int lo,int hi);
};//UNI_DIST

void  UNI_DIST::init(unsigned long long seed){
randll = seed;
}//init()

int UNI_DIST::uni_float(int n,double *y,int lo,int hi,int ndp){
//uniform distribution floats > lo < hi + 1
if(lo >= hi || n <= 0)return -1;
int xint,i,nnums = (hi-lo),dp = pow(10,ndp);
double xdec;
for(i=0;i<n;i++){
randll = randll*k2 + k1;;
xdec = (double)(randll%m)/(double)(m+1);
if(ndp>0){
xdec = xdec*dp;
xdec = int(xdec);
xdec = xdec/dp;
}
randll = randll*k2 + k1;
xint = (randll%m)%nnums + lo;
y[i] = xint + xdec;
}
return 0;
}//uni_float()

int UNI_DIST::uni_int(int n,int *y,int lo,int hi){
//uniform distribution ints lo -> hi-1
if(lo >= hi || n <= 0)return -1;
int i,nnums = hi-lo;
for(i=0;i<n;i++){
randll = randll*k2 + k1;
if(hi < 2)y[i] = (randll%m)%nnums;
y[i] = (randll%m)%nnums + lo;
}
return 0;
}//uni_int()

void minmaxi(int n,int *y,int *min,int *max){
*max = y[0],*min = y[0];
//printf("%d   %d\n\n",y[0],y[n-1]);
int i;
for(i=0;i<n;i++){
if(y[i] < *min)*min = y[i];
if(y[i] > *max)*max = y[i];
}
}

void minmaxf(int n,double *y,double *min,double *max){
*max = y[0],*min = y[0];
//printf("%d   %d\n\n",y[0],y[n-1]);
int i;
for(i=0;i<n;i++){
if(y[i] < *min)*min = y[i];
if(y[i] > *max)*max = y[i];
}
}

int main() {
UNI_DIST ud;
unsigned long long seed = unsigned (time(NULL));
ud.init(seed);
int i,n = 100;
int mini,maxi;
double minf,maxf;
int *yi = new int[n];
double *yf = new double[n];
ud.uni_int(n,yi,-3,10);
ud.uni_float(n,yf,-3,10,4);
for(i=0;i<n;i++)printf("%4d  %20.15f\n",yi[i],yf[i]);
minmaxi(n,yi,&mini,&maxi);
minmaxf(n,yf,&minf,&maxf);
printf("int %10d   %10d\n",mini,maxi);
printf("float %20.15f   %20.15f\n",minf,maxf);
return(0);
}//main()

An alternative to manually entering gcc compiler commands.

A simple command shell using system() to run batch files to call the compiler and execute the code.
System() runs the cmd.exe DOS shell.

For a small project to chew on add a project file with a list of project names and paths, and change the path names in the code for a selection.

Compiler output can be redirected to a text file. If your editor displays line numbers compiler error line numbers should match up with source file line numbers..

In folder c:\test

test.cpp
int main(){
cout<<"TEST"<<endl;
return 0;
}
Change compiler path if needed.
compile.cmd
c:\MinGW\bin\g++.exe -Wall -std=c++17 -fexceptions -g -g -s -c C:\test\test.cpp -o c:\test\test.o
c:\MinGW\bin\g++.exe -o c:\test.exe c:\test\test.o
pause
exit

Code:
int main(int argc, char * argv[])
{
char c;
char compile[80] = "c:\\test\\compile.cmd > log.txt";
char run[80] = "c:\\test\\test.exe";
while(1){
system("cls"); //clear screen
cout<<"1 Compile "<<endl;
cout<<"2 Run     "<<endl;
cout<<"3 Exit    "<<endl;
cout<<"Enter--  ";
c = getchar();cout<<endl;
switch(c){
case '1': system(compile);break;
case '2': system(run);system("pause");break;
case '3': return(0);break;
default:break;
}
}
return 0;

}