0

I'm working with double in C#. Double has Precision ~15-17 digits.

In my program, the user enters: 0.011 - 0.001, and the result shows 0.0099999999999999985 --> that's ugly in the user's eye - they will question my math ability.

I'm okay with the result being 0.0099999999999999985 internal, but when display, I want to find a way to fix the precision digits. For example:

double a = 0.011;
double b = 0.001;
double result, display;

result = a - b;//result = 0.0099999999999999985

display = ConvertForDisplay(result);//I want display = 0.01

I see that the build-in Android app "Calculator" on Samsung smartphone (e.g Galaxy Tab SM-T295) also use double (because I enter 10^309 and it gives error) but when I enter the math 0.011 - 0.001, it properly shows me 0.01.

I thought about Math.Round (double value, int digits), but the digits must be in range [0,15], while I also need to display the numbers like 1E-200.

I've search around the internet but I just saw questions about how to deal with the floating point accuracy problems (e.g don't use == but use Math.Abs(a-b) < double.Epsilon, Math.Round,...). But I can't find how to round the precision digits (e.g the value 0.0099999999999999985 has the precision digits 99999999999999985, when the those digits contains more than 15 "9"s, then it should be round for display).

So what is the proper way to fix this problem (e.g convert 0.0099999999999999985 into 0.01)?

Extra: when I enter 9999999999999999d, C# gives me 1E+16, I know this happens because C# sees that all the precision digits, which it can hold, is 9 so it rounds up to 10, but is there any way I can make it keeps all the 9s - I see that the Android app "Calculator", which I mention above, also gives this behavior, so I count this as extra - not a must-fix, but I want to know if it's fixable just for fun.

Update:

I see the behavior: (1.021 - 1.01) = 0.010999999999999899

But (1.021 - 1.01) + 1 = 1.011

So when I add 1, double triggers its "internal round" feature (I guess) and it rounds to the number I want. Maybe this could lead to my solution?

Here's another interesting discover: cast the value to float can also give the number I want, e.g: (float)(1.021 - 1.01) = 0.011

123iamking
  • 2,387
  • 4
  • 36
  • 56
  • Does this answer your question? [Rounding decimal value](https://stackoverflow.com/questions/11740989/rounding-decimal-value) – jazb Nov 09 '21 at 03:32
  • @Jazb - I mentioned in the question `I thought about Math.Round() ... need ... the numbers like 1E-200.`, so no, that's not answer my question. Did you even fully read my question? – 123iamking Nov 09 '21 at 03:36
  • "convert 0.0099999999999999985 into 0.01" - this is a misconception. There is no exact representation of 0.01 as double and 0.0099999999999999985 is the nearest representable number. So you cannot "round" to 0.01. What you can do, however, is specify the numer of digits you want to see when *formatting* this number as string. – Klaus Gütter Nov 09 '21 at 04:40
  • @KlausGütter - I know it's a misconception (maybe the word convert is not right), but I want to find out how did Android "Calculator" (which I mention in the question) do to solve this problem. – 123iamking Nov 09 '21 at 04:48
  • 1
    Probably because the calculator is using an arbitrary precision math library. – Jeremy Lakeman Nov 09 '21 at 04:51
  • @JeremyLakeman - Yes, I thought that too, but I saw that the valid number range of that calculator matches double's valid range so I hope there is a proper solution for this that I didn't know. Using arbitrary precision math library is pretty risky because C# don't have that built-in, I might have to manually write a lot of basic function for calculation. – 123iamking Nov 09 '21 at 05:05
  • 2
    Does this answer your question? [Using String Format to show decimal up to 2 places or simple integer](https://stackoverflow.com/questions/6951335/using-string-format-to-show-decimal-up-to-2-places-or-simple-integer) – Harish Nov 09 '21 at 05:25
  • 1
    Perhaps they have a introduced a type to represent the number in different ways depending on its scale. The source code is here https://android.googlesource.com/ you could check yourself. – Jeremy Lakeman Nov 09 '21 at 05:29
  • 2
    have you thought about using `decimal`? – Franz Gleichmann Nov 09 '21 at 06:06
  • @FranzGleichmann - Can't use decimal because I need the big range of double [E−324, E308] – 123iamking Nov 09 '21 at 10:18

1 Answers1

0

I re-think the issue, how about I use decimal as a tool to fix this problem. The idea is to use decimal to solve the math (if the range is suitable for decimal), then cast the decimal back to double. Here is the code:

const int LEFT = 0;
const int RIGHT = 1;

private static bool IsConvertableToDecimal(double value)
{
    const double MAX = (double)decimal.MaxValue;
    const double MIN = (double)decimal.MinValue;
    const double EXP_MIN = -1E-28;
    const double EXP_MAX = 1E-28;

    return
        (value >= EXP_MAX && value < MAX) ||
        value == 0 ||
        (value > MIN && value <= EXP_MIN);
}

private static double BasicFunc(double[] arr,
    Func<double, double, double> funcDouble,
    Func<decimal, decimal, decimal> funcDecima)
{
    var result = funcDouble(arr[LEFT], arr[RIGHT]);

    //Check if can convert to decimal for better accurancy
    if (IsConvertableToDecimal(result) &&
        IsConvertableToDecimal(arr[LEFT]) &&
        IsConvertableToDecimal(arr[RIGHT]))
        result = (double)(funcDecima((decimal)arr[LEFT], (decimal)arr[RIGHT]));

    return result;
}

public static double ADD(double[] arr)
    => BasicFunc(arr, (x, y) => x + y, decimal.Add);
public static double SUBTRACT(double[] arr)
    => BasicFunc(arr, (x, y) => x - y, decimal.Subtract);
public static double MULT(double[] arr)
    => BasicFunc(arr, (x, y) => x * y, decimal.Multiply);
public static double DIV(double[] arr)
    => BasicFunc(arr, (x, y) => x / y, decimal.Divide);
    
//Usage ================
void Test()
{
var arr = new double[2];
arr[0] = 0.011;
arr[1] = 0.001;
var result = SUBTRACT(arr);
Print(result);//Result = 0.01 ==> Success!!!
}
123iamking
  • 2,387
  • 4
  • 36
  • 56