Question

Avoiding round-off error in calculating taxes

Asked by: david_johns

This seems like an easy enough question, and hopefully it is for someone!

My application has to calculate sales tax.  I had a user complain of it mis-calculating taxes at least on one testcase.  On this particular sale the total items are $69.00.  The tax rate is 0.065, but the computer stores it as 0.064999998 so when it multiplies the two it gets 4.4849997, instead of 4.485.  Then I send it through the attached code to round it and it chooses 4.48, when it should technically be 4.49.  Any ideas on how to correct this?

double round(double value, int precision)
{
	double fraction,
		   temp;
 
 
	value = value*pow(10, precision);
 
	fraction = modf(value, &temp);
	if(fraction>=0.5) temp+=1.0;
	if(fraction<=-0.5) temp-=1.0;
 
	return temp*pow(0.1, precision);
}

                                  
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:

Select allOpen in new window

This Question has been solved and asker verified All Experts Exchange premium technology solutions are available to subscription members.

Subscribe now for full access to Experts Exchange and get

Instant Access to this Solution

  • Plus...
  • 30 Day FREE access, no risk, no obligation
  • Collaborate with the world's top tech experts
  • Unlimited access to our exclusive solution database
  • Never be left without tech help again

Subscribe Now

Asked On
2008-04-08 at 17:53:51ID23306849
Tags

C++, C

Topics

C++ Programming Language

,

C Programming Language

Participating Experts
5
Points
125
Comments
29

Trusted by hundreds of thousands everyday for fast, accurate and reliable tech support.

  • "The time we save is the biggest benefit of Experts Exchange to Warner Bros. What could take multiple guys 2 hours or more each to find is accessed in around 15 minutes on Experts Exchange." Mike Kapnisakis, Warner Bros.
  • "Our team likes having a resource that is more secure than just using Google and most experts using this service really know their stuff. It's nice to look here first versus using Google." Dayna Sellner, Lockheed Martin
  • "Anytime that I've been stumped with a problem, 9 out of 10 times Experts Exchange has either the accepted solution or an open discussion of the potential solution to the problem." Kenny Red, eBay Inc.

See what Experts Exchange can do for you.

Got a question?

We've got the answer.

Experts Exchange has been collecting answers to technology questions since 1996…3 million and counting! If you have a question, chances are we already have your answer.

Screenshot of Experts Exchange Knowledgebase

Need individual assistance?

Our experts are ready to help.

If you can't find the exact answer you're looking for, ask our exclusive community of 50,000 experts. You’ll get a personalized answer from a trusted professional.

Screenshot of Experts Exchange Knowledgebase

Want to learn from the best?

Read articles from industry experts.

Thousands of free tech tips, tricks, how-to’s and tutorials are available in our peer reviewed articles section. See for yourself how smart our experts are, no login required.

Screenshot of an Article

Working on a long term project?

Store your work and research.

Save solutions to your questions, answers you’ve discovered through searching plus helpful articles in your personal knowledgebase for easy future access.

Screenshot of Experts Exchange Knowledgebase

Access the answers to your technology questions today.

Subscribe Now

30-day free trial. Register in 60 seconds.

What Makes Experts Exchange Unique?

Members of the expert community talk about why the experience at Experts Exchange is different than what you will find anywhere else.

Trusted by the world's most respected brands.

image of each brand's logo

Faithfully serving IT professionals since 1996.

Experts Exchange Logo

Try it out and discover for yourself.

Subscribe Now

30-day free trial. Register in 60 seconds.

Related Solutions

  1. sales tax
    I am not a programmer. I am looking to find the source code to calculate the sales tax of number of items enter. Any help would be wonderful. The source code should be in C in Windows. Input: # of items, price of each item, sales tax% of your state(will us 5%), amount tender...
  2. Calculate Sales  Tax on Invoice
    My information calculates properly on my orders form, but when I get to the invoice, the sales tax is not included. The orders form looks to a table for the sales tax rate. This is in the field for Sales Tax on the invoice: =CLng([InvoiceSubtotal]*[SalesTaxRate]*100)/100 ...
  3. Tax Calculator
    Write a C program that create a tax calculation program for food stores. Some food items and all non-food items are subject to a stae wide sales tax and local district taxes. Due to differences in district taxes, each store uses different tax rate: Del Mar - 7.25% Encinitas...

