TweetFollow Us on Twitter

May 00 Challenge

Volume Number: 16 (2000)
Issue Number: 5
Column Tag: Programmer's Challenge

Programmer's Challenge

by Bob Boonstra, Westford, MA

BigNum Math

Back in September, 1995, we conducted an RSA Challenge that involved raising large integers to integral powers, modulo a third integer. The representation we used for those large integers was a BigNum type, where each digit of the large integer was stored in a byte. That representation and the operations on it were not particularly efficient, and this month we will belatedly recitfy that situation. Your Challenge is to implement a new BigNum type, of your own design, along with a number of arithmetic operations on these BigNums..

The prototype for the code you should write is:

typedef struct BigNum {
	long lengthInDigits;	/* length of the BigNum in digits */
	void *bigNumData;			/* pointer to BigNum data */
} BigNum;

BigNum NewBigNum (			/* create a BigNum */
	char sign,						/* +1 or -1 */
	char digits[],				/* digits to be made into a BigNum */
	long numDigits				/* number of digits */
);

void DisposeBigNum (		/* dispose of a BigNum */
	BigNum theBigNum			/* the BigNum to be disposed of */
);

BigNum AddBigNums (			/* sum two BigNums, returning a new one */
	BigNum bigNumA,				/* return the sum A+B */
	BigNum bigNumB
);

BigNum SubtractBigNums (	/* subtract two BigNums, returning a new one */
	BigNum bigNumA,				/* return the difference A-B */
	BigNum bigNumB
);

BigNum MultiplyBigNums (	/* multiply two BigNums, returning a new one */
	BigNum bigNumA,				/* return the product A*B */
	BigNum bigNumB
);

BigNum DivideBigNums (		/* divide two BigNums, returning a new one */
	BigNum bigNumA,				/* return the quotient A/B, discarding the remainder */
	BigNum bigNumB
);

BigNum ModBigNums (			/* divide two BigNums, returning a new one */
	BigNum bigNumA,				/* return the remainder A%B, discarding the quotient */
	BigNum bigNumB
);

BigNum PowerBigNums (		/* calculate one Bignum to the power of another, returning a new one */
	BigNum bigNumA,				/* return A raised to the power B, discarding the quotient */
	BigNum bigNumB
);

BigNum SqrtBigNum (			/* find the sqrt of a BigNum, returning a new one */
	BigNum bigNumA				/* return the square root of A */
);

long /* numDigits */ BigNumToDigits( /* convert a bigNum to decimal digits */
	BigNum bigNumA,				/* bigNum to be converted to decimal digits 0-9 */
	char *sign,						/* return +1 or -1 */
	char digits[]					/* decimal digits of bigNumA, preceeded by '-' if negative */
									/* storage for digits preallocated based on bigNumA.lengthInDigits */
);

The first thing you need to do is decide on an internal representation for BigNums. Then you need to write a NewBigNum routine that will create a BigNum from a sequence of numDigits digits and a sign value. Your NewBigNum code is responsible for allocating memory for the BigNumData. The DisposeBigNum routine is responsible for deallocating that memory. The caller of your code is responsible for pairing every NewBigNum call with a DisposeBigNum call, and the two routines should be implemented so as not to create any memory leaks. In addition to these allocation and deallocation routines, you need to write code to perform addition (AddBigNums), subtraction (SubtractBigNums), multiplication (MultiplyBigNums), division (DivideBigNums), remainders (ModBigNums), and exponentiation (PowerBigNums). Each of these routines takes two arguments, calculates the result, and returns the result in a new BigNum allocated by your code. Each of these returned BigNums will also be disposed of by a call to DisposeBigNum before the test is over, although they might be used for calculations in the interim.

Just to spice things up, you also need to provide a SqrtBigNum routine that calculates and returns the integer square root of a BigNum, the largest BigNum whose square is no larger than the original number.

And finally, to help me decipher your BigNums, you need to provide a BigNumToDigits conversion routine that converts your private BigNum data structure into a sequence of digits, along with a sign, and returns the number of digits in the decimal representation of the BigNum.

