How to round smoothly percentage in JS

4k views Asked by At

I have an object containing decimals values : evo, meteo and usage I try to display this values with three conditions :

  • values can be positive and negative
  • values must be displayed as percentage without any decimals
  • (A) must be true

(A): Math.round(meteo*100)+Math.round(usage*100) = Math.round(evo*100)

For example, if we apply A with to these values

{evo: 0.1466, meteo: 0.1231, usage: 0.0235}

We will get unvalid percentages :

evo: 15%
meteo: 12%
usage: 2%

Because values are rounded, sometimes (A) isn't verified. I am working on a getSmooth function to adjust rounded values to get the equation (A) always verified.

var smooth = getSmooth(Math.round(evo*100), Math.round(meteo*100), Math.round(usage*100);

function getSmooth(evo, meteo, usage){
    // case we need to incremente something
    if( Math.abs(evo) > Math.abs(meteo) + Math.abs(usage) ){
        // case we need to incremente usage
        if( Math.abs(meteo) > Math.abs(usage) ){
            (usage > 0) ? usage++ : usage--;
        }
        // case we need to incremente meteo
        else{
            (meteo > 0) ? meteo++ : meteo--;
        }
    }
    // case we need to decremente something
    else{
        // case we need to decremente usage
        if( Math.abs(meteo) > Math.abs(usage) ){
            (usage > 0) ? usage-- : usage++;
        }
        // case we need to decremente meteo
        else{
            (meteo > 0) ? meteo-- : meteo++;
        }
    }

    return {
        evo: evo,
        meteo: meteo,
        usage: usage
    };
}

My function isn't workind well, and it does handle only +1 incrementation/decrementation. I am pretty sure I am doing it the hard way.

Is there an easier way to achieve that task ?

2

There are 2 answers

4
BeyelerStudios On BEST ANSWER

try this:

function getSmooth(meteo, usage) {
    meteo = Math.round(meteo*100);
    usage = Math.round(usage*100);
    return {
        evo: (meteo + usage) / 100,
        meteo: meteo / 100,
        usage: usage / 100
    };
}

to test for errors in calculations in evo separate from your visualisation logic:

var evo, meteo, usage;
// ...
// do the error handling
var eps = 1e-4; // some float precision epsilon
if(Math.abs(evo - (meteo + usage)) > eps) {
     // there's an error in evo...
}
// now do the processing
var smooth = getSmooth(meteo, usage);
// continue with smooth...
3
UsainBloot On

Here is a different approach to it if you want to keep the accuracy of the value of evo and not calculate it based on the total of meteo and usage. (It's a bit uglier than using evo = metro + usage)

Look at what the decimal is for meteo and usage, then use .ceil() or .floor() depending on the result.

var smooth = getSmooth(evo, meteo, usage);

function getSmooth(evo, meteo, usage){

    evo   *= 100;
    meteo *= 100;
    usage *= 100;

    var meteo_decimal = meteo % 1;
    var usage_decimal = usage % 1;

    evo = Math.round(evo);

    if( meteo_decimal > usage_decimal ) {
        meteo = Math.ceil(meteo);
        usage = Math.floor(usage);
    } else {
        meteo = Math.floor(meteo);
        usage = Math.ceil(usage);
    }

    return {
        evo: evo / 100,
        meteo: meteo / 100,
        usage: usage / 100
    };
}