Free Tech Articles

  1. WARNING: 5 Reasons why you should NEVER fix a computer for free.
    It is in our nature to love the puzzle. We are obsessed. The lot of us. We love puzzles. We love the challenge. We thrive on finding the answer. We hate disarray. It bothers us deep in our soul. W...
  2. SCCM OSD Basic troubleshooting
    SCCM 2007 OSD is a fantastic way to deploy operating systems, however, like most things SCCM issues can sometimes be difficult to resolve due to the sheer volume of logs to sift through and the dispe...
  3. Migrate Small Business Server 2003 to Exchange 2010 and Windows 2008 R2
    This guide is intended to provide step by step instructions on how to migrate from Small Business Server 2003 to Windows 2008 R2 with Exchange 2010. For this migration to work you will need the fo...
  4. Create a Win7 Gadget
    This article shows you how to create a simple "Gadget" -- a sort of mini-application supported by Windows 7 and Vista. Gadgets can be dropped anywhere on the desktop to provide instant information, ...
  5. Outlook continually prompting for username and password
    There have been a lot of questions recently regarding Outlook prompting for a username and password whilst using Exchange 2007. There are a few reasons why this would happen and I will try to cover t...
  6. Backup Exchange 2010 Information Store using Windows Backup
    There seems to be quite a lot of confusion around the ability to backup Exchange 2010 using the built in Windows Backup feature. This stems from the omission of this feature prior to Exchange 2007 s...

Cloud Class Webinars

  1. Avoiding Bugs in Microsoft Access
    Alison Balter takes and in-depth look at avoiding bugs in Access. In this webinar you will learn about using the immediate window to debug your applications, invoking the debugger, using breakpoints to troubleshoot, stepping through code, setting the next statement to execute, ...
  2. Top 10 Best New Features in Visio 2010
    Scott Helmers gives live demonstrations of the top 10 new features in Visio 2010. This webinar will teach you how to create compelling diagrams by adding shapes to the page with a single click, linking the shapes in a diagram to data in Excel (or SQL Server, or SharePoint), ...
  3. IT Consultant Business Secrets Revealed
    Michael Munger, Experts Exchange tech pro and IT consultant, pulls back the curtain on his very successful businesses and answers question on every IT consultant and business owner should know about. He shares secrets on what he did to solve the 5 most common problems in IT, ...
  4. Disaster Recovery and Business Continuity
    Quest CTO, Mike Billon, gives an overview of the steps involved in building a dunamic disaster recovery plan. Through case studies and an examination of software/hardware tooles for monitoring and testing, you'll gain a better understandin of where you are, where you want ...
  5. Organize Your Visio Diagrams with Containers and Lists
    Scott Helmers uses cross functional flowcharts, wireframe diagrams, data graphic legends and seating charts to teach you: how to ustilize all three new structured diagram components in Visio 2010, the best practices for organizeing shapes in previous version of Visio, how to organize ...
  6. How to Us Objects, Properties, Events and Methods in Microsoft Access
    Alison Dalter gives an in-depbth look at objects, properties, events and methods in Microsoft Access. In this webinar you will learn about using the object browser, referring to objects, working with properties and methods, working with object variables, understanding the ...

Join the Community

Give a Little. Get a Lot.

Join the community of experts here and help other tech pros by answering question in your area of expertise. You can earn FREE access to all Experts Exchange's premium features and resources.

Join the Community

Answers

 

by: ozoPosted on 2008-04-08 at 18:05:39ID: 21311068

Rounding rules for taxes may vary by jurisdiction, but you probably shouldn't trust floating point numbers to do it
you could take 6900 cents * 65 = 448500 / 1000 cents rounded to 449000 / 1000 cents

 

by: GurudenisPosted on 2008-04-08 at 23:32:09ID: 21312243

