Let point be the known point index. Work backwards so that each step uses just one division, rather than separately computing positional weights:
var j = bin.length - 1
var fractionalPart = 0.0
while (j > point) {
  fractionalPart = (fractionalPart + (bin(j) - '0')) / 2.0
  j -= 1
}
The loop invariant, under exact arithmetic, is
Initially the suffix is empty. Incorporating digit and dividing by two gives exactly the same formula with replaced by . At exit , the result is . The loop variant is when a point exists. If a no-point input is handled by setting point=bin.length, the loop is skipped and the fractional contribution is zero; is a loop variant covering that convention too. Trailing zeroes and an empty fractional suffix require no special arithmetic. Floating-point rounding qualifies the exact formula for long inputs.
Equivalence partitioning divides the input domain into classes expected to exercise the same relevant behavior, then chooses representatives and boundary cases from each class. It is a way to select tests, not a proof that every member of a class must behave identically in a faulty implementation.
Useful classes for this parser include integer-only inputs, a leading point, a trailing point, and digits on both sides; all-zero inputs and inputs containing ones; leading or trailing zeroes; one-digit and longer blocks; and large integer or tiny fractional magnitudes near floating-point rounding, floating-point overflow and floating-point underflow boundaries. For instance, compare '1', '1.', '.1', '1.1', '0001.1000', and digit strings just beyond the exact integer precision of Double. Empty digit blocks have the zero convention used above. If a surrounding validator rejects an entirely empty representation or a bare point, test those as separate invalid-format classes. Unexpected digits and multiple points are likewise validator tests, since the conversion fragments assume well-formed input. Include representatives with independently calculated positional values, rather than relying only on inverse conversion.
Add the following concrete method inside FiniteDist[T]. It forms a pushforward of a finite probability distribution, adding the masses of outcomes that map to the same result:
def map[U](f: T => U): FiniteDist[U] = {
  val source = getDom
  val weighted = source.map(x => (f(x), getProb(x)))
  val target = weighted.map(_._1).distinct
  new FiniteDist[U] {
    def getDom: List[U] = target
    def getProb(y: U): Double =
      weighted.filter(pair => pair._1 == y).map(_._2).sum
  }
}
The input finite probability distribution is assumed to satisfy its interface, with stable masses during construction, and f must be a total deterministic pure function on its finite support. Output values need stable equality consistent with the mathematical equality being modeled; mutating equality-relevant fields afterwards would undermine any finite-support implementation. Computing weighted once also prevents later queries from re-evaluating f inconsistently.
Every source support element appears once in weighted. The distinct operation lists each image value once; every such image has a positive mathematical mass because it has a positive-mass preimage. For each , filtering selects exactly those with , so getProb(y) is the required sum, with an empty sum of zero off the image. The fibers form a set partition of the original support, giving
Thus colliding images have their probabilities added, not overwritten. For a concrete check, mapping a uniform distribution on by parity gives probability to each of ; mapping every source value to one constant gives a singleton domain with total mass one. Floating-point rounding can make a computed total differ slightly from one; the proof is for exact mathematical masses, and the representation uses Double approximations.
Let the committed model be amounts and an exchange rate , with invariant . Editing pounds keeps the rate and computes euros; editing euros keeps the rate and computes pounds; editing the rate keeps pounds and computes euros. A usable rate must be strictly positive, and every accepted floating-point number and derived result must be finite. Negative amounts are allowed. Rejecting invalid text preserves the last committed model.
Insert the following members inside the supplied MainFrame, after the three fields have been initialized. Its existing layout can remain as written:
private var p = 10.0
private var e = 11.5
private var r = 1.15
private var refreshing = false

private def finite(x: Double): Boolean = !x.isNaN && !x.isInfinity

private def showModel(): Unit = {
  refreshing = true
  try {
    pounds.text = java.lang.String.format(java.util.Locale.ROOT, "%.2f", Double.box(p))
    euros.text = java.lang.String.format(java.util.Locale.ROOT, "%.2f", Double.box(e))
    rate.text = r.toString
  } finally {
    refreshing = false
  }
}

private def edit(source: TextField): Unit = {
  try {
    val v = source.text.trim.toDouble
    if (finite(v)) {
      var nextP = p
      var nextE = e
      var nextR = r
      if (source eq pounds) {
        nextP = v
        nextE = v * r
      } else if (source eq euros) {
        nextE = v
        nextP = v / r
      } else if (source eq rate) {
        nextR = v
        nextE = p * v
      }
      if (nextR > 0.0 && finite(nextP) && finite(nextE) && finite(nextR)) {
        p = nextP
        e = nextE
        r = nextR
      }
    }
  } catch {
    case _: NumberFormatException => ()
  }
  showModel()
}

