Introduction
This is a rewrite of an old post I wrote back in 2019, where I had this odd requirement to truncate a decimal without rounding it. After re-reading the original article, I realized the proposed solutions were flawed in several ways. So here is my second attempt 🤓
Problem statement
How can arbitrary decimal places be cut from a decimal
without rounding it? For example, cutting three decimal places from 1.23456
should result in 1.234
and not 1.234
. Let's run through the solutions.
Math.Truncate solution
The following solution works by shifting places to the left by a multiplier.
[Fact]
public void Should_cut_decimal_places_v1()
{
const int value = 1.23456M;
const int places = 3;
var multiplier = (decimal)Math.Pow(10, places);
var actual = Math.Truncate(value * multiplier) / multiplier;
Assert.Equal(1.234M, actual);
}
In the example from above, the multiplier will be 1000
. Math.Truncate(value * multiplier)
will cut the fractional part and result in 1234
. Divided again by the multiplier results in 1.234
. Mission accomplished!
However, there is an issue with the solution from above. In case value * multiplier
becomes bigger then Decimal.MaxValue
we will face an overflow. So it's safer to handle the integral and fractional parts separately.
[Fact]
public void Should_cut_decimal_places_v2()
{
// Arrange
const decimal value = 1.23456M;
const int places = 3;
// Act
var integral = Math.Truncate(value);
var fraction = value - integral;
var multiplier = (decimal)Math.Pow(10, places);
var truncatedFraction = Math.Truncate(fraction * multiplier) / multiplier;
var actual = integral + truncatedFraction;
// Assert
Assert.Equal(1.234M, actual);
}
Modulo solution
The same goal can be reached by leveraging modulo. In the example below, value % divisor
becomes 0.00056
, which then subtracted from the original value leads to the desired outcome.
[Fact]
public void Should_cut_decimal_places_v3()
{
const decimal value = 1.23456M;
const int places = 3;
var divisor = (decimal)Math.Pow(10, -1 * places); // 0.001
var actual = (value - (value % divisor)); // 1.23400
Assert.Equal(1.234M, actual);
}
Conclusion
Both, the modulo and the shifting approach have led to a solution. Depending on your requirements you might prefer the modulo approach since it's less complex and therefore faster.