3

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).

11
  • 1
    As stated, X and Y would be 3 and 6 for an input of either 3.6 or 3.00006. Thus, X and Y fail to convey complete information about the input. Is that what you want? Commented Dec 26, 2018 at 12:00
  • For what purpose do you want this? There may be a better way to accomplish the ultimate purpose than by splitting a number according to its decimal numeral properties this way. Commented Dec 26, 2018 at 12:09
  • 1
    You warn: "For performance reasons, I am NOT interested in ANY string manipulation-based approaches; please do not recommend such a method.". But note that such an approach may be lot faster than a pure numeric approach to turn 0.xyz in xyz. Commented Dec 26, 2018 at 16:51
  • @EricPostpischil The system that produces the numbers never generates numbers like 3.000...0006. However, even if it generates, it is totally fine to consider 3 & 6 regardless of the number being 3.6 or 3.0006. Commented Dec 26, 2018 at 18:26
  • @RudyVelthuis No, because that has two drawbacks: (a) a X-digit number stored in 64bit object is represented by a X*8 bit string object (bigger memory consumption and footprint), and (2) instead of 1-2 operations/comparisons you would do on a number, you would need X char comparison and one iteration. It might be saftly neglectable in small-scale applications, but in my challenge, this operation will be used millions (or even billions) of times, which has to reply in ~5sec on a common laptop. Commented Dec 26, 2018 at 18:35

3 Answers 3

6

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));
19
  • One for the different perspective.
    – M.Armoun
    Commented Dec 26, 2018 at 6:48
  • Instead of the loop, you can do this: int y = (int)(dPart * (decimal)Math.Pow(10, count)); Commented Dec 26, 2018 at 6:50
  • 4
    @Hamed Math.Pow has worse perfomance. Also, you can store 10, 100, 1000... in array, and do not waste time at all
    – Backs
    Commented Dec 26, 2018 at 7:14
  • 1
    @MohamadArmoon: Of course IEEE-754 binary floating-point formats have scaling. The whole point of floating-point is that the point floats, meaning that there is a scale. The problem with applying your request to binary floating-point would be that most “decimal” numbers like “123.456” are not represented in binary floating-point. For example, in the IEEE-754 basic 32-bit binary floating-point format, the closest representable value is 123.45600128173828125. So it would be impossible to input 123.456 in a binary floating-point format. Commented Dec 26, 2018 at 12:07
  • 2
    @MohamadArmoon: That also illustrates a reason people may be suspicious of yur question. 123.456 and 123.4560000000001 are very close mathematically, yet they produce very different outputs. That suggests you are doing something that does not make sense in many contexts. Commented Dec 26, 2018 at 12:08
2

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
6
  • Thanks! Would it be possible to elaborate on why you AND with 31? Commented Dec 27, 2018 at 2:03
  • 1
    That is due to the format of a Decimal (this is documented). bits[3] is a 32 bit integer. that contains 16 bits reserved, 5 bits scale, some 10 bits more reserved and as top bit the sign bit. To get the scale, you must get at bits 16..20, so you must shift right by 16 and then mask with 31 (or 0x1F). The scale must have only values in the range 0..28. Commented Dec 27, 2018 at 5:23
  • C# Decimal values are not guaranteed to be normalized. 123.456 may be represented with different values of the exponent field, so that this code might produce 456, 4560, 45600, and so on. Commented Dec 27, 2018 at 13:23
  • @Eric: There is only one representation for 123.456, i.e. 123456 x 10^-3. But you can also represent 123.4560, as 1234560 x 10^-4. IOW, the number of fractional digits is remembered and kept as much as possible. So, yes, this code might produce values like 456 or 4560 for the decPart. But that is still a lot better than 4560000000000030695446184836328029632568359375, which Double would get you. <g> Commented Dec 27, 2018 at 18:42
  • @RudyVelthuis: 123.456 is a number. “123.456” and “123.4560” are numerals, which are themselves ways of representing 123.456. Many numbers have multiple representations in C# decimal. Representations of 123.456 in decimal include (0, 123456, 3) and (0, 1234560, 4) (where the three numbers indicate the contents of the sign, numeric value, and scaling factor fields of a decimal). Those encodings represent the same value, 123.456, so, yes, 123.456 does have multiple representations. Those representations also convey what Microsoft calls “trailing zeros”, but those do not affect the value. Commented Dec 27, 2018 at 20:19
-5

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.

2
  • The question specifically says For performance reasons, I am NOT interested in string manipulation-based approaches.
    – Steve
    Commented Aug 9, 2021 at 10:16
  • And add to that, the decimal point is locale-specific. Commented Sep 29, 2021 at 18:03

Not the answer you're looking for? Browse other questions tagged or ask your own question.