I'm not providing information on the distribution of calls to the various routines, except to say that the arithmetic routines will significantly outnumber the allocation and deallocation routines. The winner will be the solution that correctly completes a sequence of arithmetic operations on BigNums in the least amount of time. You are strongly encouraged to adequately comment the code in your submissions. Not only does that make your code more understandable if it is published as the winning solution, but it also helps me track down any minor problems that might occur.

I'll close with a plug for the Challenge mailing list, where you can receive notice of the problems before the hard-copy magazine reaches your mailbox, and where any post-publication clarifications are distributed. Subscription instructions can be found at www.mactech.com/progchallenge/. This will be a native PowerPC Challenge, using the CodeWarrior Pro 5 environment. Solutions may be coded in C, C++, or Pascal.

Three Months Ago Winner

The February Challenge required readers to calculate a minimal Latin square of a given order. Latin Squares are nxn arrays of integers, where each row and each column contains each integer from 1 to n exactly once. Congratulations to Willeke Rieken (The Netherlands) for coming up with the winning solution to the Latin Squares Challenge.

Eleven readers submitted entries to this Challenge, and their performance varied widely in efficiency. My test scenario was based on 28 test cases, consisting of the Latin Squares of orders 4, 5, 8, 9, 12, 13, 16, 17, 20, 21, 24, 25, 28, 29, 32, 33, 36, 37, 40, 41, 44, and 45. I selected those numbers because they formed a regular pattern that could be continued as far as the solutions would allow, and because they contained a mix of odd numbers, even numbers, perfect squares, prime numbers, and powers of two. My original intent was to test even larger numbers, but even the best solutions took too long to calculate some of the larger numbers.

Even limiting the tests to these cases, some of the solutions took a long time to execute, so I divided the tests into three sets. The first set consisted of the first ten test cases, and I ran all of the entries against that set. Three of the entries either did not complete all of the cases, or calculated a Latin Square that was larger than the squares calculated by other solutions. Three of the entries had fast execution times for the ten cases, and one more had an execution time within roughly two orders of magnitude of the best ones. So I ran the top four solutions against the next six test cases. Two of the entries completed those cases correctly, so I ran those cases against the final six test cases. The second place solution by Ernst Munter was by far the faster of the two, but unfortunately, it did not compute the minimal solution for the square of order 37. Where Ernst calculated a solution that included the following as the 28th row:

 28 27 26 25 32 31 30 35 36 37 33 34 19 20 17 21 7 8 9 5 6 4 ...

... Willeke's entry produced the following smaller value:

 28 27 26 25 32 31 30 35 36 37 33 34 19 20 17 21 7 8 9 5 6 3 ...

I decided not to disqualify solutions that produced suboptimal Latin Squares, or that failed to produce a result in a reasonable time. Instead, I ranked solutions by how many test cases they were able to complete, then how many they completed correctly, and then in order of increasing execution time. The problem statement called for the use of execution time only for correct solutions, but I felt that it was fairest to allow solutions that produced a suboptimal result to compete based on how well they did.

Willeke's algorithm takes advantage of the fact that squares whose size is a power of two can be generated with a systematic pattern of switching pairs of numbers in row n to create rows a power of 2 away from row n. He accomplishes this in his FillSquare2 routine. Squares of other sizes are filled by first filing the largest subsquare of size k (k a power of 2), filling the top right n-k square optimally, filling the diagonal, and then completing the square by trial and error. Ernst's entry makes more efficient use of information about which digits are forced into use before a particular column in a given row because the digit has already been used in subsequent columns. Ernst observes in his entry that execution time does not grow with problem size, and that problems of certain sizes (e.g., 41) take much longer to execute than one might expect based on the time required for squares of dimensions close in value.

The first table below lists, for each of the entries submitted, the final ranking based on all test cases completed, total execution time for the first ten cases, the number of test cases completed, the number completed incorrectly, and the code size, data size, and language parameters. As usual, the number in parentheses after the entrant's name is the total number of Challenge points earned in all Challenges prior to this one. The second and third tables provide the results for the remaining twelve test cases.