Should you decide to follow the (good) suggestion given by ozo, be sure to use __int64 rather than int, because otherwise an overflow may occur on bigger sums of money. If __int64 is not an option, at least use unsigned int (its numeric limit is pow(2,32) which is over 4 billion as opposed to around 2 billion for int; it means you can use unsigned to process sums up to 40 mln dollars).

 

by: ikeworkPosted on 2008-04-08 at 23:36:56ID: 21312267

ozo, the problem remains here:

448500 / 1000

the result should be a floating point number, otherwise (if you use int) its just 448


david_johns, add 0.005 and then just print the value to the last two digits ("%.2f" in the printf-statement does the trick)

    float val = 4.4849997f;
    printf( "original: %f  rounded: %.2f\n", val, val + 0.005f );

output:

    original: 4.485000  rounded: 4.49


ike

 

by: ikeworkPosted on 2008-04-08 at 23:39:35ID: 21312282

you lose more precision with fixed-point arithmetics like shown above. depending on the number of digits for a fixed point (lets take 2 here):

10 / 3 * 3 = 9.99

with floats you get 10 as outcome ..

 

by: ikeworkPosted on 2008-04-08 at 23:43:39ID: 21312302

to be clear, you cant avoid rounding errors, you can just minimize it and cut the unnecessary information ("%.2f") when you present the result to the user.

 

by: ikeworkPosted on 2008-04-08 at 23:47:09ID: 21312323

@ozo @gurudenis, of course in a given range you can reach the same precision with fixed-points, but its overkill here since you can do the same with types the compiler provides

 

by: ozoPosted on 2008-04-08 at 23:50:16ID: 21312341

I said to round 448500 / 1000 to 449000 / 1000
If you try to convert convert 4.485 to floating point depending on your implementation it may really be 4.8849999999999997868371792719699442386627197265625

 

by: ikeworkPosted on 2008-04-09 at 00:08:45ID: 21312430

>> I said to round 448500 / 1000 to 449000 / 1000

misunderstood, nevermind.;)

>> If you try to convert convert 4.485 to floating point depending on your implementation it may really be 4.8849999999999997868371792719699442386627197265625

thats not a problem, because you dont show it to the user ..

the thing with the fixed point is, you have to transform it from normal numbers (*1000) before calculations and back (/1000) after calculations. dont get me wrong, you can perfectly do it, but its overkill here

 

by: ozoPosted on 2008-04-09 at 00:15:14ID: 21312461

It can be a problem if you try to do calculations with it.
If the only place that you use floating point is as a parameter to printf("%.2f",float) then it need not be a problem.

 

by: ikeworkPosted on 2008-04-09 at 02:53:20ID: 21313178

>> It can be a problem if you try to do calculations with it.

can you show how the fpu can cause a problem in this calculation with input in the right range

  gross = round( net * taxrate );

 

by: ozoPosted on 2008-04-09 at 03:01:56ID: 21313223

The original question showed an example of
4.4849997 rounded to 4.48 instead of 4.485 rounded to 4.49

 

by: ikeworkPosted on 2008-04-09 at 04:27:08ID: 21313679

well i dont have a problem here with this example, can you run

float net = 69.0f;
float tax_rate = 0.065f;
float tax = net * tax_rate;
printf( "ori: %f  rounded: %.2f\n", tax, tax + 0.005f );

and send the output. dont trust the debugger in this case ..

 

by: ozoPosted on 2008-04-09 at 10:08:55ID: 21316896

+ 0.005f would cause an error if the true value was 4.484 which would need to be rounded to 4.48

 

by: ikeworkPosted on 2008-04-09 at 10:51:47ID: 21317299

>> + 0.005f would cause an error if the true value was 4.484 which would need to be rounded to 4.48

good catch, you're right. changing from floats to doubles with their higher precision and adjusting the rounding functions gives the desired outcome.
its important that the "net * tax_rate" is already calculated with doubles instead of floats.
so finally it works, not with floats but with doubles ;)

#include <cstdio>
#include <math.h>
 
double round_2nd( double val )
{
    return floor( val * 100.0 + 0.5 ) / 100.0;
}
 
