Decimal precision when multiplying - c#

Given 2 values like so:
decimal a = 0.15m;
decimal b = 0.85m;
Where a + b will always be 1.0m, both values are only specified to 2 decimal places and both values are >= 0.0m and <= 1.0m
Is it guaranteed that x == total will always be true, for all possible Decimal values of x, a and b? Using the calculation below:
decimal x = 105.99m;
decimal total = (x * a) + (x * b);
Or are there cases where x == total only to 2 decimal places, but not beyond that?
Would it make any difference if a and b could be specified to unlimited decimal places (as much as Decimal allows), but as long as a + b = 1.0m still holds?

Decimal is stored as a sign, an integer, and an integer exponent for the number 10 that represents the decimal location. So long as your integral portion of the number (e.g. 105 in 105.99) is not sufficiently large, then a + b will always equal one. and the outcome of your equation (x * a) + (x * b) will always have the correct value for the four decimal places.
Unlike float and double, precision is not lost up to the size of the data type (128 bits)
From MSDN:
The Decimal value type represents decimal numbers ranging from
positive 79,228,162,514,264,337,593,543,950,335 to negative
79,228,162,514,264,337,593,543,950,335. The Decimal value type is
appropriate for financial calculations requiring large numbers of
significant integral and fractional digits and no round-off errors.
The Decimal type does not eliminate the need for rounding. Rather, it
minimizes errors due to rounding. For example, the following code
produces a result of 0.9999999999999999999999999999 rather than 1
decimal dividend = Decimal.One;
decimal divisor = 3;
// The following displays 0.9999999999999999999999999999 to the console
Console.WriteLine(dividend/divisor * divisor);

The maximum precision of decimal in the CLR is 29 significant digits. When you're using that kind of precision, you're really talking approximation especially if you do multiplication because that requires intermediate results that the CLR must be able to process (see also http://msdn.microsoft.com/en-us/library/364x0z75.aspx).
If you have x with 2 significant digits and, say, a with 20 significant digits, then x * a will already have a minimum precision of 22 digits, and possibly more may be needed for intermediate results.
If x always has only 2 significant digits and you can keep the number of significant digits in a and b low enough (say, 22 digits -- pretty good and probably far enough away from 27 to deal with rounding errors), then I suppose (x * a) + (x * b) should be a pretty precise calculation always.
Finally, whether a + b always makes up 1.0m is of no significance related to a and b's individual precisions.

Related

Get Integer and Fractional part of a decimal number

Given X.Y, I want to get X and Y.
For instance, given 123.456 I want to get 123 and 456 (NOT 0.456).
I can do the following:
decimal num = 123.456M;
decimal integer = Math.Truncate(num);
decimal fractional = num - Math.Truncate(num);
// integer = 123
// fractional = 0.456 but I want 456
REF
As above-mentioned, using this method I will get 0.456, while I need 456. Sure I can do the following:
int fractionalNums = (int)((num - Math.Truncate(num)) * 1000);
// fracionalNums = 456
Additionally, this method requires knowing how many fractional numbers a given decimal number has so that you can multiply to that number (e.g., 123.456 has three, 123.4567 has four, 123.456789 has six, 123.1234567890123456789 has nineteen).
Few points to consider:
This operation will be executed millions of times; hence, performance is critical (maybe a bit-wise-based solution would do better);
Precision is critical, and no rounding is acceptable.
NOTE 1
For performance reasons, I am NOT interested in string manipulation-based approaches.
NOTE 2
The numbers in my question are of decimal type, hence methods that work for only decimal types and fail on float or double (due to floating point precision) are acceptable.
NOTE 3
Two sides of decimal (i.e., integer and fractional parts) can be considered two integers. Hence, 123.000456 is not an expected input; and even if it is given, it is acceptable to split it to 123 and 456 (because both sides are to be considered integers).
BitConverter.GetBytes(decimal.GetBits(num)[3])[2]; - number of digits after comma
long[] tens = new long[] {1, 10, 100, 1000, ...};
decimal num = 123.456M;
int iPart = (int)num;
decimal dPart = num - iPart;
int count = BitConverter.GetBytes(decimal.GetBits(num)[3])[2];
long pow = tens[count];
Console.WriteLine(iPart);
Console.WriteLine((long)(dPart * pow));
Decimal has a 96 bit mantissa, so a long is not good enough to get every possible value.
Define all (positive) powers of 10 defined for Decimal:
decimal mults[] = {1M, 1e1M, 1e2M, 1e3M, <insert rest here>, 1e27M, 1e28M};
Then, inside the loop you need to get the scale (the power of 10 by which the "mantissa" is divided to get the nominal value of the decimal):
int[] bits = Decimal.GetBits(n);
int scale = (bits[3] >> 16) & 31; // 567.1234 represented as 5671234 x 10^-4
decimal intPart = (int)n; // 567.1234 --> 567
decimal decPart = (n - intPart) * mults[scale]; // 567.1234 --> 0.1234 --> 1234
The easiest way is probably to convert the number to string.
Then take the substring after the decimal point, and convert it back to int.

High(ish) presision math on decimal

The Question:
I am using Visual C# Express 2010. I am trying to divide three integers, however, the result is always 0.
My Code:
//earlier in the code:
int totalKeywords = 3;
//the problem code:
decimal onePercent = 100 / totalKeywords / 100; //100% divided by the number of keywords divided by 100 to make one percent
MessageBox.Show(onePercent);
//result: 0
//what I want: 0.33 or more acurate
What I've tried:
I've changed the value of totalKeywords
I've tried onePercent as a double, int, float, ect.
Guesses:
It could be that the built-in math doesn't work for some reason (WHY??)
It could be that decimal / int / float, etc. don't hold decimals (I don't think so)
My Efforts:
Google
Stack Overflow
C# high precision calculations
C# High double precision
etc.
Let's break it down:
decimal onePercent = 100 / totalKeywords / 100;
First, divide the integer literal 100 by the integer variable totalKeywords (value is 3). Result: integer 33.
Next, divide the result 33 by the integer literal 100. Result: integer 0.
The right-hand expression has type int, value 0. Convert that implicitly to the decmal 0m, so you can then assign that to the decimal variable onePercent.
Result: 0m.
To fix, as others have noticed, make the leftmost constant (if not all of them, for clarity) into a decimal. This will do, as the ints will implicitly convert to decimal:
decimal onePercent = 100m / totalKeywords / 100;
This is totally unambiguous, if a little over the top:
decimal onePercent = 100m / (decimal)totalKeywords / 100m;
try 100m / totalKeywords / 100
you have to define one of your numbers (at least) as decimal.
100 is an int
100m is a decimal
http://msdn.microsoft.com/en-us/library/364x0z75.aspx
If you want a numeric real literal to be treated as decimal, use the suffix m or M, for example:
decimal myMoney = 300.5m;
on right side after calculation you will get only integer, then it will be assigned to decimal, so it gives you 0.