Note that while the top four positions in this Challenge were won by four of our top contestants in the points standing (the fifth did not compete), there are a number of new names in the list of contestants. Keep trying, folks, I know from personal experience that it takes a while to become good at this, but it is possible to knock the leaders from their perches.

Cases 1-10

Name Rank Time (msec) Completed Cases Incorrect Cases Code Size Data Size Lang
Willeke Rieken 68) 1 4.1 10 0 3976 8 C++
Ernst Munter (557) 2 2.4 10 0 3224 96 C++
Randy Boring (116) 3 3.7 10 0 3828 42 C++
Sebastian Maurer (97) 4 524.5 10 0 1336 52 C++
Claes Wihlborg 5 5271.1 10 0 2596 73 C
Bjorn Davidsson (6) 6 141740.7 10 0 2232 120 C++
Michael Lewis 7 155346.4 10 0 5112 207 C++
Paul Russell 8 1436033.6 10 0 1660 8 C
Jonny Taylor (24) 9 4.3 9 0 5788 156 C
Derek Ledbetter (4) 10 1917.3 10 2 13088 312 C++
S. S. (withdrawn) 11 2.4 7 0 592 8 C++

Cases 11-16

Name Time (msec) Completed Cases Incorrect Cases
Ernst Munter 6.1 6 0
Willeke Rieken 1968.2 6 0
Randy Boring 40604.8 3 0
Sebastian Maurer N/A 0 0

Cases 17-22

Name Time (msec) Completed Cases Incorrect Cases
Ernst Munter 3200253.1 6 1
Willeke Rieken 13013297.2 6 0

Top Contestants

Listed here are the Top Contestants for the Programmer's Challenge, including everyone who has accumulated 10 or more points during the past two years. The numbers below include points awarded over the 24 most recent contests, including points earned by this month's entrants.

Rank Name Points
1. Munter, Ernst 215
2. Saxton, Tom 139
3. Maurer, Sebastian 91
4. Rieken, Willeke 61
5. Boring, Randy 50
6. Heathcock, JG 43
7. Shearer, Rob 43
8. Taylor, Jonathan 24
9. Brown, Pat 20
9. Hostetter, Mat 20
10. Downs, Andrew 12
11. Jones, Dennis 12
12. Hart, Alan 11
13. Duga, Brady 10
14. Hewett, Kevin 10
15. Murphy, ACC 10
16. Selengut, Jared 10
17. Strout, Joe 10

There are three ways to earn points: (1) scoring in the top 5 of any Challenge, (2) being the first person to find a bug in a published winning solution or, (3) being the first person to suggest a Challenge that I use. The points you can win are:

1st place 20 points
2nd place 10 points
3rd place 7 points
4th place 4 points
5th place 2 points
finding bug 2 points
suggesting Challenge 2 points

Here is Willeke's winning Latin Squares solution:

LatinSquares.cp
Copyright © 2000
Welleke Rieken

/*
	After generating several squares a pattern emerged.
	If n is even, every second row can be generated by
	switching pairs of numbers of the row above.
	If n can be divided by 4, every third and fourth
	row can be generated by switching squares of 2 by 2
	numbers of the 2 rows above.
	Example: n = 12 is generated by generating
	n = 3 and replacing every number by a square with
	n = 4.
	Other n's are generated by generating the biggest
	power of n that fits in the square and generating
	a square of n - 2^x at the top right. This square can
	be repeated to the bottom left till the first square ends.
	the numbers in the first column are in ascending order.
	the diagonal from top rigth to bottom left is filled with n.
	Example: n = 7
	1234567
	2143675
	3412756
	4567xxx
	5x7xxxx
	67xxxxx
	7xxxxxx
	The remaining numbers are generated by trial and error.
*/

#include "LatinSquares.h"

FillSquare2
static void FillSquare2(long n, short *latinSquare,
												long theDim,
												long theStartRow, long theStartCol,
												long theStartVal, long theNrOfRows)