int main()
{
    double net = 69.0;
    double tax_rate = 0.065;
    double tax = net * tax_rate;
    printf( "ori: %f  rounded: %.2f\n", tax, round_2nd( tax ) );
 
    return 0;
}

                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:

Select allOpen in new window

 

by: ozoPosted on 2008-04-09 at 11:02:49ID: 21317411

With doubles, 0.065 might be stored as the more accurate value
0.065000000000000002220446049250313080847263336181640625
instead of
0.064999998
but it is still wrong.
and if you are legally required to to follow precise rounding rules when dealing with money, then you don't want to be dealing with FPU approximations

 

by: ikeworkPosted on 2008-04-09 at 11:21:04ID: 21317596

ok thats true. the question is if the fpu-approximation-error is high enough to give a wrong result. i could not see that yet.
you can ignore the error you were talking about, because the (wanted)rounding(error) itsself is between 0.0<=e<0.01 which is more than a billion times higher than the fpu-approximation-error in your above example. so the rounding itsself is more critical than the fpu here.
but i might be wrong, please can you show some examples.

 

by: ikeworkPosted on 2008-04-09 at 11:23:25ID: 21317617

>> please can you show some examples

i dont mean it polemic. im really interested in that matter. if you can find some please post it :)

 

by: PaulCaswellPosted on 2008-04-09 at 11:25:58ID: 21317644

The crux of tax calculation is the realisation of the following facts:

1. You cannot charge the customer a fraction of a penny/cent.

2. If you don't charge enough the tax-man will get you.

3. If you charge too much the customers will get you.

So ... from 1 ... forget floats/doubles etc. use integers capable of handling the largest amount possible.

And ... from 2 and 3 ... be sure you are as close as possible but if a choice is inevitable, round up. A run-in with the tax-man can cost you severely while a run-in with a customer may cost you a penny/cent.

Paul

 

by: ikeworkPosted on 2008-04-09 at 11:32:44ID: 21317712

hey paul, thanks for that :)
is your point:

1) the fpu would cause errors
or
2) dont use fpu, because you can do the same with int's
or
3) something else

because we focused on 1) so far.

 

by: ozoPosted on 2008-04-09 at 11:43:22ID: 21317826

If the rule is to round up when the true value is exactly in the middle, then a slightly too large value may let you err on the safe side (unless you do enough calculations to compound errors across a threshold )
But it can be tricky to predict which floats or doubles will be slightly to big and which will be slightly too small.
On my machine, 0.065 comes out slightly too big,
but 0.06 or 0.075 come out slightly too small.
So if your tax rate changes, or your code runs on a different machine, or your sale item is different, you may suddenly find yourself with a run-in with someone.
Simply increasing precision doesn't always help.  You might get closer to the true value but cross from one side of the threshold to the other.

 

by: PaulCaswellPosted on 2008-04-09 at 11:45:06ID: 21317838

Hi Ike,

Essentially, this kind of calculation should:

1. Use integers (int/long/_int64).
2. Round at the end, as near to print time as you can.
3. Round .5 up.

Paul

 

by: ikeworkPosted on 2008-04-09 at 12:08:45ID: 21318038

thanks ozo, i see your point. to you too paul :)

i found this money class:

http://www.di-mare.com/adolfo/p/money.htm

it is sort of combining what we were talking about. it uses a double as storage, but it scales all *incoming* values (assignment from doubles or floats) by a given factor (most common one is hundret for cents) and then it gets rid of the post decimal positions with the FIX()-member. so its like using an int, isnt it?
now im sort of confused what is the right way to do it, can you have a look it?
i could not find an example with simply an int-implementation.

at least this class worths it for this one, i never saw it before:

#define TENPOW(n)  1.0e##n

great trick :)

@david, sorry for hijacking this, but i guess youre interested too, right :)

thanks guys

 

by: david_johnsPosted on 2008-04-09 at 14:27:47ID: 21319254

Definitely!  Great discussion.  Thanks for all the ideas.  I'm still thinking through how I want to approach it.  The first thing I tried was switching the storage of the tax rate from a ratio (i.e. 0.065) to a percentage (i.e. 6.5).  This fixes the problem in this case.  I am trying to decide if it will generically fix the problem, or just for this case.

 