How to check input double value contain how many digits?

I need a double value to contain 2 digits after ".", such as 2.15, 20.15. If the input value is 3.125, then it should print an error message.
My code is:
private static bool isTwoDigits(double num)
{
return (num - Math.Floor(num)).ToString().Length <= 4;
}
If you input 2.15, then it will be 2.15 -2 = 0.15 <= 4 - which works. But when I change num to 20.15 it doesn't, because (num - Math.Floor(num)) here will return 0.14999999999.
Any other good ideas?
This is the nature of binary floating points number. Just like 1/3 can't be exactly written out as a finite decimal number, 0.1 can't be exactly represented by a finite binary expansion.
So depending on what you are trying to achieve exactly, you could:
If you are validating some string input (e.g. a textbox), you can process the information at the string level, e.g. with a RegEx.
You can store your numbers in the decimal datatype, which can store decimal values exactly.
You can do your computation on a double but you have to give yourself a tolerance. If you expect only 2 digits of precision, you can do something like Math.Abs(x - Math.Round(x, 2)) < 0.00000001). The definition of this tolerance margin depends on your use case.
If you're really worried about the number of decimal places, on a base-10 number, use decimal instead of double.
the decimal is for calculating financial calculations, and the reason it's called decimal in the first place is so that it can better handle base-10 calculations such as dollars and cents.
And you can also check if the number is 2 digits a bit more simply.
return num % 0.01m == 0.0m;
SO as has already been said, you can use regexp to ensure the entire format is correct.
But if you know there will only be 1 decimal because its already a number you can also just use String.IndexOf
eg
double foo = .... ;
string fooString = foo.ToString();
if (fooString.Length - fooString.IndexOf(".") != 3) => error.
(Its 3 because Length is max index + 1 )

Float wrong calculation [duplicate]

This question already has answers here:
Why am I getting the wrong result when using float? [duplicate]
(4 answers)
Float is converting my values
(4 answers)
Closed 9 years ago.
The result must be 806603.77 but why I get 806603.8 ?
float a = 855000.00f;
float b = 48396.23f;
float res = a - b;
Console.WriteLine(res);
Console.ReadKey();
You should use decimal instead because float has 32-bit with 7 digit precision only that is why the result differs, on other hand decimal has 128-bit with 28-29 digit precision.
decimal a = 855000.00M;
decimal b = 48396.23M;
decimal res = a - b;
Console.WriteLine(res);
Console.ReadKey();
Output: 806603.77
A float (also called System.Single) has a precision equivalent to approximately seven decimal figures. Your res difference needs eight significant decimal digits. Therefore it is to be expected that there is not enough precision in a float.
ADDITION:
Some extra information: Near 806,000 (806 thousand), a float only has four bits left for the fractional part. So for res it will have to choose between
806603 + 12/16 == 806603.75000000, and
806603 + 13/16 == 806603.81250000
It chooses the first one since it's closest to the ideal result. But both of these values are output as "806603.8" when calling ToString() (which Console.WriteLine(float) does call). A maximum of 7 significant decimal figures are shown with the general ToString call. To reveal that two floating-point numbers are distinct even though they print the same with the standard formatting, use the format string "R", for example
Console.WriteLine(res.ToString("R"));
Because float has limited precision (32 bits). Use double or decimal if you want more precision.
Please be aware that just blindly using Decimal isn't good enough.
Read the link posted by Oded: What Every Computer Scientist Should Know About Floating-Point Arithmetic
Only then decide on the appropriate numeric type to use.
Don't fall into the trap of thinking that just using Decimal will give you exact results; it won't always.
Consider the following code:
Decimal d1 = 1;
Decimal d2 = 101;
Decimal d3 = d1/d2;
Decimal d4 = d3*d2; // d4 = (d1/d2) * d2 = d1
if (d4 == d1)
{
Console.WriteLine("Yay!");
}
else
{
Console.WriteLine("Urk!");
}
If Decimal calculations were exact, that code should print "Yay!" because d1 should be the same as d4, right?
Well, it doesn't.
Also be aware that Decimal calculations are thousands of times slower than double calculations. They are not always suitable for non-currency calculations (e.g. calculating pixel offsets or physical things such as velocities, or anything involving transcendental numbers and so on).