// n is a power of 2. fill the first row with ascending numbers
// and switch them around to generate the other rows.
{
	short *aFrom1, *aTo1, *aFrom2, *aTo2;
	long 	aValue = theStartVal + 1, aRowsDone, aMultiple;
	short	*aStartSquare = latinSquare + (theStartRow * n) + 
			theStartCol;
	
	// fill first row
	aFrom1 = aStartSquare;
	for (long aCol = 0; aCol < theDim; aCol++)
	{
		*aFrom1  = aValue;
		aValue++;
		aFrom1++;
	}
	aRowsDone = 1;
	aMultiple = 1;
	while (aRowsDone < theNrOfRows)
	{
		for (long aRow = 0; aRow < aMultiple; aRow++)
		{
			if (aRow >= theNrOfRows)
				break;
			for (long anOffset = 0; anOffset < theDim; anOffset += 
							(aMultiple * 2))
			{
				aFrom2 = aStartSquare + (aRow * n) + anOffset;
				aFrom1 = aFrom2 + aMultiple;
		aTo1 = aStartSquare + ((aMultiple + aRow) * n) + anOffset;
				aTo2 = aTo1 + aMultiple;
				for (long aCol = 0; aCol < aMultiple; aCol++)
				{
					*aTo1 = *aFrom1;
					aFrom1++;
					aTo1++;
					*aTo2 = *aFrom2;
					aFrom2++;
					aTo2++;
				}
			}
		}
		aRowsDone += aMultiple;
		aMultiple <<= 1;
	}
}

CopySquare
static inline void CopySquare(long n, short *theFrom, short *theTo,
															long theDim)
{
// copy a square of size theDim from theFrom to theTo
	short *aFrom, *aTo;
	
	for (long aRow = 0; aRow < theDim; aRow++)
	{
		aFrom = theFrom + (aRow * n);
		aTo = theTo + (aRow * n);
		for (long aCol = 0; aCol < theDim; aCol++)
		{
			*aTo = *aFrom;
			aFrom++;
			aTo++;
		}
	}
}

CantFillRow
static short CantFillRow(long theDim, short *theValInCol,
													short *theValInRow, long theCol,
													long *theValue)
// check if there are numbers that can't be placed and if there
// are enough columns for the bigger numbers
{
	long	aGreaterPlacesNeeded = 0;
	short	aValOK = 0;
	for (long i = *theValue + 1; i < theDim; i++)
		if (!theValInRow[i])
		{
			aGreaterPlacesNeeded++;
			aValOK = 0;
			for (long j = theCol + 1; j < theDim; j++)
				if (!theValInCol[j * theDim + i])
				{
					aValOK = 1;
					break;
				}
			if (!aValOK)
			{
				*theValue = i - 1;
				return 1;
			}
		}
	for (long j = theCol + 1; j < theDim; j++)
	{
		aValOK = 0;
		for (long i = *theValue + 1; i < theDim; i++)
			if (!(theValInRow[i] || theValInCol[j * theDim + i]))
			{
				aValOK = 1;
				break;
			}
		if (aValOK)
			aGreaterPlacesNeeded-;
	}
	if (aGreaterPlacesNeeded > 0)
		return 1;
	return 0;	
}

CompleteSquare
static void CompleteSquare(long n, short *latinSquare,
						long theDim, long theSubDim,
						long theStartRow, long theStartCol,
						long theStartVal)