by: ikeworkPosted on 2008-04-09 at 14:48:11ID: 21319415

>> I am trying to decide if it will generically fix the problem, or just for this case.

if i understood ozo's post right, even if you solve one case at one cpu, you cannot predict how it will behave on another if you use floats or doubles.

im gonna double check my tax-papers next time ;)

 

by: ozoPosted on 2008-04-09 at 14:48:51ID: 21319424

In this case, binary floating point can represent a value like 6.5 exactly.
But it still can't represent 4.485 exactly, or prices like 69.01 exactly, so there may still be potential for trouble at the boundaries.  

 

by: gatorvipPosted on 2008-04-11 at 11:15:37ID: 21336740

Another approach is to use strings instead of floating-point. Of course, the mathematical operations would need to be (re)implemented  to allow for this change.

 

by: ozoPosted on 2008-04-11 at 14:42:00ID: 21338431

If you can arrange for all calculated values to end up in the middle of a range
instead of on the edge between different ranges, and you can put a bound on how much error your floating point numbers might have and how much errors can accumulate as you do repeated calculations, and can guarantee that that will always be less than the amount of error it would take to cross a boundary, then it can be safe to use floating point representations.

 

by: PaulCaswellPosted on 2008-04-11 at 15:15:51ID: 21338638

Nicely put ozo, and I would like to add perhaps the second half of your monologue.

You can arrange for all calculated values to end up within exactly predictable errors across all platforms if you use integers.

Double-long integer maths is pretty easy. Multiplication addition and subtraction are trivial. Here's an implementation of MulDiv (multiply by one number and divide by another) that seems to work reliably and handle transient overflow.

Paul

// Mul a number by a number and divide by another.
// Handles transient overflow.
/*
	We must work in two longs.
 
	First we multiply up x by m into h and l.
	This will be done using a technique I have never seen documented
	but it works.
 
	Notice that, for example:
 
	43 * 21 = 903
 
	and
 
	4 * 2					* 100	= 800
	( (4 * 1) + (3 * 2) )	* 10	= 100
	3 * 1							= 3
									  ---
									  903
									  ---
 
	This technique works just as well for powers of 2 as powers of ten.
 
	The division process will still have to be done the old way.
 
	I'll only use 31 bits of each long as there is no concept of 
	a carry flag in 'C' - I can use the sign instead.
	
*/
// Some useful defines.
#define Shl(hi,lo)	hi <<= 1; hi |= (lo >> 31); lo <<= 1;
#define Shr(hi,lo)	lo >>= 1; lo |= (hi << 31); hi >>= 1;
 
#define ulong unsigned long
#define unless(e) if(!(e))
#define until(e) while(!(e))
 
