43

My teacher has assigned a program to use both if-else statements and switch statements, so we understand how to implement both. The program asked us to prompt the user to input their weight and height in pounds and meters respectively. This is my attempt:

Without the switch

#include "stdafx.h"
#include <iostream>

using namespace std;


int main()
{
    double height, weight, BMI, heightMeters, weightKilo;
    const double KILOGRAMS_PER_POUND = 0.45359237;
    const double METERS_PER_INCH = 0.0245;

    cout << "Please enter your height (inches) and weight (pounds)" << endl;
    cin >> height >> weight;

    weightKilo = weight*KILOGRAMS_PER_POUND;
    heightMeters = height*METERS_PER_INCH;
    BMI = weightKilo / (heightMeters*heightMeters);

    if (BMI < 18.5) {
        cout << "You are underweight " << endl;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        cout << "You are normal" << endl;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        cout << "You are overweight" << endl;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        cout << "You are obese" << endl;
    }
    else {
        cout << "You are gravely overweight" << endl;
    }
}

With the switch

#include "stdafx.h"
#include <iostream>

using namespace std;


int main()
{
    double height, weight, heightMeters, weightKilo;
    int BMI, q;
    const double KILOGRAMS_PER_POUND = 0.45359237;
    const double METERS_PER_INCH = 0.0245;

    cout << "Please enter your height (inches) and weight (pounds)" << endl;
    cin >> height >> weight;

    weightKilo = weight*KILOGRAMS_PER_POUND;
    heightMeters = height*METERS_PER_INCH;
    BMI = weightKilo / (heightMeters*heightMeters);

    if (BMI < 18.5) {
        q = 1;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        q = 2;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        q = 3;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        q = 4;
    }
    else {
        q = 5;
    }

    switch (q) {
        case 1: cout << "You are underweight" << endl; break;
        case 2: cout << "You are a normal weight " << endl; break;
        case 3: cout << "You are overweight" << endl; break;
        case 4: cout << "You are obese" << endl; break;
        case 5: cout << "You are gravely overweight" << endl; break;
    }
}

This was the way I thought of, to include a switch statement. Is there some way to implement the first code block to just a switch statement?

I was almost certain that it could not be done to use ranges nor to use doubles (18.5). I emailed my teacher and they gave me an answer along the lines of

It may not make sense to you, but sometimes you are going to have to write a program that does not make sense. I am not saying that you don't have legitimate questions, but if anyone can figure it out you can. But then, maybe it can't be figured out. That's the challenge".