// fill remaining numbers by trial and error
{
	short	*aStartSquare = latinSquare +
												((theStartRow * n) << theSubDim) +
												(theStartCol << theSubDim);
	short	*aValInRow = new short[theDim];
	short	*aValInCol = new short[theDim * theDim];
	short	*aToBeFilled = new short[theDim * theDim];
	long	aRow, aCol, aValue, aSubDimvalue;
	short	*p, *q;

	aSubDimvalue = 1 << theSubDim;
	// fill left row and diagonal
	p = aStartSquare + ((n + theDim - 2) << theSubDim);
	q = aStartSquare + (n << theSubDim);
	for (aCol = 1; aCol < theDim; aCol++)
	{
		CopySquare(n, aStartSquare + ((theDim - 1) << theSubDim),
								p, aSubDimvalue);
		p += ((n - 1) << theSubDim);
		CopySquare(n, aStartSquare + (aCol << theSubDim),
								q, aSubDimvalue);
		q += (n << theSubDim);
	}
	// which numbers are used and which numbers have to be filled in
	for (aCol = 0; aCol < theDim * theDim; aCol++)
	{
		aValInCol[aCol] = 0;
		aToBeFilled[aCol] = 1;
	}
	for (aRow = 0; aRow < theDim; aRow++)
	{
		p = aStartSquare + ((aRow * n) << theSubDim);
		for (aCol = 0; aCol < theDim; aCol++)
		{
			if (*p)
			{
				aValue = aCol * theDim +
									(((*p - 1) >> theSubDim) - theStartVal);
				aValInCol[aValue] = 1;
				aToBeFilled[aValue] = 0;
			}
			p += (aSubDimvalue);
		}
	}

	// which numbers are in this row
	for (aValue = 0; aValue < theDim; aValue++)
		aValInRow[aValue] = 0;
	aValue = 0;
	aRow = 1;
	aCol = 0;
	p = aStartSquare + (n << theSubDim);
	while (1)
	{
		// find next place to ve filled
		while (*p)
		{
			aValInRow[((*p - 1) >> theSubDim) - theStartVal] = 1;
			aCol++;
			p += (aSubDimvalue);
			if (aCol >= theDim)
			{
				aCol = 0;
				aRow++;
				p = aStartSquare + ((aRow * n) << theSubDim);
				for (aValue = 0; aValue < theDim; aValue++)
					aValInRow[aValue] = 0;
				aValue = 0;
			}
		}
		// find next posible value
		while ((aValue < theDim) &&
						(aValInCol[aCol * theDim + aValue] ||
							aValInRow[aValue] ||
							CantFillRow(theDim, aValInCol, aValInRow,
													aCol, &aValue)))
			aValue++;
		if (aValue < theDim)
		{
			// place value
			aValInCol[aCol * theDim + aValue] = 1;
			aValInRow[aValue] = 1;
			CopySquare(n, aStartSquare + (aValue << theSubDim),
									p, aSubDimvalue);
			
			// next column
			aCol++;
			p += (aSubDimvalue);
			if (aCol >= theDim)
			{
				// next row
				aRow++;
				if (aRow < theDim)
				{
					p = aStartSquare + ((aRow * n) << theSubDim);
					for (aValue = 0; aValue < theDim; aValue++)
						aValInRow[aValue] = 0;
					for (aCol = 0; aCol < theDim; aCol++)
					{
						aValInRow[aValue] = 0;
						if (*p)
					aValInRow[((*p - 1) >> theSubDim) - theStartVal] = 1;
						p += (aSubDimvalue);
					}
					aCol = 0;
					p = aStartSquare + ((aRow * n) << theSubDim);
				}
				else
				{
					return;
				}
			}
			aValue = 0;
		}
		else
		{
			// undo
			aCol-;
			p -= (aSubDimvalue);
			aValue = ((*p - 1) >> theSubDim) - theStartVal;
			while (aCol >= 0 && !aToBeFilled[aCol * theDim + aValue])
			{
				aCol-;
				if (aCol >= 0)
				{
					p -= (aSubDimvalue);
					aValue = ((*p - 1) >> theSubDim) - theStartVal;
				}
			}
			if (aCol < 0)
			{
				aRow-;
				p = aStartSquare +
						(((aRow * n) + theDim - 1) << theSubDim);
				aCol = theDim - 1;
				for (aValue = 0; aValue < theDim; aValue++)
					aValInRow[aValue] = 1;
			}
			aValue = ((*p - 1) >> theSubDim) - theStartVal;
			*p = 0;
			aValInCol[aCol * theDim + aValue] = 0;
			aValInRow[aValue] = 0;
			aValue++;
		}
	}
	delete[] aValInCol;
	delete[] aValInRow;
	delete[] aToBeFilled;
}

