2

I am learning JavaScript and currently I build simple tip calculator using function and switch statement. Basically, tip 20% of the bill when the bill is less than $50, 15% when the bill is between $50 and $200, and 10% if the bill is > $200. The sample input/argument:

simpleTipCalculator(124)
simpleTipCalculator(48)
simpleTipCalculator(268)

if you calculate manually, the result(expected result):

18.599999999999998
9.600000000000001
26.8

Here is my code so far:

function simpleTipCalculator(bill){
    let tip = 0
    switch(bill){
        case bill > 0 && bill < 50:
            tip = bill * 0.2
            break;
        case bill >= 50 && bill <= 200:
            tip = bill * .15
            break;
        default:
            tip = bill * .1

    }
    console.log(tip)
}

and the result of this function:

12.4
4.800000000000001
26.8

I feel confused about this and then I change my code to this:

function simpleTipCalculator(bill){
        let tip = 0
        switch(true){
            case bill > 0 && bill < 50:
                tip = bill * 0.2
                break;
            case bill >= 50 && bill <= 200:
                tip = bill * .15
                break;
            default:
                tip = bill * .1

        }
        console.log(tip)
    }

The result is what I expected.

18.599999999999998
9.600000000000001
26.8

My question is how did this happen? please explain me about that because I don't know what to look for on Google about this

  • 4
    `switch(bill)` is NOT the correct logic if all your `case`s resolve to booleans. A swtich is basically working as if `switch(a) /* ... */ case x: /* ... */ case y:` is actually `if (a == x) else if(a == y)`. So in your case you are trying to check if the bill *amount* is equal to `true` or `false`. – VLAZ Jul 10 '19 at 07:49
  • your function in short `(bill) => bill * (bill < 50? .2: bill <=200? .15: .1);` – Thomas Jul 10 '19 at 08:16

3 Answers3

4

When you do

switch(bill){

a case will be fulfilled if the expression that follows it is === to the value of bill. For example, if bill is 124, then switch(bill) would require

case: 124:
    tip = bill * .15 // because 124 <= 200

for your program to work as expected. Your code is producing unexpected results because all the cases fail, and it falls through to the default.

The switch(true) works because when you have cases of

case bill > 0 && bill < 50:

this will effectively evaluate to

case true
// block below will run, because `true` is `===` to the expression that was switched against

or

case false:
// block below will not run, because `false` is `!==` to the expression that was switched against

and run the case block accordingly.

switch is not the right tool for the job, IMO - it's confusing and verbose. (It's never the right tool for any programming task in Javascript, I think.) I'd use if/else instead:

const getTip = (bill) => {
  if (bill > 0 && bill < 50) {
    return bill * 0.2;
  } else if (bill >= 50 && bill <= 200) {
    return bill * 0.15;
  } else {
    return bill * 0.1;
  }
};

function simpleTipCalculator(bill) {
  console.log(getTip(bill));
}

