Reputation: 16121
Can someone please provide code (any language will do, but I write the .Net languages and VB6) for an algorithm that rounds numbers ending with 5 towards a given number?
RoundTo(double value, double toWards, int numberOfDigitsBehindComma)
RoundTo(1.25,1,1)=1.2 RoundTo(1.25,2,1)=1.3
RoundTo(1.26,1,1)=1.3 RoundTo(1.24,2,1)=1.2
Include a solution for negative numbers please.
EDIT: There seems to be a lot of confusion about my requirements I will fill in all the Assertions that the resulting code has to meet. My solution does so.
[TestMethod]
public void RoundTowards()
{
double x=3.44;double y=3.45;double z=4.45;
double a = 3.51; double b = 4.5001; double c = -1.14; double d = -1.15;
var mean=4;
Assert.AreEqual(3.4,x.RoundTowards(mean,1));
Assert.AreEqual(3.5, y.RoundTowards(mean, 1));
Assert.AreEqual(4.4, z.RoundTowards(mean, 1));
Assert.AreEqual(3.5, a.RoundTowards(mean, 1));
Assert.AreEqual(4.5, b.RoundTowards(mean, 1));
mean = 5;
Assert.AreEqual(3.4, x.RoundTowards(mean, 1));
Assert.AreEqual(3.5, y.RoundTowards(mean, 1));
Assert.AreEqual(4.5, z.RoundTowards(mean, 1));
Assert.AreEqual(3.5, a.RoundTowards(mean, 1));
Assert.AreEqual(4.5, b.RoundTowards(mean, 1));
mean = 3;
Assert.AreEqual(3.4, x.RoundTowards(mean, 1));
Assert.AreEqual(3.4, y.RoundTowards(mean, 1));
Assert.AreEqual(4.4, z.RoundTowards(mean, 1));
Assert.AreEqual(3.5, a.RoundTowards(mean, 1));
Assert.AreEqual(4.5, b.RoundTowards(mean, 1));
Assert.AreEqual(Math.Round(-1.1,4),Math.Round( c.RoundTowards(mean, 1),4));
Assert.AreEqual(Math.Round(-1.1,4),Math.Round(d.RoundTowards(mean, 1),4));
mean = -2;
Assert.AreEqual(Math.Round(3.4,4),Math.Round( x.RoundTowards(mean, 1),4));
Assert.AreEqual(Math.Round(3.4,4),Math.Round( y.RoundTowards(mean, 1),4));
Assert.AreEqual(Math.Round(4.4,4),Math.Round( z.RoundTowards(mean, 1),4));
Assert.AreEqual(Math.Round(3.5,4),Math.Round( a.RoundTowards(mean, 1),4));
Assert.AreEqual(Math.Round(4.5,4),Math.Round( b.RoundTowards(mean, 1),4));
Assert.AreEqual(Math.Round(-1.1, 4), Math.Round(c.RoundTowards(mean, 1), 4));
Assert.AreEqual(Math.Round(-1.2, 4), Math.Round(d.RoundTowards(mean, 1), 4));
}
[TestMethod]
public void RoundTowardsTowardZero()
{
double x = 3.45; double y = -3.45;
double a = -3.551; double b = 4.551; double c = 4.5500001; double d = 4.5501;
var mean = 0;
Assert.AreEqual(3.4, x.RoundTowards(mean, 1));
Assert.AreEqual(-3.4, y.RoundTowards(mean, 1));
Assert.AreEqual(-3.6, a.RoundTowards(mean, 1));
Assert.AreEqual(4.6, b.RoundTowards(mean, 1));
Assert.AreEqual(4.5, c.RoundTowards(mean, 1));
Assert.AreEqual(4.6, d.RoundTowards(mean, 1));
}
[TestMethod]
public void Test14_55()
{
Assert.AreEqual((14.55).RoundTowards(9, 1) ,14.5);
Assert.AreEqual((14.55).RoundTowards(15,1), 14.6);
}
[TestMethod]
public void Test14_5499999()
{
Assert.AreEqual((14.54999999).RoundTowards(9, 1) ,14.5);
Assert.AreEqual((14.54999999).RoundTowards(15,1), 14.6);
}
Thanks!!!
Upvotes: 2
Views: 1159
Reputation: 16121
Well, this seems to do the job, at least my tests are 'green', that is. I do not particularly like the necessity to round once more in the end as, the re-addition of the value to round can again give birth to typical doubles like .99999999999999999999, in stead of .1
<Extension()>
Public Function RoundTo(ByVal value As Double, mean As Double, digitsBehindComma As Integer) As Double
Dim correctedValue = value - mean
Dim increasedValue = correctedValue * Math.Pow(10, digitsBehindComma)
Dim trailingDigitCorrection = Math.Sign(correctedValue) * Math.Pow(10, -4) 'Safeguard against a trailing bit (i.e. 1.50000000001)
Dim halfAddition = Math.Sign(correctedValue) * 0.5
Dim division = 10 ^ digitsBehindComma
Dim sum = increasedValue - trailingDigitCorrection + halfAddition
Dim result = Fix(increasedValue - addition + halfAddition) / division
Return Math.Round(result + mean, digitsBehindComma)
End Function
Upvotes: 2
Reputation: 25522
I think all the solutions here are too complicated. Your problem seems to be that you want to be able to control the direction of rounding when you are exactly at the midpoint. You can reduce the problem of having N digits trailing the decimal point just by multiplying and then dividing by an integer power of 10, so it's enough to fix this for the case where the "5" is right after to the decimal point. If you want to round number x so that e.g. 0.5 is rounded upwards to 1, you just do
result = floor(x + 0.5);
If you want to round x so that 0.5 is rounded downwards to 0, you just do
result = ceil(x - 0.5);
These work because if x = 0.5, floor(x + 0.5) = floor(1) = 1, and ceil(x - 0.5) = ceil(0) = 0. To see that other numbers are rounded always correctly,
x = 0.4: floor(x + 0.5) = floor(0.9) = 0
ceil(x - 0.5) = ceil(-0.1) = 0
x = 0.6: floor(x + 0.5) = floor(1.1) = 1
ceil(x - 0.5) = ceil(0.1) = 1
so the whole code becomes:
double RoundTo(double value, double towards, int digits) {
double mult = pow(10, digits); /* to handle variable number of digits */
bool downwards = (towards < value);
value *= mult; /* scale */
value = (downwards ? ceil(value - 0.5) /* round midpoint downwards */
: floor(value + 0.5)); /* round midpoint upwards */
return value / mult; /* scale back */
}
This solution also offloads the whole process to the actual mathematics library and your CPU's ALU, and is therefore very robust. This handles obviously negative numbers without any extra tweaking, and works correctly with infinities etc.
Upvotes: 2
Reputation: 7731
Updating @8bitwide's solution below.
EDIT: to deal with error in floating point representations, I replaced value2 == 0.5
with isHalf(value2)
function that can do a fuzzy compare. Sounds like that is OK for your purposes since your numbers come from computations on at most thousands of low-precision values (based on bridge tournaments I've attended).
That is, if the number 4.5500000000001 occurs, it is surely a representation of 4.55 instead of the actual number 4.5500000000001.
The test case includes 4.5500001. double
has about 15 digits of accuracy, so if you're getting numbers accurate to only 7 digits, something is very wrong with your calculations.
#include <cstdlib>
#include <iostream>
#include <math.h>
bool isHalf(double x)
{
return abs(x - 0.5) <= 1e-10; // or whatever degree of fuzziness suits you
}
double round(double value, double toWards, int numberOfDigitsBehindComma)
{
double value0 = value * pow(10,numberOfDigitsBehindComma);
double value1 = floor(value0);
double value2 = value0 - value1; // 0 <= value2 < 1
if (value2 > 0.5 || isHalf(value2) && toWards > value))
{
value1++;
}
double value3 = value1 / pow(10,numberOfDigitsBehindComma);
return value3;
}
Upvotes: 2
Reputation: 44250
This baby is bit inefficient, maybe it will misbehave on the corner cases, but it appears to do the right thing for the four test points.
#include <stdio.h>
#include <math.h>
double roundto (double val, double towards, unsigned ndigit, double expected)
{
double up, down, mult, res;
int dir;
dir = (val == towards) ? 0 : (val > towards) ? -1 : 1;
mult = pow(10, ndigit);
down = floor (val * mult) / mult;
up = ceil (val * mult) / mult;
if (val-down == up-val) {;}
else dir = (val-down < up-val) ? -1 : 1;
res = dir > 0 ? up : down;
/*
fprintf (stderr, "Val=%f Expected=%f: dir=%d Mult=%f Down=%f Up=%f Res = %f\n"
, val, expected, dir, mult, down, up, res);
*/
return res;
}
int main(void)
{
double result;
result = roundto(1.25, 1, 1, 1.2);
printf ( "Result = %f\n", result);
result = roundto(1.25, 2, 1, 1.3);
printf ( "Result = %f\n", result);
result = roundto(1.26, 1, 1, 1.3);
printf ( "Result = %f\n", result);
result = roundto(1.24, 2, 1, 1.2);
printf ( "Result = %f\n", result);
return 0;
}
Upvotes: 0
Reputation: 21778
In relation to our discussion:
[...] it is a hidden requierement that I am myself unsure about how to deal with it: Round(4.5500001,4,1)==4.5 The problem is that when a double really should be 4.55 (und thus Round(4.55,4,1) =4.5) such a number sometimes can be represented as 4.550000001 . I want to get rid of such trailing bits but am unsure about how toe deal with that problem. In my own algorithm I remove the trailing digits 4 places further than the number of digits behind the comma that I need.
It is used to score bridge-tournaments, a rounding error at the second digit behind the comma can mean the difference between a championship and second place.
I presume your software is not time-critical: that is --- you don't perform the computation million of times per second, and it is not the case that if it goes a bit too slow, the software will be unusuable.
To avoid the rounding problems coming from the binary nature of floats or doubles, I would suggest using decimal system for all your relevant calcuations. Of course, the computation will be few times slower than when using binary system, but it should get your computation exact.
In Visual Basic 6 there is a type called Currency
, but it is a fixed-point variable: it holds always 4 digits (in decimal) after the dot. VB.NET introduces the Decimal
which is not fixed, but works in decimal system as well.
I don't know exactly which mathematical operations it supports, but I am pretty sure that all basic ones are there. Using more complex ones (logarithms, exponents, trigonometric functions) may require some damaging casts, but I hope you don't need that in bridge :)
Once in the world of decimal, you should be able to easily implement the rounding function (e.g. the one provided by xan), without any rounding problems.
An alternative I can suggest -- use integers everywhere. If you always care only about - say - 4 digits after the dot, just multiply all values by 10000 and perform your computation with those "augmented" values. Just pay attention when you perform multiplication.
Upvotes: 1
Reputation: 2071
Coded in straight C
I'm not sure I completely understood the question, so I made some assumptions Say you are rounding 1.235, I assumed you only wanted the 5 to matter if you where rounding to the decimal place before the 5. So
So RoundTo(1.235,2,1) = 1.2 but RoundTo(1.235,2,2)= 1.24 and RoundTo(1.235,1,2) = 1.23
Works for negative numbers, It is not the least computationally intensive solution, but should be easy to understand and modify.
#include <cstdlib>
#include <iostream>
#include <math.h>
double round(double value, double toWards, int numberOfDigitsBehindComma)
{
double value1 = floor(value * pow(10,numberOfDigitsBehindComma));
double value2 = floor(value * pow(10,numberOfDigitsBehindComma + 1)) - value1 * 10;
if (fabs(value2) > 5 || (fabs(value2) == 5 && toWards > value))
{
value1++;
}
double value3 = value1 / pow(10,numberOfDigitsBehindComma);
return value3;
}
Upvotes: 2