0

In .NET, I have a value in a double variable that I need to convert to decimal with a specified number of decimal places, rounding as needed. The answer I am looking for would have a prototype something like this:

decimal DoubleToDecimal(double value, int numberOfDecimalPlaces)

The best I have been able to come up with converts the double to a string with the correct number of decimal places, and then parses it back into a decimal:

return decimal.Parse(
    double.ToString("0." + new string(numberOfDecimalPlaces,'0'))
);

I would prefer a way that doesn't involve the conversion to/from string, as that seems quite inefficient.

2 Answers2

2
decimal DoubleToDecimal(double value, int numberOfDecimalPlaces){
    return Math.Round((decimal)value, numberOfDecimalPlaces);
}

If you need to preserve trailing zeroes try using this:

Math.Round((decimal)value,numberOfDecimalPlaces)+(0M*((decimal)Math.Pow(10,-numberOfDecimalPlaces)))

see:

> Math.Round(dd,20)
3.2222222
> (0M*((decimal)Math.Pow(10,-20)))
0.00000000000000000000
> Math.Round(dd,20)+(0M*((decimal)Math.Pow(10,-20)))
3.22222220000000000000
Bogdan Mart
  • 460
  • 8
  • 19
  • **Juste a little remark**: create a method for that is *useless* here, and adds a CPU Proc Call and a CPU Stack Push & Pop for nothing, hence *that slows the execution* just for a wrapper... unless one wants a wrapper. –  Jul 12 '20 at 22:30
  • @olivier-rogier I would agree, but the original question asked in terms of such a method. And I've updated my answer with more complex code preserving trailing zeroes, and it makes sense to move into a method. – Bogdan Mart Jul 12 '20 at 22:43
  • Indeed, you're right to offer what's required. It was just a little remark in case if :) –  Jul 12 '20 at 22:46
  • For what it's worth, a tiny little wrapper method would very likely just get inlined by the compiler – Flydog57 Jul 12 '20 at 23:43
  • Never assume such thing: VS C# compiler does not do optimization with *inlining methods* by repeating code => release binary: `IL_000a: call valuetype [mscorlib]System.Decimal ConsoleApp.Program::DoubleToDecimal(float64, int32)`. So for just a useless wrapper, unless you want that, this can be an acceptable design choice to detach a need from an implementation, you have one CPU Proc Call thus one CPU Stack Push before and one CPU Stack Pop after the call. [Intel® 64 and IA-32 Architectures Software Developer Manuals](https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html) –  Jul 13 '20 at 09:11
  • Errata: one push for every parameter, and one pop for the result if not void and one for every out params. Thus here there is two pushes in the stack: [ldc.r8](https://learn.microsoft.com/dotnet/api/system.reflection.emit.opcodes.ldc_r8) & [ldc.i4](https://learn.microsoft.com/dotnet/api/system.reflection.emit.opcodes.ldc_i4). –  Jul 13 '20 at 09:32
  • Such a discussion under this question, I hope this would not confuse newbie programmers. I'll put in my own two cents. – Bogdan Mart Jul 13 '20 at 09:44
  • Indeed C# compiler can not inline anything, but CLR and JIT could(!). I actually tested before and it wasn't inlining for me, so I thought it's really seldom case for internal classes. it could be actually influenced by developer: https://stackoverflow.com/a/12303978/716769 So I've tested this code on sharplab and I was surprised that it actually inline simple method, and going extra mile: it replaces Math.Round with Decimal.Round. You could check this here (need to post in the second message due to limit) – Bogdan Mart Jul 13 '20 at 09:52
  • https://sharplab.io/#v2:C4LghgzgtgPgAgJgIwFgBQcDMACR2DC2A3utmbjgCYCmAxgJZRgA22AsgBSUD2ArgEbNq2SgEpipclLgB2bABE+g6gBVu8uoxZcANNgCsogNySyAX1MURmpqzYIuSoSPEk0U6XMUChajQ1sHSj1DE3dyC3CySxoAlgUnVXUbbR4fYQA3Fl5qPXoAO2BsfN4ofmoAJwB5ADN/LWYABWYwWmoIUTcPMll2MGAACwA6ACU+fMoOLhTmUSzmHL0Sssra+ttm1vbjS0ipSxiZhPS/GaDE7HnF7AKi5fLqupnNto6u7t62fuGx3gmp2INObZXL3VZPOJNFqvUQAaks3WwHAADGwAFQAmaiL6DIaNbgAdw4SGROgAtGDHusWC9tqIdlFsHtyAc0GYgA – Bogdan Mart Jul 13 '20 at 09:53
  • And regarding stack usage, on x86 (32 bit) it is true that arguments would be pushed to stack, but on x86_64 it would use registers, stack would only be used for return address. ``` L0003: mov r9d, 5 L0009: mov rax, C.DoubleToDecimal2(Double, Int32) ``` doubles in MMX registers and ints in regular ones. And actually return address in a stack would be inside L1 cache. To be honest MODRN cpu's are pretty good at function calling. Tihis is due to fact that most later languages was really bad at inlining, and Intel/AMD engineers had to overcome that. – Bogdan Mart Jul 13 '20 at 10:01
  • Thanks for the quick answer. This one gets the checkmark because it beat the other answer by 6 minutes, plus it has a lot of great comments. I can't believe I didn't see the solution myself. I guess I wasn't thinking about Math.Round(decimal) getting the result I need, and at the same time setting the decimal to the correct precision. Just an aside: looking at the underlying routines that get called, I don't think a couple of push/pop instructions and a call/return will make much difference compared to the double to decimal conversion itself. :-) – Bob Ammerman Jul 14 '20 at 17:46
0

This is what you are looking for. Convert.ToDecimal converts a double into decimal Math.Round rounds your decimal to desired number of decimal places.

decimal DoubleToDecimal(double value, int numberOfDecimalPlaces)
{
    return Math.Round(Convert.ToDecimal(value), numberOfDecimalPlaces);
}
ddfra
  • 2,413
  • 14
  • 24