I have a variable of decimal type and I want to check the number of digits before decimal point in it.
What should I do? For example, 467.45 should return 3.
Solution without converting to string (which can be dangerous in case of exotic cultures):
static int GetNumberOfDigits(decimal d)
{
decimal abs = Math.Abs(d);
return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1);
}
Note, that this solution is valid for all decimal values
UPDATE
In fact this solution does not work with some big values, for example: 999999999999998, 999999999999999, 9999999999999939...
Obviously, the mathematical operations with double are not accurate enough for this task.
While searching wrong values I tend to use string-based alternatives proposed in this topic. As for me, that is the evidence that they are more reliable and easy-to-use (but be aware of cultures). Loop-based solutions can be faster though.
Thanks to commentators, shame on me, lesson to you.
Instead of converting to string, you can also divide the number by 10 until it equals 0. Interesting is, that the mathematical operations on decimals are much slower than converting the decimal to a string and returning the length (see benchmarks below).
This solution does not use the Math-methods that take a double as input; so all operations are done on decimals and no casting is involved.
using System;
public class Test
{
public static void Main()
{
decimal dec = -12345678912345678912345678912.456m;
int digits = GetDigits(dec);
Console.WriteLine(digits.ToString());
}
static int GetDigits(decimal dec)
{
decimal d = decimal.Floor(dec < 0 ? decimal.Negate(dec) : dec);
// As stated in the comments of the question,
// 0.xyz should return 0, therefore a special case
if (d == 0m)
return 0;
int cnt = 1;
while ((d = decimal.Floor(d / 10m)) != 0m)
cnt++;
return cnt;
}
}
Output is 29. To run this sample, visit this link.
Side note: some benchmarks show surprising results (10k runs):
while ((d = decimal.Floor(d / 10m)) != 0m): 25ms
while ((d = d / 10m) > 1m): 32ms
ToString with Math-double-operations: 3ms
ToString with decimal-operations: 3ms
BigInt (see answer of #Heinzi): 2ms
Also using random numbers instead of always the same value (to avoid possible caching of the decimal to string conversion) showed that the string-based methods are much faster.
I would try this:
Math.Truncate(467.45).ToString().Length
If you want to be sure not having some weird results for different cultures and with negative decimals, you better use this:
var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length
I would prefer the following instead of casting to int to ensure that you can also handle big numbers (e.g. decimal.MaxValue):
Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length
decimal d = 467.45M;
int i = (int)d;
Console.WriteLine(i.ToString(CultureInfo.InvariantCulture).Length); //3
As a method;
public static int GetDigitsLength(decimal d)
{
int i = int(d);
return i.ToString(CultureInfo.InvariantCulture).Length;
}
Note: Of course you should check first your decimals full part is bigger than Int32.MaxValue or not. Because if it is, you get an OverflowException.
Is such a case, using long instead of int can a better approach. However even a long (System.Int64) is not big enough to hold every possible decimal value.
As Rawling mentioned, your full part can hold the thousands separator and my code will be broken in such a case. Because in this way, it totally ignores my number contains NumberFormatInfo.NumberGroupSeparator or not.
That's why getting numbers only is a better approach. Like;
i.ToString().Where(c => Char.IsDigit(c)).ToArray()
Here's a recursive example (mostly for fun).
void Main()
{
digitCount(0.123M); //0
digitCount(493854289.543354345M); //10
digitCount(4937854345454545435549.543354345M); //22
digitCount(-4937854345454545435549.543354345M); //22
digitCount(1.0M); //1
//approximately the biggest number you can pass to the function that works.
digitCount(Decimal.MaxValue + 0.4999999M); //29
}
int digitCount(decimal num, int count = 0)
{
//divided down to last digit, return how many times that happened
if(Math.Abs(num) < 1)
return count;
return digitCount(num/10, ++count); //increment the count and divide by 10 to 'remove' a digit
}
Math.Floor(Math.Log10((double)n) + 1); is the way to go.
Converting to int is BAD because decimal may be bigger than int:
Decimal.MaxValue = 79,228,162,514,264,337,593,543,950,335;
Int32.MaxValue = 2,147,483,647; //that is, hexadecimal 0x7FFFFFFF;
Math.Floor(n).ToString().Count(); is bad because it may include thousands seperators.
If you have a bias towards smaller numbers, you can use something more simple like this.
It is split up into two methods, so the first method is smaller and can be inlined.
Performance is about the same as the solution with the Log10, but without the rounding errors. The Method using Log10, is still the fastest (a bit) specially for numbers > 1 million.
public static int CountNrOfDigitsIfs(decimal d) {
var absD = Math.Abs(d);
// 1
if (absD < 10M) return 1;
// 2
if (absD < 100M) return 2;
// 3
if (absD < 1000M) return 3;
// 4
if (absD < 10000M) return 4;
return CountNrOfDigitsIfsLarge(d);
}
private static int CountNrOfDigitsIfsLarge(decimal d) {
// 5
if (d < 100000M) return 5;
// 6
if (d < 1000000M) return 6;
// 7
if (d < 10000000M) return 7;
// 8
if (d < 100000000M) return 8;
// 9
if (d < 1000000000M) return 9;
// 10
if (d < 10000000000M) return 10;
// 11
if (d < 100000000000M) return 11;
// 12
if (d < 1000000000000M) return 12;
// 13
if (d < 10000000000000M) return 13;
// 14
if (d < 100000000000000M) return 14;
// 15
if (d < 1000000000000000M) return 15;
// 16
if (d < 10000000000000000M) return 16;
// 17
if (d < 100000000000000000M) return 17;
// 18
if (d < 1000000000000000000M) return 18;
// 19
if (d < 10000000000000000000M) return 19;
// 20
if (d < 100000000000000000000M) return 20;
// 21
if (d < 1000000000000000000000M) return 21;
// 22
if (d < 10000000000000000000000M) return 22;
// 23
if (d < 100000000000000000000000M) return 23;
// 24
if (d < 1000000000000000000000000M) return 24;
// 25
if (d < 10000000000000000000000000M) return 25;
// 26
if (d < 100000000000000000000000000M) return 26;
// 27
if (d < 1000000000000000000000000000M) return 27;
// 28
if (d < 10000000000000000000000000000M) return 28;
return 29; // Max nr of digits in decimal
}
This code is generated using the following T4 template:
<#
const int SIGNIFICANT_DECIMALS = 29;
const int SPLIT = 5;
#>
namespace Study.NrOfDigits {
static partial class DigitCounter {
public static int CountNrOfDigitsIfs(decimal d) {
var absD = Math.Abs(d);
<#
for (int i = 1; i < SPLIT; i++) { // Only 29 significant digits
var zeroes = new String('0', i);
#>
// <#= i #>
if (absD < 1<#= zeroes #>M) return <#= i #>;
<#
}
#>
return CountNrOfDigitsIfsLarge(d);
}
private static int CountNrOfDigitsIfsLarge(decimal d) {
<#
for (int i = SPLIT; i < SIGNIFICANT_DECIMALS; i++) { // Only 29 significant digits
var zeroes = new String('0', i);
#>
// <#= i #>
if (d < 1<#= zeroes #>M) return <#= i #>;
<#
}
#>
return <#= SIGNIFICANT_DECIMALS #>; // Max nr of digits in decimal
}
}
}
So, I've run into this before, and solved it with this code:
SqlDecimal d = new SqlDecimal(467.45M);
int digits = d.Precision - d.Scale;
SqlDecimal is part of the System.Data.SqlTypes namespace. "Precision" is the total number of significant digits, while "Scale" is the number of digits after the decimal point.
Now, I know one objection to going this route is that SqlDecimal is part of the SQL Server-specific code. It's a valid point, but I would also point out that it's a part of the .NET framework itself, and has been since at least version 1.1, so it seems like it would be still be applicable no matter what the code around it is doing.
I looked under the hood with a decompiler (JetBrains' dotPeek in this instance), to see if maybe the code for calculating precision and scale could be easily extracted and just used, without pulling in SqlDecimal. The code to calculate scale is very simple, but the method to calculate precision is non-trivial, so if it were me, I'd just go through SqlDecimal.
This will do if you really don't want to use the Log method (which IMO is the best way). It's about the clearest way I can think of of doing this using ToString():
Math.Abs(val).ToString("f0", CultureInfo.InvariantCulture).Length
Or alternatively, if you don't want to count 0.123M as having one digit:
Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length
You could use ToString function with a custom format.
Decimal value = 467.45m;
int count = Math.Abs(value).ToString("#", System.Globalization.CultureInfo.InvariantCulture).Length;
The # specify you only want the characters before the .
The System.Globalization.CultureInfo.InvariantCulture ensure you won't get any formating from the Region Option.
This answer is pretty much lifted from Calculate System.Decimal Precision and Scale but with a minor change to fit the question asked.
class Program
{
static void Main()
{
decimal dec = 467.45m;
Console.WriteLine(dec.GetNumberOfDigitsBeforeDecimalPlace());
}
}
public static class DecimalEx
{
public static int GetNumberOfDigitsBeforeDecimalPlace(this decimal dec)
{
var x = new System.Data.SqlTypes.SqlDecimal(dec);
return x.Precision - x.Scale;
}
}
Also if you want to do it without using the SqlDecimal class check out Jon Skeet's answer for the same question.
var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
var count = d.ToString().TakeWhile(c => c != sep).Count();
The mathematical way of doing this (and probably the fastest) is to get a logarytm of base 10 of a absolute value of this number and round it
up.
Math.Floor(Math.Log10(Math.Abs(val)) + 1);
TLDR all the other answers. I wrote this in PHP, and the math would be the same. (If I knew C# I'd have written in that language.)
$input=21689584.999;
$input=abs($input);
$exp=0;
do{
$test=pow(10,$exp);
if($test > $input){
$digits=$exp;
}
if($test == $input){
$digits=$exp+1;
}
$exp++;
}while(!$digits);
if($input < 1){$digits=0;}
echo $digits;
I don't doubt there's a better way, but I wanted to throw in my $.02
EDIT:
I php-ized the code I mentioned in my comments, but did away with the int conversion.
function digitCount($input){
$digits=0;
$input=abs($input);
while ($input >= 1) {
$digits++;
$input=$input/10;
//echo $input."<br>";
}
return $digits;
}
$big=(float)(PHP_INT_MAX * 1.1);
echo digitCount($big);
Use modulo, i'm not a C# programmer, but I'm pretty sure this solution work:
double i = 1;
int numberOfDecimals = 0;
while (varDouble % i != varDouble)
{
numberOfDecimals++;
i*=10;
}
return numberOfDecimals;
This would be the Java solution
public class test {
public static void main(String args[]) {
float f = 1.123f;
int a = (int) f;
int digits = 0;
while (a > 0) {
digits++;
a=a/10;
}
System.out.println("No Of digits before decimal="+digits);
}
}
If you treat zeros or lack of zeroes as 1 number, this is OK. If you want zero to return zero or lack of zero to return zero, then there are a few edge cases to work out which shouldn't be too hard to add. Also, should Absolute value to handle negative numbers. Added that test case as well.
const decimal d = 123.45m;
const decimal d1 = 0.123m;
const decimal d2 = .567m;
const decimal d3 = .333m;
const decimal d4 = -123.45m;
NumberFormatInfo currentProvider = NumberFormatInfo.InvariantInfo;
var newProvider = (NumberFormatInfo) currentProvider.Clone();
newProvider.NumberDecimalDigits = 0;
string number = d.ToString("N", newProvider); //returns 123 = .Length = 3
string number1 = d1.ToString("N", newProvider); //returns 0 = .Length = 1
string number2 = d2.ToString("N", newProvider); //returns 1 = .Length = 1
string number3 = d3.ToString("N", newProvider); //returns 0 = .Length = 1
string number4 = Math.Abs(d4).ToString("N", newProvider); //returns 123 = .Length = 3
Here's a somewhat final solution, if you find a test case that doesn't work, let me know. It should return 3,0,0,0,3 for the test cases provided.
public static int NumbersInFrontOfDecimal(decimal input)
{
NumberFormatInfo currentProvider = NumberFormatInfo.InvariantInfo;
var newProvider = (NumberFormatInfo)currentProvider.Clone();
newProvider.NumberDecimalDigits = 0;
var absInput = Math.Abs(input);
var numbers = absInput.ToString("N", newProvider);
//Handle Zero and < 1
if (numbers.Length == 1 && input < 1.0m)
{
return 0;
}
return numbers.Length;
}
Here is my optimized version of the code inspired by Gray's answer:
static int GetNumOfDigits(decimal dTest)
{
int nAnswer = 0;
dTest = Math.Abs(dTest);
//For loop version
for (nAnswer = 0; nAnswer < 29 && dTest > 1; ++nAnswer)
{
dTest *= 0.1M;
}
//While loop version
//while (dTest > 1)
//{
// nAnswer++;
// dTest *= 0.1M;
//}
return (nAnswer);
}
If you don't want the Math.Abs to be called inside this function then be sure to use it
outside the function on the parameter before calling GetNumOfDigits.
I decided to remove the other codes to reduce clutter in my answer, even though they helped me get to this point...
If there is any improvements needed, then let me know and I will update it :).
In order to get an accurate and culturally agnostic answer I do the following:
Use System.Numerics.BigInteger, whose constructor accepts a decimal and doesn't seem to produce any rounding errors.
Use BigInteger.Abs() to remove any sign.
Use BigInteger.ToString() with the "#" format to suppress any separators that might occur.
Code
decimal num = 123213123.123123M;
int length = BigInteger.Abs((BigInteger)num).ToString("#").Length;
You could do this by rounding the number, then getting the length of the new number. You could do it like this:
var number = 476.43;
var newNumber = Math.round(number);
//get the length of the rounded number, and subtract 1 if the
//number is negative (remove the negative sign from the count)
int digits = newNumber.ToString().Length - (number < 0 ? 1 : 0);
The other solutions will lose digits if the number is too large.
public int Digits(Decimal i)
{
NumberFormatInfo format = CultureInfo.CurrentCulture.NumberFormat;
var str = Math.Abs(i).ToString().Replace(format.NumberGroupSeparator, "");
var index = str.IndexOf(format.NumberDecimalSeparator);
var digits = index == -1 ? str.Length : index;
}
Algorithm:
Convert |decimal| to String.
If "." exist in the decimal, cut before it, otherwise consider the whole number.
Return string length.
Example:
3.14 --> 3.14 --> "3.14" --> "3.14".Substring(0,1) --> "3".Length --> 1
-1024 --> 1024 --> "1024" --> IndexOf(".") = -1 --> "1024" --> 4
Code:
static int getNumOfDigits (decimal num)
{
string d = Math.Abs(num).ToString();
if (d.IndexOf(".") > -1)
{
d = d.Substring(0, d.IndexOf("."));
}
return d.Length;
}
I haven't tested this but I would keep it straightforward and do:
decimal value = 467.45;
string str = Convert.toString(value); // convert your decimal type to a string
string[] splitStr = str.split('.'); // split it into an array (use comma separator assuming you know your cultural context)
Console.WriteLine(splitStr[0].Count); // get the first element. You can also get the number of figures after the point by indexing the next value in the array.
This does not handle negative numbers. If you care about those then considering taking the absolute value. Furthermore, if you want 0 before the decimal place to not be counted then you can use a simple if statement to check it.
simple :
string value = "467.45";
int count = value.split('.')[0] == "0" ? 0 : value.split('.')[0].ToString().Replace("-","").Count();
Related
I have encountered this weird behaviour, which is probably best described by a small example:
Random R = new Random();
for (int i = 0; i < 10_000; i++)
{
double d = R.NextDouble() * uint.MaxValue;
}
Now, the last digit of d before the decimal mark is always even, i.e. int r = (int) (d % 10) is always 0, 2, 4, 6, or 8. There are odd digits on either side, though.
I suspected that multiplying with uint.MaxValue (2^32 - 1) could force some rounding error in the last digits, but since double has more than 50 bits of precision, this should beat uint with about 20 bits to spare after the separator. This behaviour also occurs if I explicitly store uint.MaxValue as a double before the multiplication and use that instead.
Can someone shed any light on this?
This is a deficiency in the .Net Random class.
If you inspect the source code you will see the following comment in the implementation of the private method GetSampleForLargeRange():
// The distribution of double value returned by Sample
// is not distributed well enough for a large range.
// If we use Sample for a range [Int32.MinValue..Int32.MaxValue)
// We will end up getting even numbers only.
This is used in the implementation of Next():
public virtual int Next(int minValue, int maxValue) {
if (minValue>maxValue) {
throw new ArgumentOutOfRangeException("minValue",Environment.GetResourceString("Argument_MinMaxValue", "minValue", "maxValue"));
}
Contract.EndContractBlock();
long range = (long)maxValue-minValue;
if( range <= (long)Int32.MaxValue) {
return ((int)(Sample() * range) + minValue);
}
else {
return (int)((long)(GetSampleForLargeRange() * range) + minValue);
}
}
But it is NOT used for the values returned from NextDouble() (which just returns the value returned from Sample().
So the answer is that NextDouble() is not well-distributed.
You can use RNGCryptoServiceProvider to generate better random numbers, but it's a bit of a fiddle to create the double. From this answer:
static void Main()
{
var R = new RNGCryptoServiceProvider();
var bytes = new Byte[8];
for (int i = 0; i < 10_000; i++)
{
R.GetBytes(bytes);
var ul = BitConverter.ToUInt64(bytes, 0) / (1 << 11);
var d = ul / (double)(1UL << 53);
d *= uint.MaxValue;
Console.WriteLine(d);
}
}
Is it possible in C# to format a double value with double.ToString in a way that I have always a fixed number of digits, no matter on which side of the decimal point?
Say I wish 6 digits, I want to have these results:
0.00123456789 gives "0.00123"
1.23456789 gives "1.23457"
123.456789 gives "123.457"
0.0000000123456789 gives "0.00000"
12345678.9 gives "12345679" (on overflow I want to see all digits left of decimalpoint)
4.2 gives "4.20000"
I'm experimenting with double.ToString, but cannot find any suitable format string.
Already tried "G6" (gives sometimes exponential format), "F6" (comes close, but 0.123456789 gives "0.123457" which are 7 digits).
I think some of your examples are wrong.
But I still think that I understand what you want to achieve.
I made an extension method.
public static class StringExtensionMethods
{
public static string ToString(this double d, int numberOfDigits)
{
var result = "";
// Split the number.
// Delimiter can vary depending on locale, should consider this and not use "."
string[] split = d.ToString().Split(new string[] { "." }, StringSplitOptions.None);
if(split[0].Count() >= numberOfDigits)
{
result = split[0].Substring(0, numberOfDigits);
}
else
{
result = split[0];
result += ".";
result += split[1];
// Add padding.
while(result.Count() < numberOfDigits +1)
result += "0";
result = result.Substring(0, numberOfDigits + 1);
}
return result;
}
}
I ran it with your examples:
double d0 = 0.00123456789;
double d1 = 1.23456789;
double d2 = 123.456789;
double d3 = 0.0000000123456789;
double d4 = 12345678.9;
double d5 = 4.2;
Console.WriteLine(d0.ToString(6));
Console.WriteLine(d1.ToString(6));
Console.WriteLine(d2.ToString(6));
Console.WriteLine(d3.ToString(6));
Console.WriteLine(d4.ToString(6));
Console.WriteLine(d5.ToString(6));
This is the output:
0.00123
1.23456
123.456
1.23456
123456
4.20000
I don't think this is the best way to solve it, but I like extension methods.
DoubleConverter class: http://1drv.ms/1yEbvL4
If your goal is to avoid "jumping" of the decimal point:
Use g formating, this does the most sensible thing to do
See where the decimal point is in your resulting string
pad with spaces at the beginning to align the column at the decimal point
As I understand, there is no predefined format that does what I need. So for everyone who is interested, here is the function I ended up with:
public string FormatValue(double d, int noOfDigits)
{
double abs = Math.Abs(d);
int left = abs < 1 ? 1 : (int)(Math.Log10(abs) + 1);
int usedDigits = 0;
StringBuilder sb = new StringBuilder();
for(; usedDigits < left; usedDigits++)
{
sb.Append("0");
}
if(usedDigits < noOfDigits)
{
sb.Append(".");
for(; usedDigits < noOfDigits; usedDigits++)
{
sb.Append("0");
}
}
return d.ToString(sb.ToString());
}
Can anyone suggest me a way to multiply decimal numbers without using multiplication(*) sign.
I know it looks like homework thing, but I just want to know how to achieve this. I have already done this for positive and negative integers as below:
int first = 2;
int second =2;
int result = 0;
bool isNegative = false;
if (first < 0)
{
first = Math.Abs(first);
isNegative = true;
}
if (second < 0)
{
second = Math.Abs(second);
isNegative = true;
}
for (int i = 1; i <= second; i++)
{
result += first;
}
if (isNegative)
result = -Math.Abs(result);
Want to multiply this for decimals:
decimal third = 1.1;
decimal fourth = 1.2;
Thanks
Bit of a cheat, but if the task strictly relates to all forms of multiplication (rather than just the * operator), then divide by the reciprocal:
var result = first / (1 / (decimal)second);
Just another way ;)
if (second < 0)
{
second = Math.Abs(second);
first = (-1) * first;
}
result = Enumerable.Repeat(first, second).Sum();
The simplest way to multiple two decimals without using the * operator is to use the Decimal.Multiply method.
Example:
decimal first = -2.234M;
decimal second = 3.14M;
decimal product = Decimal.Multiply(first, second);
Strictly with out '*' ? I'd take note of the XOR operator implemented. Although I know this code is not much different to the OP's.
int first = 2;
int second =2;
int result = 0;
bool isNegative;
isNegative = (first<0)^(second<0);
first = (first<0)?Math.Abs(first):first;
second = (second<0)?Math.Abs(second):second;
for (int i = 1; i <= second; i++)
result += first;
result = (isNegative)?-Math.Abs(result):result;
Here's another approach, using rules of logs, for the sake of completeness:
decimal result = (decimal)Math.Exp(
Math.Log((double)third) + Math.Log((double)fourth));
It's not going to work for all decimals (in particular not for negative numbers, although it could be extended to detect these and work around them), but it's interesting nevertheless.
In my C# program I have a double obtained from some computation and its value is something like 0,13999 or 0,0079996 but this value has to be presented to a human so it's better displayed as 0,14 or 0,008 respectively.
So I need to round the value, but have no idea to which precision - I just need to "throw away those noise digits".
How could I do that in my code?
To clarify - I need to round the double values to a precision that is unknown at compile time - this needs to be determined at runtime. What would be a good heuristic to achieve this?
You seem to want to output a value which is not very different to the input value, so try increasing numbers of digits until a given error is achieved:
static double Round(double input, double errorDesired)
{
if (input == 0.0)
return 0.0;
for (int decimals = 0; decimals < 17; ++decimals)
{
var output = Math.Round(input, decimals);
var errorAchieved = Math.Abs((output - input) / input);
if (errorAchieved <= errorDesired)
return output;
}
return input;
}
}
static void Main(string[] args)
{
foreach (var input in new[] { 0.13999, 0.0079996, 0.12345 })
{
Console.WriteLine("{0} -> {1} (.1%)", input, Round(input, 0.001));
Console.WriteLine("{0} -> {1} (1%)", input, Round(input, 0.01));
Console.WriteLine("{0} -> {1} (10%)", input, Round(input, 0.1));
}
}
private double PrettyRound(double inp)
{
string d = inp.ToString();
d = d.Remove(0,d.IndexOf(',') + 1);
int decRound = 1;
bool onStartZeroes = true;
for (int c = 1; c < d.Length; c++ )
{
if (!onStartZeroes && d[c] == d[c - 1])
break;
else
decRound++;
if (d[c] != '0')
onStartZeroes = false;
}
inp = Math.Round(inp, decRound);
return inp;
}
Test:
double d1 = 0.13999; //no zeroes
double d2 = 0.0079996; //zeroes
double d3 = 0.00700956; //zeroes within decimal
Response.Write(d1 + "<br/>" + d2 + "<br/>" + d3 + "<br/><br/>");
d1 = PrettyRound(d1);
d2 = PrettyRound(d2);
d3 = PrettyRound(d3);
Response.Write(d1 + "<br/>" + d2 + "<br/>" + d3 +"<br/><br/>");
Prints:
0,13999
0,0079996
0,00700956
0,14
0,008
0,007
Rounds your numbers as you wrote in your example..
I can think of a solution though it isn't very efficient...
My assumption is that you can tell when a number is in the "best" human readable format when extra digits make no difference to how it is rounded.
eg in the example of 0,13999 rounding it to various numbers of decimal places gives:
0
0.1
0.14
0.14
0.14
0.13999
I'd suggest that you could loop through and detect that stable patch and cut off there.
This method seems to do this:
public double CustomRound(double d)
{
double currentRound = 0;
int stability = 0;
int roundLevel = 0;
while (stability < 3)
{
roundLevel++;
double current = Math.Round(d, roundLevel);
if (current == currentRound)
{
stability++;
}
else
{
stability = 1;
currentRound=current;
}
}
return Math.Round(d, roundLevel);
}
This code might be cleanable but it does the job and is a sufficient proof of concept. :)
I should emphasise that that initial assumption (that no change when rounding) is the criteria we are looking at which means that something like 0.3333333333 will not get rounded at all. With the examples given I'm unable to say if this is correct or not but I assume if this is a double issues that the problem is with the very slight variations from the "right" value and the value as a double.
Heres what I tried:
public decimal myRounding(decimal number)
{
double log10 = Math.Log10((double) number);
int precision = (int)(log10 >= 0 ? 0 : Math.Abs(log10)) + (number < 0.01m ? 1 : 2);
return Math.Round(number, precision);
}
test:
Console.WriteLine(myRounding(0.0000019999m)); //0.000002
Console.WriteLine(myRounding(0.0003019999m)); //0.0003
Console.WriteLine(myRounding(2.56777777m)); //2.57
Console.WriteLine(myRounding(0.13999m)); //0.14
Console.WriteLine(myRounding(0.0079996m)); //0.008
You can do it without converting to string. This is what I created fast:
private static double RoundDecimal(double number)
{
double temp2 = number;
int temp, counter = 0;
do
{
temp2 = 10 * temp2;
temp = (int)temp2;
counter++;
} while (temp < 1);
return Math.Round(number, counter < 2 ? 2 : counter);
}
or
private static double RoundDecimal(double number)
{
int counter = 0;
if (number > 0) {
counter = Math.Abs((int) Math.Log10(number)) + 1;
return Math.Round(arv, counter < 2 ? 2 : counter);
}
After giving it another thought I did the following and looks like it does what I want so far.
I iterate over the number of digits and compare Round( value, number ) and Round( value, number + 1 ). Once they are equal (not == of course - I compare the difference against a small number) then number is the number of digits I'm looking for.
Double.ToString() can take a string format as an argument. This will display as many characters as you require, rounding to the decimal place. E.G:
double Value = 1054.32179;
MessageBox.Show(Value.ToString("0.000"));
Will display "1054.322".
Source
Generic formats (i.e, pre-generated)
How to generate custom formats
You can use no of digits with Math.Round Function
Double doubleValue = 4.052102;
Math.Round(doubleValue, 2);
This will return 4.05 as your required answer.
This is tested code, can u explain me how i am wrong. So i need to change.
I have a situation that I cannot change: one database table (table A) accepts 6 decimal places, while a related column in a different table (table B) only has 3 decimal places.
I need to copy from A to B, but if A has more than 3 decimal places the extra data will be lost. I cant change the table definition but I can add a workaround. So I'm trying to find out how to check if a decimal has more than 3 decimal places or not?
eg
Table A
Id, Qty, Unit(=6dp)
1, 1, 0.00025
2, 4000, 0.00025
Table B
Id, TotalQty(=3dp)
I want to be able to find out if Qty * Unit from Table A has more than 3 decimals (row 1 would fail, row 2 would pass):
if (CountDecimalPlaces(tableA.Qty * tableA.Unit) > 3)
{
return false;
}
tableB.TotalQty = tableA.Qty * tableA.Unit;
How would I implement the CountDecimalPlaces(decimal value) {} function?
You could compare the value of the number rounded to 3 decimal places with the original value.
if (Decimal.Round(valueDecimal, 3) != valueDecimal)
{
//Too many decimals
}
This works for 3 decimal places, and it can be adapted for a generic solution:
static bool LessThan3DecimalPlaces(decimal dec)
{
decimal value = dec * 1000;
return value == Math.Floor(value);
}
static void Test()
{
Console.WriteLine(LessThan3DecimalPlaces(1m * 0.00025m));
Console.WriteLine(LessThan3DecimalPlaces(4000m * 0.00025m));
}
For a real generic solution, you'll need to "deconstruct" the decimal value in its parts - take a look at Decimal.GetBits for more information.
Update: this is a simple implementation of a generic solution which works for all decimals whose integer part is less than long.MaxValue (you'd need something like a "big integer" for a trully generic function).
static decimal CountDecimalPlaces(decimal dec)
{
Console.Write("{0}: ", dec);
int[] bits = Decimal.GetBits(dec);
ulong lowInt = (uint)bits[0];
ulong midInt = (uint)bits[1];
int exponent = (bits[3] & 0x00FF0000) >> 16;
int result = exponent;
ulong lowDecimal = lowInt | (midInt << 32);
while (result > 0 && (lowDecimal % 10) == 0)
{
result--;
lowDecimal /= 10;
}
return result;
}
static void Foo()
{
Console.WriteLine(CountDecimalPlaces(1.6m));
Console.WriteLine(CountDecimalPlaces(1.600m));
Console.WriteLine(CountDecimalPlaces(decimal.MaxValue));
Console.WriteLine(CountDecimalPlaces(1m * 0.00025m));
Console.WriteLine(CountDecimalPlaces(4000m * 0.00025m));
}
This is a very simple one line code to get count of decimals in a Decimal:
decimal myDecimal = 1.000000021300010000001m;
byte decimals = (byte)((Decimal.GetBits(myDecimal)[3] >> 16) & 0x7F);
Multiplying a number with 3 decimal places by 10 to the power of 3 will give you a number with no decimal places. It's a whole number when the modulus % 1 == 0. So I came up with this...
bool hasMoreThanNDecimals(decimal d, int n)
{
return !(d * (decimal)Math.Pow(10, n) % 1 == 0);
}
Returns true when n is less than (not equal) to the number of decimal places.
The basics is to know how to test if there are decimal places, this is done by comparing the value to its rounding
double number;
bool hasDecimals = number == (int) number;
Then, to count 3 decimal places, you just need to do the same for your number multiplied by 1000:
bool hasMoreThan3decimals = number*1000 != (int) (number * 1000)
All of the solutions proposed so far are not extensible ... fine if you are never going to check a value other than 3, but I prefer this because if the requirement changes the code to handle it is already written. Also this solution wont overflow.
int GetDecimalCount(decimal val)
{
if(val == val*10)
{
return int.MaxValue; // no decimal.Epsilon I don't use this type enough to know why... this will work
}
int decimalCount = 0;
while(val != Math.Floor(val))
{
val = (val - Math.Floor(val)) * 10;
decimalCount++;
}
return decimalCount;
}
carlosfigueira solution will need to check for 0 otherwise "while ((lowDecimal % 10) == 0)" will produce an infinity loop when called with dec = 0
static decimal CountDecimalPlaces(decimal dec)
{
if (dec == 0)
return 0;
int[] bits = Decimal.GetBits(dec);
int exponent = bits[3] >> 16;
int result = exponent;
long lowDecimal = bits[0] | (bits[1] >> 8);
while ((lowDecimal % 10) == 0)
{
result--;
lowDecimal /= 10;
}
return result;
}
Assert.AreEqual(0, DecimalHelper.CountDecimalPlaces(0m));
Assert.AreEqual(1, DecimalHelper.CountDecimalPlaces(0.5m));
Assert.AreEqual(2, DecimalHelper.CountDecimalPlaces(10.51m));
Assert.AreEqual(13, DecimalHelper.CountDecimalPlaces(10.5123456978563m));
One more option based on #RodH257's solution, but reworked as an extension method:
public static bool HasThisManyDecimalPlacesOrLess(this decimal value, int noDecimalPlaces)
{
return (Decimal.Round(value, noDecimalPlaces) == value);
}
You can then call that as:
If !(tableA.Qty * tableA.Unit).HasThisManyDecimalPlacesOrLess(3)) return;
There is probably a more elegant way to do this, but off the top of my head I would try
a = multiply by 1000
b = truncate a
if (b != a) then there is additional precision that has been lost
bool CountDecimalPlaces(decimal input)
{
return input*1000.0 == (int) (input*1000);
}
Here is my version:
public static int CountDecimalPlaces(decimal dec)
{
var a = Math.Abs(dec);
var x = a;
var count = 1;
while (x % 1 != 0)
{
x = a * new decimal(Math.Pow(10, count++));
}
var result = count - 1;
return result;
}
I tried first #carlosfigueira/#Henrik Stenbæk, but their version does not work with 324000.00m
TEST:
Console.WriteLine(CountDecimalPlaces(0m)); //0
Console.WriteLine(CountDecimalPlaces(0.5m)); //1
Console.WriteLine(CountDecimalPlaces(10.51m)); //2
Console.WriteLine(CountDecimalPlaces(10.5123456978563m)); //13
Console.WriteLine(CountDecimalPlaces(324000.0001m)); //4
Console.WriteLine(CountDecimalPlaces(324000.0000m)); //0
could you convert it to a string and just do a len function or would that not cover your situation?
follow up question:
would 300.4 be ok?
Public Function getDecimalCount(decWork As Decimal) As Integer
Dim intDecimalCount As Int32 = 0
Dim strDecAbs As String = decWork.ToString.Trim("0")
intDecimalCount = strDecAbs.Substring(strDecAbs.IndexOf(".")).Length -1
Return intDecimalCount
End Function