1

I have a float number: 10071072872302 and I need to convert it to decimal.

float number = 10071072872302f;
var convertNumber = Convert.ToDecimal(number);

I get the value: 10071070000000 which is Wrong.

If I convert it to double first, and then convert it to decimal:

float number = 10071072872302f;
var convertNumber = Convert.ToDecimal(Convert.ToDouble(number));

I get the value: 10071073357824 which is also Wrong.

If I just convert float to double:

float number = 10071072872302f;
var convertNumber = Convert.ToDouble(number);

I get the value: 10071073357824 which is Wrong.

I am really confused. My company's database is designed with decimal data type. I need to programmatically calculate the value of the data according to the pre-configured formula, I need to use the NCalc library which returns the float data type. Unfortunately, I can't convert float to decimal`.

Could anyone help me with this impasse?

EDIT I want to clarify why I need to convert float to decimal.

To get a value, I need to calculate the expression like: ISA16+ISA8+ISNULL(MAX((ABS(BSMS10)-ABS(BSMS10_)),0),0)

After taking the data in the database to replace the expression, I have an expression like this: 10071072872302+872302+MAX((ABS(-072872302)-ABS(2302)),0)

To calculate the value of this expression, I use the NCalc library

private static object CalculateString(string formula)
    {
        var expression = new Expression(formula);

        expression.EvaluateFunction += (name, args) =>
        {
            switch (name)
            {
                case "ABS":
                    args.Result = Math.Abs(Convert.ToDouble(args.Parameters[0].Evaluate()));
                    break;
                case "MAX":
                    args.Result = Math.Max(Convert.ToDouble(args.Parameters[0].Evaluate()), Convert.ToDouble(args.Parameters[1].Evaluate()));
                    break;
            }
        };

        var value = expression.Evaluate();
        return value;
    }

Expressions come in many forms. However, to check the value, I tried checking with a number of 10071072872302

var result = CalculateString("10071072872302");

The result I get is a float value, so I need to convert it to decimal to match the data type in the database. That is why I posed the question above.

Minh Giang
  • 631
  • 9
  • 28
  • The *float* type can store up to 7 digits of precision. So what you got is correct. Consider *double* or *decimal*. Clearly you'll favor the latter, use 10071072872302m as a literal. – Hans Passant Feb 28 '20 at 14:20
  • 4
    `10071072872302` cannot be stored in a float without a loss of precision. –  Feb 28 '20 at 14:20
  • 4
    `number.ToString("F")` results in 10071070000000.00. So it's wrong from the get go. – juharr Feb 28 '20 at 14:22
  • Actually, the float value I got from a third party, so I need to convert to decimal. But it's too difficult – Minh Giang Feb 28 '20 at 15:13
  • 1
    It doesn't matter where you got it. That value cannot fit inside a float. You will not get back the missing precision by converting it. –  Feb 28 '20 at 15:13

3 Answers3

6

I have a float number: 10071072872302.

float number = 10071072872302f;

Unfortunately, that's too late, float has no exact representation for 10071072872302 . So the nearest machine number that's the most close is : 10071073357824. So that's the value that is actually stored in your variable.

see for instance : FloatConverter

You must strive to get another representation, not float (string, decimal, or other bigint library), to get this number.

Note that double should be actually ok for this precise number (double number = 10071072872302 ), but I wouldn't rely on floating point number type (float or double) to store an exact value in any case.

See also this generic Q&A : Is floating point math broken?

Pac0
  • 21,465
  • 8
  • 65
  • 74
  • @MinhGiang Ok, this is much more precise, thanks for the edit. I don't know NCalc, but a quick search showed me that there is a `Convert.ToDecimal()` function, can you try to use it instead of `ToDouble()` ? – Pac0 Feb 29 '20 at 07:56
  • _`The result I get is a float value`_ : this my lack of knowledge of NCalc speaking : How do you know it's a `float` (as in the alias of `System.Float`) ? The returned signature of your method is `object`, so I can't really be sure of what's going on under the hood. – Pac0 Feb 29 '20 at 07:59
1

I already been solved my problem. I found Jint library instead of NCalc

https://www.nuget.org/packages/Jint

static Engine engine = new Engine()
             .Execute("function ABS(number){return Math.abs(number);}")
             .Execute("function MAX(a,b){return Math.max(a,b);}")
             .Execute("function Max(a,b){return Math.max(a,b);}")
             .Execute("function ISNULL(a,b){return a==null?b:a;}")
             .Execute("function Divide(a,b){return a==null||b==null||b==0?null:a/b;}");

    private static object CalculateString(string formula)
    {
        var result = engine
            .Execute(formula)
            .GetCompletionValue()
            .ToObject();
        return result == null ? null : (object)Convert.ToDecimal(result);
    }

It's works like a charm :)

Minh Giang
  • 631
  • 9
  • 28
0

You are running into two problems here:

  • Decimal vs. Binary. NCalc appears to be calculating expressions using float values. But note that float and double are binary floating-point number types, and not all decimal numbers can be represented exactly in a binary number type (regardless of its length); examples include 0.1 and 0.3. (In comparison, decimal is a decimal floating-point number type.) If NCalc is parsing the decimal numbers in your expressions into binary number types such as float or double, then there is a risk of rounding error due to the need to convert certain decimal numbers to those types, even if the decimal numbers in your expressions have relatively few digits.
  • Limited precision.float, double, and decimal are limited-precision number types. For example, float has a precision of 24 bits (about 7 decimal digits) and decimal has a precision of about 28 decimal digits. If your expressions have numbers with more digits than a number type can handle, then there will be a loss of precision on those numbers once converted to that type. On the other hand, BigInteger is a number type with effectively unlimited precision, so this may be viable if you're only working with integers.

I'm not familiar with NCalc, but see if you can set NCalc to parse and process numbers in the decimal number type (provided all the numbers you care about have 28 decimal digits or less). Provided all the numbers in your expressions are integers, and nothing in your expressions can cause fractions to occur in a calculation, you can also use BigInteger instead of decimal if NCalc supports it. If NCalc doesn't support decimal or BigInteger, then you may need to use another expression parsing library.

Peter O.
  • 32,158
  • 14
  • 82
  • 96