0

I do not understand why my output is coming out as wrong. I have attached my code and my result(highlighted the issue)

I am adding a coin to the insertCoin function in VendingMachine class. As soon as I add 10 cents to this function, it prints out error-> Does not accept 10 cents. I am converting the input to float using static_cast. I spent some good time on this and at this point, I feel I just cannot see the issue probably because I dont understand some concept.

Also as a quick background, new to c++ and trying to get my object oriented programming up to date. Trying to make a Vending machine product hehe. Thank you again !

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
#include <stdlib.h> 
using namespace std;

class Item
{
    private:
        string m_name;
        float m_cost;
    public:
        Item(string t_name,float t_cost):m_name(t_name),m_cost(t_cost)
        {
            
        }
        string getName()
        {
            return m_name;
        }
        float getCost()
        {
            return m_cost;
        }
};
class VendingMachine
{
    private:
        vector<Item> m_totalItems;
        unordered_map<string,Item>m_products;
        float m_remainingCharges{0};
        float m_moneyInserted{0};
        size_t itemCount{0};
    public:
        VendingMachine()
        {
            
        }
        void addItemToVendingMachine(string t_name,size_t t_cost)
        {
            float temp=static_cast<float>(t_cost)/static_cast<float>(100);  
            Item item(t_name,temp);
            m_totalItems.push_back(item);
            m_products.insert(make_pair(t_name,item));
        }
        bool chooseProduct(string t_name)
        {
            for(auto item:m_totalItems)
            {
                if(item.getName()==t_name)
                {
                    m_remainingCharges=item.getCost();
                    return true;
                }
                itemCount++;
            }
            cout<<"Item not currently available: "+t_name<<endl;
            return false;
        }
        void insertCoin(size_t t_coin)
        {   
            float temp=static_cast<float>(t_coin);
            if(t_coin<=50)
            {
                temp/=100;
                cout<<temp<<endl;
            }
            if(temp==0.01 or temp==0.05 or temp==0.1 or temp==1.00 or temp==2.00 or temp==0.25 or temp==0.50)
            {
                m_moneyInserted+=temp;
                m_remainingCharges-=m_moneyInserted;
            }
            else
            {
                cout<<"Does not accept: "<< t_coin<<" ,please insert correct coin."<<endl;
                return;
            }
        }
        pair<Item,float> getProduct()
        {
            auto item=m_totalItems[itemCount];
            auto itemBack=m_totalItems.back();
            m_totalItems[itemCount]=itemBack;
            m_totalItems.pop_back();
            return make_pair(item,abs(m_remainingCharges));
        }
        
        float refund()
        {
            if(m_remainingCharges<0)
                return abs(m_remainingCharges);
            else
                return 0;
        }
        void resetOperator()
        {
             m_remainingCharges=0;
             m_moneyInserted=0;
             itemCount=0;
        }
};
int main()
{
    Item item("Candy",0.50);
    cout<<item.getName()<<" ";
    cout<<item.getCost()<<endl;
    VendingMachine machine;
    machine.addItemToVendingMachine("CANDY",10);
    machine.addItemToVendingMachine("SNACK",50);
    machine.addItemToVendingMachine("Coke",25);
    machine.insertCoin(10);
    machine.insertCoin(25);
    machine.insertCoin(50);
    machine.chooseProduct("CANDY");
    auto temp=machine.getProduct();
    cout<<temp.first.getName()<<endl;
    cout<<temp.second<<endl;
    machine.resetOperator();
    return 0;  
};

enter image description here

  • 1
    Don't compare `float`s with `==`. Read [this](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) if you haven't before. – cigien Sep 25 '20 at 16:52
  • 2
    Don't use floating point at all for money. Keep everything in the lowest denomination (pennies or whatever) until you need to print something. – 001 Sep 25 '20 at 16:54
  • 1
    It's interesting that this program already contains the solution: count cents – harold Sep 25 '20 at 16:54
  • 1
    When coding anything having to do with money, don't use `float` or `double` use `string` (as entered by user), and `int` or `long` (number of cents). It is the only sane way. – Jeffrey Sep 25 '20 at 16:55

2 Answers2

0

Why doesn't it work as expected ?

This is not specific to C++ but to floating point numbers.

The C++ standard doesn't tell how floating points are encoded. But in general, floating point numbers are encoded using power of two fractions. And with these schemes, some decimal numbers have no exact match and instead, the closest match is used, with some approximation.

For example, the most popular encoding is probably IEEE-754. This nice online converter, shows that there is no exact match for 0.1 . The closest approximation is 0.100000001490116119384765625:

  • If you print this value out, with all the rounding, everything will seem fine.
  • But if you compare for equality with ==, the value must be exactly the same. Unfortunately different calculations may make different roundings. So you will get two different numbers, very close to 0.1 but different from each other.
  • In your case, the literal value 0.1 is not a float: it's a double, which has a higher precision than a float and hence makes different approximations.

Practical evidence

If you're not convinced, try the following changes:

    if(t_coin<=50)
    {
        temp/=100;
        cout.precision(10);  // print more digits
        cout<<scientific<<"temp:"<<temp<<" vs.double "<<0.1
             <<" or float "<<0.1f<<endl;
        }

and then try to compare floats with floats:

    if(temp==0.01f or temp==0.05f or temp==0.1f 
        or temp==1.00f or temp==2.00f 
        or temp==0.25f or temp==0.50f)

Online demo

How to solve it?

The best option here, is to work with integers and count the cents, as someone suggested in the comments. There is no floating point induced approximation in such calculation, so for money it's ideal.

A workaround it to align all the floating point numbers to either double or float (by adding a trailing f to the numeric literals). This would work when comparing constant values, if there could b e no rounding issue inf some calculations.

Another solution is to replace the strict equality comparison, with an ineguality checking that the difference between both numbers is very small. This is the epsilon approach proposed by Jason in the other answer, comparing the values with the help of a function almost_equal() defined as explained here

Not related: don't use size_t for reperesenting money. THhis is misleading for the readers and maintainers ;-). If you want an unsigned int, or an unsigned long, or an unsigned long long say so.

Christophe
  • 68,716
  • 7
  • 72
  • 138
0

You can use the epsilon to compare floating point numbers, overcoming the fact that it's very difficult to precisely equate floating point numbers, you have to use "very nearly equals". See this post for more detail: What is the most effective way for float and double comparison?

Personally I would recommend changing your code to work in integer units of cents in order to avoid having to implement the "very nearly equals" pattern throughout your code.

Den-Jason
  • 2,395
  • 1
  • 22
  • 17