Float is converting my values

I have a method that tests a value is within the range allowed on fields. If it is outside the range returns null and if inside returns the value.
internal float? ExtractMoneyInRangeAndPrecision(string fieldValue, string fieldName, float min, float max, int scale, int lineNumber)
{
float returnValue;
//Check whether valid float if
if (float.TryParse(fieldValue, out returnValue))
{
//Check whether in range
if (returnValue >= min && returnValue <= max)
{
int decPosition = 0;
decPosition = fieldValue.IndexOf('.');
if (
(decPosition == -1) ||
((decPosition != -1) && (fieldValue.Substring(decPosition, fieldValue.Length - decPosition).Length -1 <= scale))
)
{
return returnValue;
}
}
}
return null;
}
Here is my unit test:
[TestMethod()]
[DeploymentItem("ImporterEngine.dll")]
public void ExtractMoneyInRangeAndPrecisionTest_OutsideRange()
{
MockSyntaxValidator target = new MockSyntaxValidator("", 0);
string fieldValue = "1000000";
string fieldName = "";
float min = 1;
float max = 999999.99f;
int scale = 2;
int lineNumber = 0;
float? Int16RangeReturned;
Int16RangeReturned = target.ExtractMoneyInRangeAndPrecision(fieldValue, fieldName, min, max, scale, lineNumber);
Assert.IsNull(Int16RangeReturned);
}
As you can see the max is 999999.99 but when the method takes it in it changes it to 1,000,000
Why is this?
http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems
In short, because of the way floating-point numbers represent real numbers, the number you assign to a float is not always the number you get back out. The value you specify is converted to the nearest value that can be represented in scientific notation with a magnitude determined by a base of 2.
In the case of 999999.99, the nearest number that can be represented as a float with the same number of sig figs is 7.6293945 * 217 = 999999.99504, which when rounded to the same sig figs is 1,000,000.00. This may not be the EXACT case, but error like this is inherent in the use of floats.
Do not use floating-point types in situations where the accuracy of the number at a given level of precision is required. Instead, use the decimal type, which will retain the precision of values entered.
Not every string of digits can be converted to a float. Without checking, I would say that 999999.99 is one such number. A decimal would solve this.
The float type doesn't have enough precision to do what you want to do. I would recommend using the decimal type. Floats can be accurate to 7 decimal digits at most, and you're using 8 here. Decimal can have up to 28 digits, which is more than enough for any amount. Moreover, unlike float, the value the compiler uses and the value you write will always be the same.
Here's the long explanation:
Floats (single-precision floating-point numbers) are stored as an integer times a power of two, where the integer is in a certain range (between 2^23 and 2^24).
When you write a decimal number in your code, the compiler interprets this as the number in this form that is closest to the number you wrote. Sometimes the match is exact (99999.75). In other cases, your number needs to be rounded to the closest floating-point number. This is what happened here:
99999.99 = 2^19 * 1.907348613739013671875
= 2^19 * 2^-23 * (2^23 * 1.907348613739013671875)
= 2^-4 * 15999999.84
The closest integer to 15999999.84 is 16000000, so the rounded value is
(float)99999.99 = 2^-4 * 16000000
= 1000000
The big advantage of the decimal type is that is represented as a 96 bit integer times a power of 10, so decimal numbers with up to 28 digits can be represented exactly, without any rounding. What you see is what you get.
The biggest disadvantage of decimal is that it is significantly slower, but in a situation like this where you're converting strings to numbers, this is not a factor.
Floating-point types (as defined in C#) are approximate. For precision you should always use decimal.
From MSDN:
The decimal keyword indicates a 128-bit data type. Compared to floating-point types, the decimal type has more precision and a smaller range, which makes it appropriate for financial and monetary calculations. The approximate range and precision for the decimal type are shown in the following table.
http://msdn.microsoft.com/en-us/library/364x0z75.aspx
There seems to be some dispute about what qualifies as a floating-point type in C#. While decimal does qualify as a floating-point type by actual definition, it is not defined as such according to the MSDN specification.
http://msdn.microsoft.com/en-us/library/9ahet949.aspx

Categories

Resources