simpleTipCalculator(124)
simpleTipCalculator(48)
simpleTipCalculator(268)
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 2
    "*`switch` is not the right tool for the job, IMO*" I completely agree. The inverted `switch` where you check if one of several expressions evaluates to `true` are very annoying to read and understand. – VLAZ Jul 10 '19 at 07:53
  • 1
    "*It's never the right tool for any programming task in Javascript, I think*" - That's quite severe. – Kévin Bibollet Jul 10 '19 at 07:58
  • @KévinBibollet I can't think of a situation where an object (indexed by values or functions) or `if`/`else` wouldn't be more concise and clearer. I know it's opinion-based, but do you have a suggestion of when `switch` might be preferable? I'm curious – CertainPerformance Jul 10 '19 at 08:03
  • @CertainPerformance - I only use `switch` when I am sure that the tested value is an integer and that there are more of two cases. The other cases could be hardly readable, or simply don't *need* a `switch`. But what do you mean by "*an object (indexed by values or functions)*"? That may be something that I didn't think of when I read your answer. Also you have probably a better vision of it since I have surely less experience in JS than you. – Kévin Bibollet Jul 10 '19 at 08:37
  • @KévinBibollet Can you give an example? Do you mean, eg something like https://gist.github.com/CertainPerformance/724867787578b99c8a7bd4c860fca5ad , is that a case where you'd prefer the `switch`? – CertainPerformance Jul 10 '19 at 08:49
  • @CertainPerformance - Yes, that's exactly what I was meaning. But I tend to think, in that particular case, that's clearly based on the developer's preference. However I don't know if there is a performance difference between `switch` and `if / else`. – Kévin Bibollet Jul 10 '19 at 09:00
  • @KévinBibollet Consider https://gist.github.com/CertainPerformance/f3e141c7e07ce9976b767231afc50a01 instead - I think it's unambiguously better, it's so much more terse (a big benefit when there are more than a couple cases) and doesn't require reassignment. Purpose is not performance (which rarely matters), but readability, and a greater signal-to-noise ratio. In that particular situation, or when the cases start at 0, you could also use an array, `const fruitNames = [, 'apple', 'banana', 'carrot']` (though sparse arrays are usually a bad idea) – CertainPerformance Jul 10 '19 at 09:02
  • 1
    @CertainPerformance - Thanks for sharing your point. I didn't think of using an object to replace the `switch` statement (and yet I should have think of it sooner). I am joining you and your opinion now. – Kévin Bibollet Jul 10 '19 at 09:16
1

switch case does not work the way you are using it.

switch(test){
    case a:
        break;
    case b:
        break;
    case c:
        break;
}

compares the value of test to the value of a, then b and so on

so in your first code example it compares 124 with the value/output of (bill > 0 && bill < 50) which is false

so you are comparing integers with booleans.
To make ranges work you have to do it like in your second example.

Stackoverflow post I found: https://stackoverflow.com/a/5619997/7584725

CodingKiwi
  • 676
  • 8
  • 22
1

This is because you are comparing switch(bill) where bill is number to boolean case: ... where case is if statement. This will always lead to default since any comparison would fail. You need to change switch as switch(!!bill) or simply switch(true)

function simpleTipCalculator(bill){
    let tip = 0
    switch(!!bill) {
        case (bill > 0 && bill < 50):
            tip = bill * 0.2
            break;
        case (bill >= 50 && bill <= 200):
            tip = bill * .15
            break;
        default:
            console.log('default');
            tip = bill * .1
    }
    return tip;
}

console.log(simpleTipCalculator(124));
jank
  • 840
  • 4
  • 7
  • What you have done does work in this scenario but is a concept for having troubles to debug your code if it does not produce the desired output. Think what happens if you try to do `!!bill` when `bill` is a negative value. Or a string. Or an object. JavaScript might be lenient for some hackisch code but don't try it if it is not consistent for all values. – KarelG Jul 10 '19 at 08:00
  • Negative bills do not exists, `!!-5` returns `true`.. anyway, my point is if you are refering to edge cases negative values should be checked when you get the bill value, not in tip calculation. Also this function should not be called if bill does not exists, or if bill is an object, in this case you should send eg. price into function not the whole object. Otherwise there would be too many checkings in every single functions that values are actually correct – jank Jul 10 '19 at 08:15
  • 1
    @jank so your argument is that `!!bill` will always be `true`? *(at least for reasonable arguments)* Then why don't you simply write `true`? – Thomas Jul 10 '19 at 08:19
  • @jank yes, but your answer and your snippet still contain `!!bill`, which is logically incorrect, and the only argument to write it like this is the argument you make in your comment: that (for all reasonable arguments) `!!bill === true`. So I'm asking: why don't you simply write `true` which would also be logically correct. It makes no sense for this function to check wether the value in `bill` is `truthy`. What you want, is to compare `true` to the conditions in the `case` blocks. – Thomas Jul 10 '19 at 08:35
  • @Thomas That's all true, in my answer i just explained what was he comparing, how he should use `bill` variable to work with his statements and how he should simplify it since we know how `!!bill` will perform. – jank Jul 10 '19 at 08:52
  • @jank ideally, the `bill` variable *shouldn't* be used in the switch in this scenario. Explaining how to do it despite that is misguided at best and can actively harm the logic at worst. – VLAZ Jul 10 '19 at 08:54