long MulDiv (long x, long m, long d)
{
	long r = 0;				// The result.
	ulong topBit = 0L;		// Preserves the top bit of l.
	// The high and low halves of x and m.
	ulong xh = (x >> 16) & 0xFFFFL;	// 4
	ulong xl = (x & 0xFFFFL);		// 3
	ulong mh = (m >> 16) & 0xFFFFL;	// 2
	ulong ml = (m & 0xFFFFL);		// 1
	// The high and low 31 bits of the transient.
	ulong h = xh * mh;				// 4 * 2 * 100		
	ulong l = xl * ml;				// 3 * 1
	// The divisor.
	ulong dh = 0L;
	ulong dl = d;
	// A temp long.
	ulong t = (xh * ml) + (xl * mh);// (4 * 1) + (3 * 2)
	// We must be careful during the adding up to preserve carry.
	// Take l down to 31 bits before the add.
	// Save the top bit of l.
	topBit += l >> 31;
	// and the top bit of t << 16
	topBit += (t >> 15) & 1;
	// And remove them.
	l &= ~( 1L << 31 );
	t &= ~( 1L << 15 );
	// Add in t * 10 to l.
	l += t << 16;
	// Grab the resultant top bit.
	topBit += l >> 31;
	// And remove it.
	l &= ~( 1L << 31 );
	// Put the real one in.
	l += topBit << 31;
	// Add in t * 10 to h.
	h += t >> 16;
	// And the carry.
	h += topBit >> 1;
 
	// h and l now contain x * m.
	// Now divide by d.
 
	// First shift both dh:dl and h:l right until dl is odd.
	while ( (dl != 0) && ((dl & 1) == 0) )
	{
		Shr(dh,dl);
		// What we loose from h:l would be lost anyway during division.
		Shr(h,l);
	}
 
	// Use long division now if necessary.
	if ( h != 0 )
	{
		int bit = 0;				// Which bit we are looking at.
 
		// Long division.
 
		// First shift d left until dh:dl > h:l
		until ( (dh > h) || ((dh == h) && (dl > l)) )
		{
			Shl(dh,dl);
			// Count the bits.
			bit += 1;
		}
		// Shift right one to get back to <=.
		Shr(dh,dl);
		bit -= 1;
 
		do
		{
			// Subtract.
			/*
				Strange but it works.
 
				Imagine working in one-decimal-digit unsigned space.
				5 - 7 = 8 (because 0 - 1 = 9)
				should be interpreted as a borrow, i.e.
				(1)5 - 7 = 8
				and therefore gives the correct answer whether we borrow or not.
			*/
			h -= dh;
			if ( l < dl )
			{
				// Borrow.
				h -= 1;
			}
			l -= dl;
 
			// Record the result.
			r |= 1L << bit;
			// Shift right until dh:dl <= h:l.
			until ( (bit == (0-1)) || (dh < h) || ((dh == h) && (dl <= l)) )
			{
				Shr(dh,dl);
				// Count the bits.
				bit -= 1;
			}
			// Stop when all bits done or d is now 0.
		} until ( (bit == (0-1)) || ((dh == 0) && (dl == 0)) );
 
 
	}
	else
	{
		// No need for long division 'cause its a 32bit value.
		r = l / dl;
	}
 
	return r;
}

                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:

Select allOpen in new window

 

by: david_johnsPosted on 2008-04-19 at 11:56:59ID: 21393517

I wanted to post back the final solution you all helped me to come up with.  Feel free to offer additional suggestions, but I tested it for every dollar amount from $0.01 to $1000.00 and it worked without error.  I am assuming it will be okay past there until I reach the limit my variable types allow.  Note that I am not worried about amounts above $10,000.

//Tax Rates are stored as longs, where 6000 means 6% for example
//Totals are stored as longs denoting cents
tax = taxableProducts * productsRate;
tax += taxableShipping * shippingRate;
 
remainder = tax%100000;
if(remainder>=50000) tax+=100000-remainder;
else if(remainder<=-50000) tax+=(-100000-remainder);
else tax-=remainder;
taxFloat = (float) round((double) tax/(double) 10000000, 2);
return taxFloat;

                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:

Select allOpen in new window

20120131-EE-VQP-002

3 Ways to Join

30-Day Free Trial

The Experts

98% positive feedback on 31,087 answers since March 2000. angeliii is a Microsoft Most Valuable Professional for his work with MS SQL Server & Develoment.

He has also proven his knowledge of Visual Basic Programming, PHP Scripting and Oracle Databases.

The Experts

97% positive feedback on 10,752 answers since July 2000. lrmoore has more than 18 years experience in the networking industry.

The six-time Mircosoft MVPs specialties include firewalls, virtual private networking, and network management.

Testimonials

"...and excellent source for support... Kind of like having your very own IT dept." Electriciansnet

Testimonials

"I was apprehensive at signing up at first. However... it has already made my life as an IT administrator much easier." JaCrews

Testimonials

"WOW! You guys have great, active, and knowledgeable people on here." moore50

Business Clients

Business Clients

In the Press

"If you’ve got a question... Experts Exchange can supply an answer.”

In the Press

"...an invaluable aid for both IT professionals and those who require tech support."

In the Press

"where IT professionals provide quick answers on just about any topic"

Business Account Plans

Loading Advertisement...