Truncating a float to a specified number of decimal digits

Pages: 12
The ios.precision(n) function rounds a floating-point value rather than truncating it.

To truncate a float to an int, we can, of course, cast it to an int.
We can also use the trunc() function in the <math> library for this purpose.

However, if we want to truncate (rather than round) a float to a specified number of digits, how can we do so?

eg: the float is 1234.5678. I would like to output it truncated to 2 decimal digits, which should give 1234.56.

Using ios.precision(6) would round it as 1234.57.
Last edited on
Can a floating-point value truly be "truncated"? - judge for yourself:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
   double x = 1234.5678;

   x = (int)( 100 * x )  /  100.0;

   cout.setf( ios::fixed );
   for ( int i = 2; i <= 16; i++ ) cout << setprecision( i ) << x << endl;
}


1234.56
1234.560
1234.5600
1234.56000
1234.560000
1234.5600000
1234.56000000
1234.560000000
1234.5600000000
1234.56000000000
1234.560000000000
1234.5599999999999
1234.55999999999995
1234.559999999999945
1234.5599999999999454
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <cmath>
#include <limits>
#include <cmath>
#include <utility>
#include <iomanip>

// return { value after truncation, true } if truncation was successful
// return { approximate value after truncation, false } if precision is insufficient
std::pair< double, bool > trunc_n( double value, std::size_t digits_after_decimal = 0 )
{
    static constexpr std::intmax_t maxv = std::numeric_limits<std::intmax_t>::max() ;
    static constexpr std::intmax_t minv = std::numeric_limits<std::intmax_t>::min() ;

    unsigned long long multiplier = 1 ;
    for( std::size_t i = 0 ; i < digits_after_decimal ; ++i ) multiplier *= 10 ;

    const auto scaled_value = value * multiplier ;

    const bool did_trunc =  scaled_value != scaled_value+0.5 && scaled_value != scaled_value-0.5 ;

    if( scaled_value >= minv && scaled_value <= maxv )
        return { double( std::intmax_t(scaled_value) ) / multiplier, did_trunc } ;
    else return { std::trunc(scaled_value) / multiplier, did_trunc } ;
}

int main()
{
    for( double value : { -1234567890123.555555, -1234567890.555555, 1234567890.555555, 1234567890123.555555 } )
    {
        std::cout << '\n' << std::fixed << std::setprecision(6) << value << '\n' ;
        for( int ndigits = 2 ; ndigits <= std::cin.precision() ; ++ndigits )
        {
            const auto result = trunc_n(value,ndigits) ;
            std::cout << std::setprecision(ndigits) << result.first ;
            if( !result.second ) std::cout << " *** insuffiicient floating point precision ***" ;
            std::cout << '\n' ;
        }
    }
}

http://coliru.stacked-crooked.com/a/ccd091946576f69a
Can a floating-point value truly be "truncated"?


I already provided an example of what I want to do:

If a float holds 1234.5678, truncation to 2 decimal digits would be 1234.56. That's what I want the OP to be.

What I wanted to know is if there is a library function for this purpose. I get the message - there isn't.

So to do this, we would need to write a custom function, which can be done.
> If a float holds 1234.5678, truncation to 2 decimal digits would be 1234.56.

I think that what lastchance was trying to point out is that there may not be a floating point representation for the precise mathematical value 1234.56
Here's my function for this purpose. It's a Quick and dirty job, and error checking hasn't been built-in, but I've tested it and it seems OK:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
template<typename F>
F trunc_decs(const F& f,
                 int decs)
{
    int i1 = floor(f);
    F rmnd = f - i1;
    int i2 = static_cast<int> (rmnd * pow(10, decs));
    F f1 = i2 / pow(10, decs);

    return i1 + f1;
}


int main()
{
    cout << "trunc_decs(1234.5678, 3) = "
         <<  setprecision(7)                                /// else will round
         << trunc_decs(1234.5678, 3) << endl;

    cout << "trunc_decs(1234.5678, 2) = "
         << trunc_decs(1234.5678, 2) << endl;

    cout << "trunc_decs(1234.5678, 1) = "
         << trunc_decs(1234.5678, 1) << endl;

    cout << "trunc_decs(1234.5678, 0) = "
         << trunc_decs(1234.5678, 0) << endl;

    return 0;
}
I would like to output it truncated

If it's just for outputting:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>
using namespace std;


string truncate(float val, int numDigits)
{
  std::string output = std::to_string(val).substr(0, numDigits+1);
  if (output.find('.') ==  string::npos ||
      output.back() == '.')
  {
    output.pop_back();
  }
  return output;
}
  
int main() 
{
    float value = 1234.5678;
    
    cout << truncate(value, 6) << '\n';
    cout << truncate(value, 5) << '\n';
    cout << truncate(value, 4) << '\n';
    cout << truncate(value, 3) << '\n';
    cout << truncate(value, 2) << '\n';
}


or as a float:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>
using namespace std;


float truncate(float val, int numDigits)
{
  std::string output = std::to_string(val).substr(0, numDigits+1);
  if (output.find('.') ==  string::npos ||
      output.back() == '.')
  {
    output.pop_back();
  }
  return stof(output);
}
  