So, I'm asking: Is there some method to just use a switch statement for the first code block, or is what I did the best way to use a switch statement in the code, even if it is in no way necessary?

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
TEEBQNE
  • 6,104
  • 3
  • 20
  • 37
  • 3
    You can't use switches with doubles. – robert Oct 09 '15 at 12:12
  • 2
    Unrelated: the conversion from inches to meters is 0.0254 m/in. Also it is very nice to see named conversion factors in your code. I can't tell you how many "magic numbers" I come across in legacy code where I can't figure out what the heck the number means. – Carlton Oct 09 '15 at 12:16
  • I know this which is why I e-mailed my teacher saying it wasn't possible. – TEEBQNE Oct 09 '15 at 12:17
  • 6
    *"It may not make sense to you but sometimes you are going to have to write a program that does not make sense."* - That a strange thing to tell to a student. – Christian Hackl Oct 09 '15 at 12:28
  • 4
    @ChristianHackl Yes it is. I believe it because she doesn't know what she is talking about. – TEEBQNE Oct 09 '15 at 12:32
  • Related to unrelated; that's too much of an approximation when using `double`. There are 39.3700787 inches to a metre. – Bathsheba Oct 09 '15 at 12:47
  • @ChristianHackl: I think the first thing we should teach everyone is that 99% of what you will do in your life will not make sense. For programming, the initial concept of how to get the job done basically *never* translates to reality. I have had to tell people over and over that programming is not about logic, reason, rules, ideas, or other such things. Much of the time it is drudge-work, motivated by a few brilliant but un-implementable ideas. Finding a way to implement them is the real work, and it is not rational. Languages make this process even less intuitive than it should be. –  Oct 09 '15 at 16:20
  • 1
    @nocomprende, I strongly disagree. When studying a problem set, the solution may not seem to translate very well to a language like C++ because you have to consider that the C++ language models a very low level machine: pointers and bit types and whatnot. Solutions written in a functional language will closely resemble mathematical models of your problem set, though. – sleblanc Oct 09 '15 at 17:37
  • @sebleblanc: I think that very few programming problems have anything to do with things that can be modeled mathematically. Much programming is in business situations, and the rules can be strange, perplexing or downright ridiculous. Even a situation of a system that is fairly reasonable often has one or two wrinkles that make no sense at all, which means corrupting the nice logic with unpleasant and disruptive special cases. Exceptions are the rule, overall. No language models craziness well. –  Oct 09 '15 at 17:50
  • 1
    @nocomprende Just because the logic you have to implement doesn't make sense (because of bureaucracy or stupidity or whatever) doesn't mean your code has to be a mess. The *implementation* (your code) should still make sense when I read it even if what it's supposed to do doesn't. (Feel free to leave comments and documentation about issues where you've correctly implemented something that seems unintuitive.) – jpmc26 Oct 09 '15 at 21:37
  • @jpmc26 "*Schoen Machine*" –  Oct 10 '15 at 01:09
  • @nocomprende: Whether something makes sense or not is a matter of how much value you personally attach to it, hence a very subjective thing and something everyone can decide for themselves. It's true that in the industry you often have to implement things that are technically wrong, but this is often (not always) the fault of lacking social skills, i.e. being unable to explain the importance of certain technical issues to business managers. Many programmers are simply unable to mentally translate "bad code" or "bad design" into actual business problems. – Christian Hackl Oct 10 '15 at 12:17
  • @ChristianHackl: I think you make the assumption that the machine, or the language or the design process are somehow ideal. *They are not*. We are not prescient, so NO system or method that we come up with *will ever be* ideal. It is not a matter of convincing people how to do things the right way. There is no right way. We learn and progress, so we are at all times wrong. Programming is particularly so because it is entirely an invention of the mind, which is the area we lack most in knowledge and awareness. Many people could never understand a program. These are our managers and customers. –  Oct 12 '15 at 13:32

7 Answers7

110

As always in C++, favour standard library algorithms. In this case you want to do a range lookup. This is easy with an ordered sequence of boundaries:

double const boundaries[] = { 18.5, 25, 30, 35 };

switch (upper_bound(begin(boundaries), end(boundaries), BMI) - boundaries) {
    case 0: cout << "You are underweight "       << endl; break;
    case 1: cout << "You are normal"             << endl; break;
    case 2: cout << "You are overweight"         << endl; break;
    case 3: cout << "You are obese"              << endl; break;
    case 4: cout << "You are gravely overweight" << endl; break;
};

Actually, I suggest you

See a live demo on Coliru

#include <iostream>
#include <algorithm>

const char* bmi_classification(double bmi) {
    static double const boundaries[] = { 18.5, 25, 30, 35 };

    double const* lookup = std::upper_bound(std::begin(boundaries), std::end(boundaries), bmi);
    switch (lookup - std::begin(boundaries)) {
        case 0: return "underweight";
        case 1: return "normal";
        case 2: return "overweight";
        case 3: return "obese";
        case 4: return "gravely overweight";
    }
    throw std::logic_error("bmi_classification");
}

int main() {
    for (double BMI : { 0.0, 18.4999, 18.5, 24.0, 25.0, 29.0, 30.0, 34.0, 35.0, 999999.0 }) {
        std::cout << "BMI: " << BMI << " You are " << bmi_classification(BMI) << "\n";
    }
}

Prints

BMI: 0 You are underweight
BMI: 18.4999 You are underweight
BMI: 18.5 You are normal
BMI: 24 You are normal
BMI: 25 You are overweight
BMI: 29 You are overweight
BMI: 30 You are obese
BMI: 34 You are obese
BMI: 35 You are gravely overweight
BMI: 999999 You are gravely overweight

BONUS

You can be more elegant without the requirement to use switch:

Live On Coliru

const char* bmi_classification(double bmi) {
    constexpr int N = 5;
    static constexpr std::array<char const*, N> classifications {
        { "underweight", "normal", "overweight", "obese", "gravely overweight" }};
    static constexpr std::array<double, N-1> ubounds {
        { 18.5, 25, 30, 35 }};

    auto lookup = std::upper_bound(std::begin(ubounds), std::end(ubounds), bmi);
    return classifications.at(lookup - std::begin(ubounds));
}
Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 30
    A beautiful implementation. The OP will benefit by studying every part, as did I. – Bathsheba Oct 09 '15 at 12:35
  • Thanks so much. I guess what i'm confused about is how to get a user input for it. When I remove the for loop, it prints out the incorrect bmi type. Is there another function I can use other then upper_bound, since this is just picking the BMI type one over, since it prints the value over the val. (Ex: if it is normal it prints overweight, underweight it prints normal, etc). – TEEBQNE Oct 09 '15 at 13:03
  • Is there a bug in "0 You are normal"? Shouldn't that be underweight? – P45 Imminent Oct 09 '15 at 13:04
  • 1
    @P45Imminent Ooops. Yes, removed the extraneous `0` boundary element. Thanks! – sehe Oct 09 '15 at 13:07
  • @user3657449 You already had the input part. Just combine these! (And use `double` of course) http://coliru.stacked-crooked.com/a/78d17057957f19a3 – sehe Oct 09 '15 at 13:10
  • 2
    In your switch statement, why you didn't write : `upper_bound(...) - begin(boundaries)` It looks to me that it's more generic that way. – Pumkko Oct 09 '15 at 13:11
  • @Pumkko`std::distance`, even. –  Oct 09 '15 at 13:13
  • @Cthulhu and others: I've incorporated some style improvements anyways, now that this answer took off so much. Thanks :) – sehe Oct 09 '15 at 16:18
  • I would be tempted to bundle a bunch of that into `size_t upper_bound_index( List const& l, Element const&e ) { using std::begin; using std::end; auto it = std::upper_bound( begin(l), end(l), e ); return it - begin(l); }`, which decouples the implementation from the logic of `bmi_classification`, myself. – Yakk - Adam Nevraumont Oct 09 '15 at 19:23
  • @Yakk If you use an algorithm a lot, yes by all means. I'd probably generalize `index_of` first :) – sehe Oct 09 '15 at 19:30
  • Why don't you use an array instead of that ugly switch? – Ismael Miguel Oct 11 '15 at 10:32
  • @IsmaelMiguel I think you missed the question title? _("Range checks using a switch statement")_. I added the lookup approach just for comparison now (see [Bonus](http://coliru.stacked-crooked.com/a/bb742e91e108466b)) – sehe Oct 11 '15 at 16:40
21

Unless you have an absolutely ghastly compiler extension, you can't switch on a range in C++.

But you could use a switch elegantly if you create a std::vector of the BMI ranges:

std::vector<double> v = {18.5, 25.0 /*etc*/}

Then use std::lower_bound along with std::distance to get the position of a given BMI in the above ranges. This is the quantity that you switch on.

You could then go one stage further and define a std::vector<std::string> of the output messages. Then you need neither a switch nor an if block! All the selection logic is delegated to std::lower_bound.

I deliberately haven't given you the full code: I trust these hints are sufficient.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 1
    Although I don't use gcc [case ranges](http://stackoverflow.com/a/18853544/1708801), I feel calling such extensions ghastly is a bit over the top commentary. – Shafik Yaghmour Oct 15 '15 at 19:09
8

We need to fit in the input, so, instead of this code:

if (BMI < 18.5) {
        q = 1;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        q = 2;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        q = 3;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        q = 4;
    }
    else {
        q = 5;
    }

    switch (q) {
    case 1: cout << "You are underweight" << endl; break;
    case 2: cout << "You are a normal weight " << endl; break;
    case 3: cout << "You are overweight" << endl; break;
    case 4: cout << "You are obese" << endl; break;
    case 5: cout << "You are gravely overweight" << endl; break;

    }

You need something like

switch (1 + (BMI >= 18.5) + (BMI >= 25) + (BMI >= 30) + (BMI >= 35)) {
    case 1: cout << "You are underweight" << endl; break;
    case 2: cout << "You are a normal weight " << endl; break;
    case 3: cout << "You are overweight" << endl; break;
    case 4: cout << "You are obese" << endl; break;
    case 5: cout << "You are gravely overweight" << endl; break;
}

The logic is to convert the if-elses into a mathematical formula, returning an int.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • 1
    Difficult to maintain: you have to remember to update the `5` whenever you add a new case. –  Oct 09 '15 at 12:41
  • That is true. Generally, there are pros and cons whenever you have a solution. I believe the idea here is valid, but whether it is the best, it is up to debate. – Lajos Arpad Oct 09 '15 at 12:44
  • it's a nice idea to mathematical calculate an integer from the BMI. In this special problem there is probably no case to add in the future. BMI calculation-formula wont change. – Fanax Oct 09 '15 at 14:44
  • 1
    What about `switch ((BMI >= 18.5)+(BMI >= 25.0)+(BMI >= 30)+...)`? It looks simpler. – chi Oct 09 '15 at 15:38
  • @chi, you are right. The op expected 1 to be the first case, so I have left 1 + at the start, but I have edited my answer according to your suggestion. Thank you. – Lajos Arpad Oct 09 '15 at 16:05
  • 1
    Say no to `1` based indexes! – Yakk - Adam Nevraumont Oct 09 '15 at 19:43
  • In this case we have cases, not indexes. – Lajos Arpad Oct 10 '15 at 12:22
5

You can not use a double inside a switch. The documentation says:

switch ( expression )
   case constant-expression : statement
   [default   : statement]

The expression must be of an integral type or of a class type for which there is an unambiguous conversion to integral type. Integral promotion is performed as described in Integral Promotions.

On a side note:

There are some compilers (like Clang 3.5.1) which are allowing the case x ... y as an extension to the C++ language. But that too is for an integral datatype. Something like

switch(x){
       case 0:
            cout << "Test1";
            break;
       case 0 ... 9:
            cout << "Test2";
            break;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rahul Tripathi
  • 168,305
  • 31
  • 280
  • 331
3

A switch in C++ only allows you to check for the values of integers and chars.

The BMI is a double type, so it's not possible to check its value in a switch.

In your solution with the switch you also should declare the variable BMI as double. If you declare it as integer all decimal results will be casted to an integer, and you will lose the decimal places.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Fanax
  • 127
  • 3
  • 8
2

You could calculate your case labels dynamically from an array/vector instead of hardcoding an if/else expression:

//#include "stdafx.h"
#include <iostream>

using namespace std;


inline int seg(double d){ //calculate segment for a BMI of d
  constexpr double segs[] = { 18.5, 25, 30, 35 };
  constexpr int n = sizeof(segs)/sizeof(double);
  int r; for(r=0; r<n; r++)
    if(d<segs[r]) return r;
  return r;
}

int main()
{
  double height, weight, heightMeters, weightKilo;
  int BMI, q;
  const double KILOGRAMS_PER_POUND = 0.45359237;
  const double METERS_PER_INCH = 0.0245;

  cout << "Please enter your height (inches) and weight (pounds)" << endl;
  cin >> height >> weight;

  weightKilo = weight*KILOGRAMS_PER_POUND;
  heightMeters = height*METERS_PER_INCH;
  BMI = weightKilo / (heightMeters*heightMeters);



  switch (seg(BMI)) {
    case 0: cout << "You are underweight" << endl; break;
    case 1: cout << "You are a normal weight " << endl; break;
    case 2: cout << "You are overweight" << endl; break;
    case 3: cout << "You are obese" << endl; break;
    case 4: cout << "You are gravely overweight" << endl; break;
  }

}

(You could even make the seg functions constexpr if your really wanted to).

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
1

You can do something like:

switch ((round)BMI)
{
    case 1: case 2: case 3: .... case 15: case 16: case 17: cout<< "You are underweight " << endl; break;
    case 18: ... case 24: cout << "You are normal" << endl; break;
    case 25: ... case 29: cout << "You are overweight" << endl; break;
    case 30: ... case 34: cout << "You are obese" << endl; break;
    default: cout << "You are gravely overweight" << endl;
}

Also I couldn't help but notice this that since you are using if-else you can avoid the first condition in else-if statements like:

if (BMI < 18.5)
{
    cout << "You are underweight " << endl;
}
else if (BMI < 25.0)
{
    cout << "You are normal" << endl;
}
else if (BMI < 30.0)
{
    cout << "You are overweight" << endl;
}
else if(BMI < 35)
{
    cout << "You are obese" << endl;
}
else
{
    cout << "You are gravely overweight" << endl;
}

Apart from this, both of your implementations look good.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Karup
  • 2,024
  • 3
  • 22
  • 48