Truncating a decimal without rounding
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.