I have a user interface with two numeric input boxes, send amount and receive amount. The values are in different currencies and are related by an exchange rate. That is, receive amount should always be send amount * exchange rate. But the user is allowed to click into either input box and modify the value, which causes the other value to be updated.
I’ve got a simple system to observe changes to either value and update the other one. I’ve had problems with infinite loops of the values updating each other because they’re floats and multiplying then dividing by the exchange rate doesn’t always create precisely the same value. So I added a simple flag to ignore updates while the updater itself is running.
But now I’m adding more functionality when the user updates an amount field, requiring other objects to listen to the fields. As a result some of those objects end up changing one of the amount fields, which sets off an infinite loop.
I’ve always felt like this is a fragile modelling of the problem, but I haven’t thought of anything better. Is there any established pattern or approach to modelling two values that update each other?
The right way to implement state and state change notifications is epitomized by the following setter method pseudocode:
method setValue( T newValue )
{
if( newValue equals existingValue )
return;
existingValue = newValue;
issue state change notification;
}
This is known to work, and to work perfectly. No infinite loops, no oscillations, no unnecessary notifications, no problems whatsoever.
Until one day someone decides to use floats.
Your problem is not with the very well established concept of state change notifications and the observer pattern, your problem is with the fact that you have been using a crappy data type for the job.
You tried to overcome the malfunctions caused by the imprecision inherent in floats with an approach which, although not outright wrong, was not really the correct approach for the problem at hand: you added a flag to ignore updates while the updater itself is running.
So, here are some approaches for solving your problem directly, instead of letting it be and trying to work around it:
-
Use proper equality comparisons for your floats:
if( abs( float1 - float2 ) < epsilon )
where epsilon is a very small number like 0.00000001, depending on the desired precision. For more information, see this: What’s wrong with using == to compare floats in Java? -
Use a “decimal” or “BigDecimal” data type instead of float. If you are dealing with currency values, you definitely should be doing this. For more information, see this: Why not use Double or Float to represent currency?
There’s a nice video from Apple’s WWDC about this subject. Solution is simple:
Stored somewhere is the truth. The truth is not a number, it is a pair (number, currency). If a user enters a number into a Yen field, the truth is changed to X Yen. When a dollar field is notified, it doesn’t change the truth to dollars. It displays the amount as best as it can, but it doesn’t change the truth doing that. Only when the user enters a number, the truth is changed.
When the question was asked, there was the assumption that there are two truths, related by a formula, and that is where the trouble starts. One truth, not two.
1
Add an argument called sender which can be chained together as events are raised. As a first step check to see if if the current object is on the chain, if so, just mark as handled so no updates occur.
Example
Button1 initiates change.
Sender: Button1
Button2 intercepts, first thing it does is check if it’s already on the sender chain. In this case it is not, so it does an update, adding another sender on the chain (Button1, Button2) and raises an event that it has changed. Button1 intercepts, inspects chain, Button1 and Button2 are on the chain, so it shouldn’t do an update and thus no more events will be raised.
This way the UI doesn’t end up chasing its tail.
The two input boxes are two views onto the same value. If one box is changed, the other is changed as well. Therefore, there should be no separate “on change” event to subscribe to. The two immediate event handlers just emit one common event that takes note of which box was edited. Pseudocode:
class ConvertFromTo(from: Input, to: Input) {
val on-change = new Observable[ChangeEvent]()
from.on-change.add(e => on-change.emit(new ChangeEvent(this, Direction.From, e.value))
to.on-change.add(e => on-change.emit(new ChangeEvent(this, Direction.To, e.value))
}
This allows external listeners to subscribe to the combined ConvertFromTo.on-change
observable. You can use this abstraction layer to weed out events that didn’t actually change the conversion value. If some component needs to be notified on changes to the displayed value, the can still subscribe directly to the change event of that input field.