listenTo(pounds, euros, rate)
reactions += {
  case EditDone(source) if !refreshing => edit(source)
}
The invariant is interpreted with the usual floating-point rounding in multiplication and division. All candidate values are calculated before committing any of them, so invalid text, zero/negative rates, NaN, infinity and floating-point overflow leave the old model intact. The committed r remains positive, so euro-to-pound division is defined. Using a fixed formatting locale keeps the decimal separator compatible with toDouble. The displayed amounts are rounded to two decimal places, while the model retains their full available precision. The rate is not forcibly rounded to two places, which could otherwise turn a valid small positive rate into displayed zero.
The refreshing guard prevents synchronous derived-field notifications from recursively committing another edit. EditDone is an edit-completion event, not a request to react to every partial keystroke; pressing the field's completion action is enough to trigger the conversion. If a particular UI additionally needs live updates, it can subscribe to the appropriate text-change events with the same validation and authoritative-source policy. The supplied API and archived Scala Swing documentation suffice for the requested completion-event implementation; no unidentified practical library is required.[ref]
Avoid forming the enormous powers in the original ratios. Put , and ; algebra gives
Use simultaneous exponentiation by squaring to compute the two powers with one loop and a shared remaining exponent:
def ec(n: Int): Double = {
  require(n > 1)
  val dn = n.toDouble
  val a = 1.0 + 1.0 / dn
  val b = 1.0 + 1.0 / (dn - 1.0)
  var remaining = n - 1
  var u = a
  var v = b
  var pa = 1.0
  var pb = 1.0
  while (remaining > 0) {
    if (remaining % 2 == 1) {
      pa *= u
      pb *= v
    }
    u *= u
    v *= v
    remaining /= 2
  }
  ((dn + 1.0) * a) * pa - dn * pb
}
The exact-arithmetic loop invariant is , and . The even integer and odd integer cases preserve both equalities in parallel. The loop variant is remaining. On termination, the displayed expression is exactly the desired approximation in the ideal arithmetic model. For , the loop uses at most multiplications, followed by three more: multiplications and one loop.
Converting n to Double before adding one also avoids overflowing n + 1 at Int.MaxValue. The algebra avoids power overflow but does not remove catastrophic cancellation: the final subtraction combines quantities of order to obtain a result of order . For sufficiently large , floating-point rounding can outweigh the mathematical truncation error. For example, at n == 1000000 this Double computation returns approximately 2.7180521293, although the exact formula is approximately 2.71828182845916. Arbitrarily many requested decimal digits require increased arithmetic precision, not simply a larger Int argument.
Use sparse dot product by merging indices: only equal coordinate indices can contribute, so merge the two increasing index streams:
def dotProduct(vec1: Node, vec2: Node): Double = {
  require(vec1 != null && vec2 != null)
  require(vec1.index == vec2.index, "dimension mismatch")
  var p = vec1.next
  var q = vec2.next
  var answer = 0.0
  while ((p ne vec1) && (q ne vec2)) {
    if (p.index < q.index) p = p.next
    else if (q.index < p.index) q = q.next
    else {
      answer += p.value * q.value
      p = p.next
      q = q.next
    }
  }
  answer
}
The loop invariant states that the accumulator contains all common-coordinate products already passed, while no unprocessed smaller coordinate can acquire a future partner. If one current index is smaller, the other list has no remaining entry at that index, so its product is zero. Equal indices add their product and advance both. Once either list ends, all remaining coordinates of the other have zero partners.
Thus the code computes the dot product in coordinate order, subject to floating-point rounding, using time and constant auxiliary storage. It does not scan implicit zeros or alter either vector, and it also handles both arguments referring to the same header.
Use a customer object with no selected platforms as the base component, and wrap it once for each selected channel. Each decorator object has the same interface, delegates existing work, adds one platform send, and adds that platform's per-message charge. The code below uses the Customer and Comms interfaces from the question; every decorator receives the communication implementation explicitly.
final class NoChannels(val name: String) extends Customer {
  def spread(msg: String): Unit = ()
  def cost: Double = 0.0
}

abstract class CustomerDecorator(protected val inner: Customer)
    extends Customer {
  final val name: String = inner.name
  def spread(msg: String): Unit = inner.spread(msg)
  def cost: Double = inner.cost
}

final class OnDiscord(c: Customer, out: Comms)
    extends CustomerDecorator(c) {
  override def spread(msg: String): Unit = {
    super.spread(msg)
    out.sendD(name, msg)
  }
  override def cost: Double = super.cost + 0.01
}

final class OnFacebook(c: Customer, out: Comms)
    extends CustomerDecorator(c) {
  override def spread(msg: String): Unit = {
    super.spread(msg)
    out.sendF(name, msg)
  }
  override def cost: Double = super.cost + 0.05
}

final class OnWhatsApp(c: Customer, out: Comms)
    extends CustomerDecorator(c) {
  override def spread(msg: String): Unit = {
    super.spread(msg)
    out.sendW(name, msg)
  }
  override def cost: Double = super.cost + 0.03
}

def customer(name: String, discord: Boolean, facebook: Boolean,
             whatsapp: Boolean, out: Comms): Customer = {
  var c: Customer = new NoChannels(name)
  if (discord) c = new OnDiscord(c, out)
  if (facebook) c = new OnFacebook(c, out)
  if (whatsapp) c = new OnWhatsApp(c, out)
  c
}
The factory admits each platform at most once, matching a subset of preferences. For example, customer("reader", true, false, true, out) sends once to Discord and once to WhatsApp and has cost per message, using the stipulated charges. The innermost wrapper executes first, giving deterministic Discord–Facebook–WhatsApp order for selected channels. The zero-platform case sends nothing and costs zero. Repeated messages reuse the same immutable choice of wrappers.
By structural induction on the wrapper chain, the base sends to the empty subset with zero cost; one additional wrapper preserves all prior sends and adds exactly its channel and charge. Thus the sends and charge are composed from the same selected components. There are channel-specific classes instead of one class per subset, and a new channel needs one new wrapper. A direct manual construction containing the same wrapper twice would intentionally send and charge twice, so the factory prevents that configuration. cost computes a per-message amount rather than charging by itself; a later payment mechanism can use it. The interface uses Double, so numerical tests should allow ordinary floating-point rounding.
Round-trip testing 2026-10-24
Test a conversion together with its intended inverse. Equality of representations may need normalization, while equality of numerical values can be limited by floating-point rounding or truncation. Distinct strings can encode the same value, so a value round trip and a character-for-character round trip are different properties.
An independent oracle is still needed: the wrong functions and satisfy both composition identities on integers, although neither implements an intended identity conversion. Thus passing inverse-consistency tests does not alone establish the specification of either conversion.