FillSquare
static void FillSquare(long n, short *latinSquare,
						long theDim, long theSubDim,
						long theStartRow, long theStartCol,
						long theStartVal, long theNrOfRows)
// fill latin square
// if n can be divided by a power of 2,
// theSubDim is 2^x, theDim is n/(2^x)
{
	if (theDim == 1)	// n is a power of 2
		FillSquare2(n, latinSquare, 1 << theSubDim,
				theStartRow << theSubDim, theStartCol << theSubDim,
				theStartVal << theSubDim, theNrOfRows << theSubDim);
	else
	{
		long	aMaxPower2, aNrOfRows, aStartCol, aStartRow;
		short	*aStartSquare = latinSquare +
												((theStartRow * n) << theSubDim) +
															(theStartCol << theSubDim);
		aMaxPower2 = 1;
		while (aMaxPower2 <= theDim) aMaxPower2 <<= 1;
		aMaxPower2 >>= 1;
		// fill top left of the square with a square with n = 2^aMaxPower2
		FillSquare2(n, latinSquare, aMaxPower2 << theSubDim,
				theStartRow << theSubDim, theStartCol << theSubDim,
				theStartVal << theSubDim, aMaxPower2 << theSubDim);
		aNrOfRows = theDim - aMaxPower2;
		if (aNrOfRows > theNrOfRows) aNrOfRows = theNrOfRows;
		// fill top right of the square with a square with n = theDim - 2^aMaxPower2
		FillSquare(n, latinSquare, theDim - aMaxPower2, theSubDim,
								theStartRow, theStartCol + aMaxPower2,
								theStartVal + aMaxPower2, aNrOfRows);
		// copy the square from the top right along the diagonal to the bottom left
		aStartCol = aMaxPower2 - aNrOfRows;
		aStartRow = aNrOfRows;
		while (aStartCol >= 0 && aStartRow < theNrOfRows)
		{
			if (aStartRow + aNrOfRows > theNrOfRows)
				aNrOfRows = theNrOfRows - aStartRow;
			if (aNrOfRows > aStartCol && aStartCol > 0)
				aNrOfRows = aStartCol;
	for (long aRow = 0; aRow < (aNrOfRows << theSubDim); aRow++)
			{
				short	*aFrom = aStartSquare + (aRow * n) +
												(aMaxPower2 << theSubDim);
				short	*aTo = aStartSquare +
									(((aStartRow << theSubDim) + aRow) * n) +
											(aStartCol << theSubDim);
				for (long aCol = 0; aCol < ((theDim - aMaxPower2) << 
							theSubDim); aCol++)
				{
					*aTo = *aFrom;
					aFrom++;
					aTo++;
				}
			for (long aCol = ((aStartCol + (theDim - aMaxPower2)) <<
				theSubDim); aCol < (aMaxPower2 << theSubDim); aCol++)
				{
					*aTo = 0;
					aTo++;
				}
			}
			aStartCol -= (theDim - aMaxPower2);
			aStartRow += (theDim - aMaxPower2);
		}
		// generate the remaning numbers
		CompleteSquare(n, latinSquare, theDim, theSubDim,
										theStartRow, theStartCol, theStartVal);
	}
}

LatinSquares
void LatinSquares(
  short n, /* dimension of the latin square to be generated */
  short *latinSquare /* set latinSquare[c + r*n] to square value row r, col c */
) {
	short	*p = latinSquare;
	long	aSubDim = 0;
	// init
	for (long i = 0; i < n * n; i++, p++)
		*p = 0;
	// can n be divided by a power of 2
	while (!(n & (1 << aSubDim))) aSubDim++;
	FillSquare(n, latinSquare, n >> aSubDim, aSubDim,
							0, 0, 0, n >> aSubDim);
}
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