int main() 
{
    float value = 1234.5678;
    
    cout << truncate(value, 6) << '\n';
    cout << truncate(value, 5) << '\n';
    cout << truncate(value, 4) << '\n';
    cout << truncate(value, 3) << '\n';
    cout << truncate(value, 2) << '\n';
}
Last edited on
Very good. Now, for extra credit, change (in the post two above this) your line
<< setprecision(7) /// else will round
to
<< fixed << setprecision(16) /// else will round
and observe what happens (once you've included all the headers).

trunc_decs(1234.5678, 3) = 1234.5670000000000073
trunc_decs(1234.5678, 2) = 1234.5599999999999454
trunc_decs(1234.5678, 1) = 1234.5000000000000000
trunc_decs(1234.5678, 0) = 1234.0000000000000000



When you have done that, change the number to 1234.4321 and used fixed and precision(16) manipulators.
Last edited on
Typical values around 1234.56 which can be represented as a double
(typical: std::numeric_limits<double>::is_iec559 == true)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <cmath>
#include <limits>
#include <iomanip>

int main()
{
    const double value = 1234.56 ;
    constexpr auto N = std::numeric_limits<double>::digits10 + 1 ;

    std::cout << std::boolalpha << "iec559? " << std::numeric_limits<double>::is_iec559 << '\n'
              << std::fixed << std::setprecision(N)
              << "representable numbers around 1234.56\nto about "
              << N << " digits after the decimal point\n" ;

    const double a_lower_value = std::nexttoward( std::nexttoward( value, value-1 ), value-1 ) ;
    const double a_higher_value = std::nexttoward( std::nexttoward( value, value+1 ), value+1 ) ;
    for( double v = a_lower_value ; v <= a_higher_value ; v = std::nexttoward( v, v+1 ) )
        std::cout << v << '\n' ;
}

iec559? true
representable numbers around 1234.56
to about 16 digits after the decimal point
1234.5599999999994907
1234.5599999999997181
1234.5599999999999454
1234.5600000000001728
1234.5600000000004002

http://coliru.stacked-crooked.com/a/fce759083e73a209
http://rextester.com/VDD84140
trunc_decs(1234.5678, 3) = 1234.5670000000000073
trunc_decs(1234.5678, 2) = 1234.5599999999999454
trunc_decs(1234.5678, 1) = 1234.5000000000000000
trunc_decs(1234.5678, 0) = 1234.0000000000000000

And does this happen when rounding (rather than truncation) is done as well?

I think the behavior you have recorded has nothing to do with truncation per se.
The point is that a floating-point number like 1234.56 cannot be stored precisely. Consequently, "truncation" is meaningless.

As @JLBorges points out, there are actually several distinct numbers which would be printed out as 1234.56 to a moderate precision. Which particular one of those numbers would you like any trunc() function to return?

Try this program - absolutely no truncation needed. x is already assigned the value that you want. You can't even guarantee which side of 1234.56 the higher-precision values would fall, so writing to strings won't help you.

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
   double x = 1234.56;
   cout.setf( ios::fixed );
   for ( int i = 2; i <= 16; i++ ) cout << setprecision( i ) << x << endl;
}
Last edited on
> The point is that a floating-point number like 1234.56 cannot be stored precisely.
> Consequently, "truncation" is meaningless.

Truncation of a floating point value is definitely not meaningless.

Most floating point values are approximations of a real number. The result of a truncation is both meaningful and useful if it is as close as possible (actually as close as needed) to the mathematical ideal of the truncated real number. This is true of the result of any floating point computation: std::sin() or 1.0/3.0 are not meaningless just because the result may not be precise representation of the actual value with infinite precision.
Absolute mathematical precision to N/finity digits is often both meaningless and misleading. It is by applying a value of precision by which measurements gain value. Whenever scientists do any math the precision is very much a part of the value of any computation. Adding precision is just sloppy.

The issue is that radix=10 and radix=2 are not digit-for-digit compatible, especially when you start playing with fractional parts. So knowing how it makes a difference at all is another level of care you must have when dealing with computers.

For your reading pleasure:

What Every Computer Scientist Should Know About Floating-Point Arithmetic
http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
closed account (48T7M4Gy)
Quite a good article.
Last edited on
> Exactly as @lastchance wrote x = (int)( 100 * x ) / 100.0;

Try this:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <limits>
#include <cmath>
#include <iomanip>

int main()
{
    double x = std::numeric_limits<int>::max() * 2.0 ;  ;
    std::cout << std::fixed << std::setprecision(2) << x << '\n' ;
    
    x = (int)( 100 * x ) / 100.0 ; // undefined behaviour
    std::cout << std::fixed << std::setprecision(2) << x << '\n' ;
}

http://coliru.stacked-crooked.com/a/b45777673d4d4ae8
Last edited on
closed account (48T7M4Gy)
the difficulty is representing decimals given computers are fundamentally binary.
Last edited on
closed account (48T7M4Gy)
BCD is essentially a part of any problem here.
Last edited on
> because of the 13-14 digit limitation

Try this (just 8 digits):
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <limits>
#include <iomanip>

int main()
{
    double x = 90'000'000.0 ; // a mere 8 significant digits
    std::cout << std::fixed << std::setprecision(2) << x << '\n' ;
    
    x = (int)( 100 * x ) / 100.0 ; // undefined behaviour on this implementation
    std::cout << std::fixed << std::setprecision(2) << x << '\n' ;
}

http://coliru.stacked-crooked.com/a/c0c88d61ef46b7ce


> the algorithm @lastchance has presented would be 'unreliable' or at least prone to error in the last few digits

The "algorithm", very reliably, engenders undefined behaviour.
Last edited on
closed account (48T7M4Gy)
.
Last edited on
closed account (48T7M4Gy)
.
Last edited on
Pages: 12