Truncating a decimal without rounding

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.

Further reading

Math.Truncate Method (System)
Calculates the integral part of a number.
Math.Pow(Double, Double) Method (System)
Returns a specified number raised to the specified power.