2

Como se podría hacer para aproximar un numero por defecto y por exceso? Entendiendo que digamos, tenemos el numero 2.534823

Una aproximación por defecto a 4 decimales, sería 2.5348

y para aproximación por exceso a 4 decimales, sería 2.5349

Es como hacer un redondeo pero para una posicion de decimal especifica, hay alguna manera de hacerlo en C++?

Tengo el siguiente ejemplo, pero sería unicamente para redondear al numero entero más cercano, ya sea mayor o menor:

#include<iostream>
#include<math.h>
using namespace std;


int main(){

    float num;
    int decim;

    cout<<"\tCalcular aproximaciones por defecto y exceso"<<endl<<endl;

    cout<<"Ingrese un numero: " ;
    cin>>num;

    cout<<"Ingresa la cantidad de dedcimales a dejar: ";
    cin>>decim;

    /*
        con ceil y floor me acerca al entero,
        ceil al entero más cercano mayor
        floor al  entero más cercano menor
    */

    cout<<"Aproximación por exceso: "<<ceil(num)<<endl;

    cout<<"Aproximación por defecto: "<<floor(num)<<endl;

    /*
       la aproximación debería quedar como:
       numero ingresado = 1.432986

       para por defecto  con 4 decimales, quedaria 1.4329
       para exceso con 4 decimales quedaría 1.4323
       pero con ceil, subiría a 2
       y con floor bajaría a 1


    */

    return 0;
}

En C Se podía truncar un numero, a una cantidad especifica de decimales al imprimirlo(Es decir, la aproximación por defecto):

printf("%.3f", x);

Ó

printf("%3f", x);

No recuerdo cual de las 2 formas, lo que quiero saber, es como se podría hacer lo mismo pero en C++ y además obtener la aproximación por exceso

2 Answers2

4

Como ya sabemos, los números en coma flotante son imprecisos, así que hacerlo directamente con el valor binario puede provocar resultados ... curiosos.

Una posible solución es realizar la aproximación sobre el valor ya convertido a cadena, si bien esto nos obliga a realizar ciertas operaciones a mano:

#include <string>
#include <iostream>
#include <sstream>

std::string upround( float value, unsigned decimals ) {
  std::stringstream buffer;

  buffer << value;

  // Cosas mias. `str( )` devuelve copias ... no vamos a estar todo el rato copiando cosas.
  std::string workCopy( buffer.str( ) );

  // Buscamos la posición del punto decimal.
  auto posOfPoint = workCopy.find( '.' );

  // Si es un número entero (sin punto), nada que hacer.
  if( posOfPoint == std::string::npos ) return workCopy;

  // Total de número a la derecha de la coma:
  auto workDecimals = workCopy.size( ) - 1 - posOfPoint;

  // Si hay menos decimales que los pedidos, nada que hacer.
  if( workDecimals < decimals ) return workCopy;

  // Recortamos la cadena.
  if( workDecimals > decimals ) workCopy.erase( workCopy.length( ) - ( workDecimals - decimals ) );

  // Trivial: el último dígito es != 9.
  if( workCopy.back( ) != '9' ) {
    ++( workCopy.back( ) );
  } else {
    // No trivial. El último dígito es un '9'. Es el caso 'chulo' :-)
    int pickup = 1;

    for( int idx = workCopy.length( ) - 1; idx >= 0; --idx ) {
      if( workCopy[idx] == '.' ) continue;
      workCopy[idx] += pickup;
      if( workCopy[idx] > '9' ) {
        workCopy[idx] = '0';
        pickup = 1;
      } else {
        pickup = 0;
        break;
      }
    }

    // Ya terminamos con la cadena.
    // Si aún nos queda 1 por sumar, hay que añadir un número al principio.
    if( pickup ) workCopy.insert ( 0, 1, '1' );
  }

  return workCopy;
}

int main( ) {
  const auto testValue = 3.1415f;
  std::cout << "Valor de prueba: " << testValue << '\n';
  std::cout << upround( testValue, 2 ) << '\n';
  std::cout << upround( testValue, 3 ) << '\n';

  const auto testValue2 = 9.9999f;
  std::cout << "Valor de prueba: " << testValue2 << '\n';
  std::cout << upround( testValue2, 3 ) << '\n';
  std::cout << upround( testValue2, 4 ) << '\n';
  return 0;
}

No lo he probado a fondo; para los valores de prueba ( 3.1415f y 9.9999f ) muestra lo siguiente:

Valor de prueba: 3.1415
3.15
3.142
Valor de prueba: 9.9999
10.000
10.0000

Nota: es relativamente sencillo convertirlo a template< >, y, ya puestos, añadirle un argumento extra para poder usar otros separadores, aparte del punto (.).

Trauma
  • 25,297
  • 4
  • 37
  • 60
3

Usa std::setprecision, tu número con 3 decimales sería 2.535 y con 4 decimales sería 2.5348, este código:

auto valor = 2.534823;

for (auto precision : {2, 3, 4, 5, 6, 7, 8, 9, 10})
    std::cout << std::setprecision(precision) << valor << '\n';

Produce la siguiente salida:

2.5
2.53
2.535
2.5348
2.53482
2.534823
2.534823
2.534823
2.534823

Puedes verlo funcionando en Wandbox. Adicionalmente, puedes configurar el estilo de redondeo de números en coma flotante mediante std::fesetround, teniendo cuatro estilos de redondeo:

  • FE_DOWNWARD: Redondeo hacia negativo.
  • FE_TONEAREST: Redondeo hacia el valor representable más cercano.
  • FE_TOWARDZERO: Redondeo hacia cero.
  • FE_UPWARD: Redondeo hacia positivo.

Con redondeo hacia negativo, el programa anterior:

std::fesetround(FE_DOWNWARD);

for (auto precision : {2, 3, 4, 5, 6, 7, 8, 9, 10})
    std::cout << std::setprecision(precision) << valor << '\n';

Muestra la siguiente salida:

2.5
2.53
2.534
2.5348
2.53482
2.534822
2.5348229
2.53482299
2.534822999
PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82