FileMaker Pro 19.4.2 - Quickly build cus...
FileMaker Pro is the tool you use to create a custom app. You also use FileMaker Pro to access your app on a computer. Start by importing data from a spreadsheet or using a built-in Starter app to... Read more
Adobe Illustrator 26.0.3 - Professional...
You can download Adobe Illustrator for Mac as a part of Creative Cloud for only $20.99/month. Adobe Illustrator for Mac is the vector graphics classics in the design industry. It is a digital... Read more
WhatRoute 2.4.9 - Geographically trace o...
WhatRoute is designed to find the names of all the routers an IP packet passes through on its way from your Mac to a destination host. It also measures the round-trip time from your Mac to the router... Read more
Notion 2.0.20 - A unified workspace for...
Notion is the unified workspace for modern teams. Notion Features: Integration with Slack Documents Wikis Tasks Release notes were unavailable when this listing was updated. Download Now]]> Read more
Monterey Cache Cleaner 17.0.2 - Clear ca...
Monterey Cache Cleaner is an award-winning general-purpose tool for macOS X. MCC makes system maintenance simple with an easy point-and-click interface to many macOS X functions. Novice and expert... Read more
Firetask Pro 4.6.8 - Innovative task man...
Firetask Pro represents the next generation of easy-to-use, project-oriented task management apps. By combining David Allen's powerful Getting Things Done (GTD®) approach with classical task... Read more
Smultron 13.0.4 - Easy-to-use, powerful...
Smultron 13 is the text editor for all of us. Smultron is powerful and confident without being complicated. Its elegance and simplicity helps everyone being creative and to write and edit all sorts... Read more
Box Sync 4.0.8057 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Audio Hijack 3.8.10 - Record and enhance...
Audio Hijack (was Audio Hijack Pro) drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio... Read more
Direct Mail 6.0.1 - Create and send grea...
Direct Mail is an easy-to-use, fully-featured email marketing app purpose-built for macOS. Create, send, and track great looking email campaigns that get results. Start your newsletter by selecting... Read more

Latest Forum Discussions

See All

Hopefully Not Jared’s Last Show – The To...
My suspicions from last week were correct, and after my two kids tested positive for Covid last week both my wife and I have now tested positive as well. It seems you just can’t escape this stuff lately. Thankfully the two little ones are pretty... | Read more »
TouchArcade Game of the Week: ‘Micro RPG...
I feel like idle games are one of those perfect fits for the mobile platform. Not that they replace more involved gaming experiences when you’re in the mood for that, but they do fit in alongside other types of games just fine as a “go to" when you... | Read more »
‘Phantom Blade: Executioners’ Closed Bet...
Phantom Blade: Executioners is holding a small-scale technical test that lets players get first dibs on the KungFuPunk action RPG. Offered to selected players only, S-Game’s first Closed Beta Test will provide players with limited edition in-game... | Read more »
New ‘Warhammer 40,000: Tacticus’ Video S...
Back in September Snowprint Studios, who you may know from their previous Legend of Solgard or Rivengard, announced that they’d partnered up with Games Workshop to put out a new tactical game in the Warhammer 40,000 universe titled Warhammer 40,000... | Read more »
SwitchArcade Round-Up: ‘Pokemon Legends:...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for January 28th, 2022. We’ve got a bunch of new releases to look at today, with a few big hitters, a few mid-level diversions, and a healthy supply of compost. Since it’s Friday, we... | Read more »
Phantom Blade: Executioners, S-Game...
S-Game has kicked off its first Closed Beta Test for Phantom Blade: Executioners, inviting a selected few to get first dibs on the upcoming KungFuPunk action RPG on mobile. The CBT officially begins this January 28th, and beta testers will receive... | Read more »
‘Infinite Galaxy’ First Anniversary: Cel...
Cultivating a new generation of valiant commanders across 240 countries worldwide, Infinite Galaxy has quenched players’ thirst to explore the vastness of space – and there are only more intergalactic adventures to embark on from here on out. Camel... | Read more »
War and Order: How to brave the cold in...
War and Order's 6th-anniversary celebrations are underway, and all in good time too - this season not only brings about fabulous festivities, but it also lets players experience the harsh winter in an entirely new way. [Read more] | Read more »
‘Hidden Folks+’ Is This Week’s New Apple...
The original Hidden Folks from Adriaan de Jongh is an excellent hidden objects game featuring hand drawn visuals. It is an absolute joy to play, and it has now released on Apple Arcade in the form of Hidden Folks+ () as an App Store great. If you’... | Read more »
Mini Metro’s First Big Update of 2022 Ad...
Last year saw great updates for Dinosaur Polo Club’s Mini Metro ($3.99) which is also available on Apple Arcade as an App Store Great. | Read more »

