NSExpression 1/2
Asked Answered
S

5

2

I want to calculate a string, which I'm doing by this:

NSExpression *expression = [NSExpression expressionWithFormat:calculationString];
float result = [[expression expressionValueWithObject:nil context:nil] floatValue];
NSLog(@"%f", result);

The problem is, when calculationstring is 1/2, the result is 0. I tried to change float with double and NSNumber and the %f to %f and %@, but I always just get 0. What to I have to change?

Also if it matters, I am in Europe, so I have commas instead of points for this value, but it shouldn't matter as I am logging with %f which shows it as points. Just for information

Sieracki answered 15/1, 2013 at 20:14 Comment(0)
N
1

Typing in NSExpression is much like in C: literals that look like integers (no decimal point/comma) are treated as integers and thus use integer division. (Under integer division, 1/2 is zero. If you want 0.5, you need floating point division.) This happens when the expression is parsed and evaluated, so attempting to change the type of the result or the formatting of the output has no effect -- those things happen after parsing and evaluation.

If your calculationString is entirely under your control, it's easy to make sure that you use floating point literals anywhere you want floating point division. (That is, use 1.0/2 instead of 1/2.) If not, you'll need to change it such that it does -- here it's probably better to decompose the parsed NSExpression and change an operand rather than munge the string.


Followup edit on the "decompose" bit: String munging in content that you know to have higher-order structure is generally problematic. And with NSExpression, you already have a parser (who's smarter than a simple regex) decomposing the string for you — that is in fact what NSExpression is all about.

So, if you're working with a user-provided string, don't try to change the expression by changing the string. Let NSExpression parse it, then use properties of the resulting object to pick it apart into its constituent expressions. If your string is simply "1/2", then your expression has an array of two arguments and the function "divide:by:" — you can replace it with an equivalent function where one of the arguments is explicitly a floating-point value:

extension NSExpression {
    var floatifiedForDivisionIfNeeded: NSExpression {
        if function == "divide:by:", let args = arguments, let last = args.last,
          let firstValue = args.first?.constantValue as? NSNumber {
            let newFirst = NSExpression(forConstantValue: firstValue.doubleValue)
            return NSExpression(forFunction: function, arguments: [newFirst, last])
        } else {
            return self
        }
    }
}
Nun answered 15/1, 2013 at 20:30 Comment(1)
So should we modify the string to contain decimal point? Is that the only way?Nuzzle
C
4

Basically, you just need to tell it that you are performing floating point operation,

1.0/2
1.0/2.0
1/2.0

Will all work

Chardin answered 15/1, 2013 at 20:24 Comment(1)
this should be marked as the right answerCchaddie
N
1

Typing in NSExpression is much like in C: literals that look like integers (no decimal point/comma) are treated as integers and thus use integer division. (Under integer division, 1/2 is zero. If you want 0.5, you need floating point division.) This happens when the expression is parsed and evaluated, so attempting to change the type of the result or the formatting of the output has no effect -- those things happen after parsing and evaluation.

If your calculationString is entirely under your control, it's easy to make sure that you use floating point literals anywhere you want floating point division. (That is, use 1.0/2 instead of 1/2.) If not, you'll need to change it such that it does -- here it's probably better to decompose the parsed NSExpression and change an operand rather than munge the string.


Followup edit on the "decompose" bit: String munging in content that you know to have higher-order structure is generally problematic. And with NSExpression, you already have a parser (who's smarter than a simple regex) decomposing the string for you — that is in fact what NSExpression is all about.

So, if you're working with a user-provided string, don't try to change the expression by changing the string. Let NSExpression parse it, then use properties of the resulting object to pick it apart into its constituent expressions. If your string is simply "1/2", then your expression has an array of two arguments and the function "divide:by:" — you can replace it with an equivalent function where one of the arguments is explicitly a floating-point value:

extension NSExpression {
    var floatifiedForDivisionIfNeeded: NSExpression {
        if function == "divide:by:", let args = arguments, let last = args.last,
          let firstValue = args.first?.constantValue as? NSNumber {
            let newFirst = NSExpression(forConstantValue: firstValue.doubleValue)
            return NSExpression(forFunction: function, arguments: [newFirst, last])
        } else {
            return self
        }
    }
}
Nun answered 15/1, 2013 at 20:30 Comment(1)
So should we modify the string to contain decimal point? Is that the only way?Nuzzle
C
1

I think You need to User DDMathParser Which is best in this situation. I have used it in One of my project which is facing same problem as you have faced

DDMathEvaluator *eval = [DDMathEvaluator defaultMathEvaluator]; id value=[eval evaluateString:@"1/2" withSubstitutions:nil error:&error]; NSLog(@"Result %@",value); Result 0.5

Cascio answered 19/5, 2014 at 5:15 Comment(0)
J
0

Rickster's solution worked, but had problems with expressions like 5*5/2, where the first argument (here 5*5) was not just a number.

I found a different solution here that works for me: https://mcmap.net/q/1176867/-can-i-force-nsexpression-and-expressionvalue-to-assume-doubles-instead-of-ints-somehow

Josephson answered 7/7, 2021 at 6:52 Comment(0)
E
0

for people who still have this problem i did a somewhat quick fix:

extension String {
    var mathExpression: String {
        var returnValue = ""
        for value in newString.components(separatedBy: " ") {
            if value.isOperator {
                returnValue += value
            } else {
                returnValue += "\(Double(value) ?? 0)"
            }
        }
        
        return returnValue
    }
    
    var isOperator: Bool {
        ["+", "-", "/", "x", "*"].contains(self)
    }
}
Ellen answered 5/10, 2021 at 7:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.