تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Reconciling Order Discrepancies0:00

One of the most scary things that I have to deal with is money, not money for a business, but money for other people. I have to calculate what they need to pay and I have to calculate what we need to pay them. And you know, best case scenario, I screw something up for me. Worst case scenario, I screw up something for them and don't get between people and their money. And that's exactly what we have done, except

and don't get between people and their money. And that's exactly what we have done, except that in this particular case, the customers end up just a little bit better because I have a little script that reconciles our orders. In fact, this is reconciling 100 orders. But we can see that there is, well there are a lot of discrepancies and everything is off by 1 cent. What is stored in the database, which is

of discrepancies and everything is off by 1 cent. What is stored in the database, which is what we charged the customers, is 1 cent less than what the actual calculation is. So some customers get a little 1 cent break, other customers have to pay the full price. Either way, this is something that needs to be fixed. But the good thing is that it is very consistent. All of the differences is just one sin, which indicates a rounding issue.

Floating-Point Precision Issues1:12

All of the differences is just one sin, which indicates a rounding issue. But let's look at some other possible reasons for something like this. And one of the most common problems is just plain arithmetic because computers aren't very good at floating point arithmetic. For example, here we have 0.1 plus. Point two is 0.3, that's obvious. However, if we actually check this to see if 0.1 plus 0.2

Point two is 0.3, that's obvious. However, if we actually check this to see if 0.1 plus 0.2 is 0.3, we see that it's false and that looks very wrong because obviously it said hey, it is 0.3. But if we actually print out the number that is calculated, well we're gonna see something very different. So 0.1 plus 0.2 is 0.3, and then a lot of zeros and then something other than zero.

So 0.1 plus 0.2 is 0.3, and then a lot of zeros and then something other than zero. Now this goes back to what I said earlier. Computers are not very good at floating point arithmetic. This is not a PHP issue. This is really not a programming language issue. This is a computer issue. But every programming language has this problem. It's why there are many programming languages that have a completely different data type for working

It's why there are many programming languages that have a completely different data type for working with decimal numbers because it's much more precise. Working with floating points, not so precise. It's kind of like if you did one over three and converted that into a decimal, you get 0.3, 3, 3, 3 repeated. It's that same kind of thing because computers are very good at integer arithmetic, but not so with decimals

because computers are very good at integer arithmetic, but not so with decimals because integer can be essentially translated into binary numbers. Decimals not so much. So that's one of the problems that we could be seeing. But usually if it's an arithmetic issue, it wouldn't be always just 1 cent off and and really, uh, 1 cent less. It might be a cent less,

Rounding vs Floor Errors3:14

and really, uh, 1 cent less. It might be a cent less, it might be a cent more, you know, things like that. This is consistently just 1 cent less, which seems to indicate a routing issue. Like for example, let's say that we have a subtotal of 5,080. Now remember we are dealing with, so that is 5080 cents, which is $50 and 80 cents. So if we wanted to calculate the tax of that,

that is 5080 cents, which is $50 and 80 cents. So if we wanted to calculate the tax of that, we would take the subtotal and multiply that by the tax rates, which is 0.8 or 0.08. So the tax for $50 and 80 cents is $4 and 6 cents 0.4. Or if we wanted to just use terms of cents, it's 406.40 cents. Now we can't have 0.40 cents, we have to round that.

it's 406.40 cents. Now we can't have 0.40 cents, we have to round that. So if we round that using the round function, then it's rounded down to 4 0 6. Makes sense. Some people might use the floor function, in which case, once again it's 4 0 6, but what if the tax was actually something like 4 0 6 0.7? Now if we round that, we get 4 0 7 because we are rounding up to the next integer,

Now if we round that, we get 4 0 7 because we are rounding up to the next integer, which is 4 0 7. So that is 407 cents. Whereas if we call floor, well then that is going to floor, it's gonna round down to 406 cents. So this right here, we are off by one and not just, you know, plus one, this is off by minus one. So this is more than likely the issue. Now a bug like this is very simple to implement

So this is more than likely the issue. Now a bug like this is very simple to implement because a lot of projects are built by multiple people and those different people a lot of times work on different parts of the program. So if developer A is working on the checkout functionality, he might use round, whereas developer B might be working on the reporting system, in which case he would use the floor. Now, you know, you can make the argument that, well,

system, in which case he would use the floor. Now, you know, you can make the argument that, well, you know, whoever is designing this application needs to come up with the guidelines and everyone needs to follow the guidelines. Absolutely. But we're people, we're gonna make mistakes and sometimes we're not gonna follow the guidelines either by on purpose or it's just a mistake. So if we take a look at the order calculator, we actually have two methods.

Finding the Buggy Code5:44