Price Scanner via MacPrices.net

Apple has clearance 2020 13″ MacBook Airs ava...
Apple has clearance, Certified Refurbished, 2020 13″ Intel-based MacBook Airs in stock today starting at only $719 and up to $370 off original MSRP. Each MacBook features a new outer case, comes with... Read more
The cheapest iPhones for sale today at Apple...
Apple has restocked Apple Certified Refurbished iPhone 8 models starting at only $359. Each refurbished iPhone comes with a fresh external case, standard Apple 1-year warranty, and free shipping.... Read more
14″ MacBook Pro with Apple M1 Max CPU now in...
Looking for a new 14″ MacBook Pro with an Apple M1 Max CPU? Stock is finally trickling into Apple resellers. B&H has Silver 14″ M1 Max MacBook Pros in stock today for $2899 including free 1-2 day... Read more
14″ MacBook Pros with Apple M1 Pro CPUs are i...
Amazon is reporting stock of 14″ MacBook Pros with M1 Pro CPUs today with a $50 discount. Shipping is free, and delivery is available by February 1st for most configurations. Be sure to make your... Read more
Apple has restocked 13″ M1 MacBook Pros for $...
Apple has restocked a full line of 13″ M1 MacBook Pros available Certified Refurbished, starting at only $1099 and up to $230 off original MSRP. These are the cheapest M1 MacBook Pros for sale today... Read more
Apple’s AirPods Max headphones are on sale fo...
Amazon has Silver, Blue, and Space Gray Apple AirPods Max headphones on sale today for $100 off MSRP. Shipping is free, and all models are in stock today. Their price is the lowest currently... Read more
Open a new line of service at Verizon and get...
Verizon is giving away 64GB Apple iPhone 12 minis or your choice of an iPhone 11 to customers who choose one of these phones and open a new line of service. Offer is available online only, and no... Read more
Open-box 13″ M1 MacBook Airs now available st...
QuickShip Electronics has open-box return 13″ M1 MacBook Airs in stock and on sale for $200-$400 off MSRP on their eBay store right now with free express delivery. According to QuickShip, “The item... Read more
Verizon’s 2022 iPad promo: $100-$310 off any...
Verizon has cellular-capable iPads on sale for $100-$310 off MSRP when purchased with an Unlimited service plan. Sale price is applied to your account monthly over a 24 or 30 month period, depending... Read more
Sunday Sale: Apple AirPods are on sale for up...
Amazon has Apple AirPods on sale for $10-$100 off MSRP today, depending on the model. All are in stock today with free delivery: – AirPods Max headphones (Blue): $449 $100 off MSRP – AirPods Max... Read more

Jobs Board

Registered Nurse (RN) Employee Health PSJH -...
…is calling for a Registered Nurse (RN) Employee Health PSJH to our location in Apple Valley, CA.** We are seeking a Registered Nurse (RN) Employee Health PSJH to be Read more
Systems Administrator - Pearson (United State...
…and troubleshoot Windows operating systems (workstation and server), laptop computers, Apple iPads, Chromebooks and printers** + **Administer and troubleshoot all Read more
IT Assistant Level 1- IT Desktop Support Anal...
…providing tier-1 or better IT help desk support in a large Windows and Apple environment * Experience using IT Service Desk Management Software * Knowledge of IT Read more
Human Resources Business Partner PSJH - Provi...
…**is calling a** **Human Resources Business Partner, PSJH** **to our location in Apple Valley, CA.** **Applicants that meet qualifications will receive a text with Read more
Manager Community Health Investment Programs...
…is calling a Manager Community Health Investment Programs PSJH to our location in Apple Valley, CA.** **Qualified candidates will be invited to do a self-paced video Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.