So if we take a look at the order calculator, we actually have two methods. We have the buggy version and then we have the correct version. And if we look at the buggy version, we get the total sense, we build the subtotal and then we calculate the tax. But in that process right here, we're using floor as opposed to round. And that is going to be the problem because we also have the correct version of calculate,

And that is going to be the problem because we also have the correct version of calculate, which is using round. So that's going back to how do we prevent this type of thing. You can come up with your guidelines and things like that, but again, we're people, we're gonna make mistakes. What we need to do is find ways to completely eliminate the mistakes. And when it comes to working with money, I don't want

Introducing a Money Value Object6:27

to completely eliminate the mistakes. And when it comes to working with money, I don't want to make mistakes at all. So I have what I call a value object. This is something that is to represent money so that anytime that I need to work with money, I use this money class. And this gives me a consistent way of working with money so that if there's any kind of rounding, it's automatically handled by the money class, it does it for me.

it's automatically handled by the money class, it does it for me. I don't have to worry about do I use round, do I use floor, do I use anything else because the money class is going to handle it for me. So let's fix the buggy method using this money class. This way we can eliminate, you know, all of the errors that would come from us. And if there were errors, it would come from that class. So, uh, let's start

Refactoring with Money Class7:20

And if there were errors, it would come from that class. So, uh, let's start by commenting out just about everything here. We still want to return the same things, but um, we will leave the existing code so that we can compare if we need to. So the first thing we need is our subtotal sense, and we're going to initialize that using our money class. Now the money class has a zero static method. Seems kind of weird to have,

Now the money class has a zero static method. Seems kind of weird to have, but I actually got the idea from a very old programming language. Uh, we could do it differently. There's a from sense method that we could have just used and then passed in zero. And this is what I had thought about originally, but that's a lot quicker. So we will start with our subtotal of zero,

but that's a lot quicker. So we will start with our subtotal of zero, but then we need to iterate over the items so that we can essentially build that subtotal. And we're gonna start with the item price. So once again, we're gonna use our money, but here we're gonna use that from cents. And we're gonna pass in the item price because the item's price is coming from the database. So therefore it is incense.

because the item's price is coming from the database. So therefore it is incense. But then we're gonna have the item total and we're gonna take the item price and we're going to multiply that by the quantity of the item that's in the cart. So we don't have to do any of the arithmetic ourselves, we don't have to write it. The money class is going to handle all of that for us. So then we will build the subtotal,

The money class is going to handle all of that for us. So then we will build the subtotal, except let's do it like this subtotal. But no, this needs to be subtotal cents. It's going to be the subtotal cents. And we're gonna add the item total. So we are building our subtotal cents here so that then it comes time to calculating the tax. So we will take our tax cents and we will take our subtotal cents,

So we will take our tax cents and we will take our subtotal cents, we'll call a percentage method so that we will pass in the tax rate, which will then allow us to get the total, which, what do we need to call that? We need to call that total cents because it makes total sense, sorry. And we will take the subtotal sense and add the tax. So you can see here that the money class is immutable.

and add the tax. So you can see here that the money class is immutable. You know, we have methods for multiplying and adding and percentage and things like that, but we aren't changing the value in that object. We are creating a new money object that represents that new value so that now we've built our tax sense, we have our total sense, and now we just need to supply that information to the view, which we could change the view to use the money object.

and now we just need to supply that information to the view, which we could change the view to use the money object. But, uh, let's not do that. So now we have a method called get sense, which is going to return whatever value we have in these money objects into sense. And so therefore they will be used in the views just as they normally have. But it, it, it is extra work. But a lot of times extra work is necessary to save us from

But it, it, it is extra work. But a lot of times extra work is necessary to save us from a lot more work later down the road. You know, I said that working with money is one of the scariest things that I do as a developer, and that's absolutely true. But a bug like this is, well, this is the reason why I'm scared of it, because these are bugs that are silent. There's no error, there's nothing that says, Hey, this is wrong.

There's no error, there's nothing that says, Hey, this is wrong. No, someone has to be paying attention or we have to have tools that are correct that work on the back end, that find these types of things. But the lessons here go way beyond money. I mean, yes, money is important, but one of those things is that consistency matters. You know, I talked about having a strategy or guidelines that the developers could have followed,

You know, I talked about having a strategy or guidelines that the developers could have followed, and yes, they could have followed that and that would've fixed this. But you know what, if for whatever reason the developers didn't follow the strategy, we are after all human. So having something in place, something that is as important as money is, you know, important to have something in place that essentially forces consistency.

as money is, you know, important to have something in place that essentially forces consistency. If you find yourself working with data that really needs to be correct, find a way to make working with that data be consistent and correct whenever you work with it. It may take a little extra work shoot, it may take a lot of extra thought to come up with something. But in the grand scheme of things, I'd rather take the time to build the tools that I need to ensure that I am consistent in my code and in my strategies.

to build the tools that I need to ensure that I am consistent in my code and in my strategies.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