How to limit a decimal point to 1 from user input on a Calculator app?

# Postmortem: Decimal Input Overflow in Calculator Application

## Summary
A calculator application allowed users to input unlimited decimal digits via button presses despite UI-level safeguards. The attempted solution prevented duplicate decimal points but failed to enforce single-digit precision after the decimal separator in numeric inputs.

## Root Cause
1. Validation scope was incomplete:
   - Only the decimal separator button (`btnDot`) was restricted
   - Digit input buttons had no validation logic
2. Flawed validation condition:
   - Guard clause only triggered if **exactly 1 digit** existed after decimal point
   - Allowed input beyond first decimal digit
3. State inspection limitations:
   - No centralized validation of the full input state during digit entry

## Why This Happens in Real Systems
- **Partial input validation**: Developers often focus validation on specific controls rather than holistic input state
- **Stateless checks**: Input handlers sometimes lack awareness of overall field context
- **Incremental development**: Features evolve without revisiting validation boundaries
- **Assumption gap**: Expecting UI controls to intrinsically limit input formatting

## Real-World Impact
1. Data corruption: Calculations using over-precise values produce incorrect results
2. UI confusion: Users see unformatted numbers (e.g., `4.999999` instead of `5.0`)
3. System failure: Downstream systems might reject malformed numbers
4. Validation breaches: Business rules requiring single-decimal precision bypassed

## Example Code
### Problematic Implementation
```csharp
private void btnDot_Click(object sender, EventArgs e)
{
    if (!txtDisplay.Text.Contains("."))
    {
        txtDisplay.Text += ".";
    }
    else
    {
        int dotIndex = txtDisplay.Text.IndexOf(".");
        int digitsAfterDot = txtDisplay.Text.Length - dotIndex - 1;
        if (digitsAfterDot >= 1)  // Only prevents action when ≥1 digit exists
        {
            return;  // Doesn't prevent subsequent digit inputs
        }
    }
}

// Missing validation in digit handlers:
private void btnDigit_Click(object sender, EventArgs e)
{
    // No decimal position checks
    txtDisplay.Text += ((Button)sender).Text;
}

Corrected Implementation

private void AddToInput(string value)
{
    if (txtDisplay.Text.Contains("."))
    {
        int decimalPos = txtDisplay.Text.IndexOf(".");
        if (txtDisplay.Text.Length - decimalPos > 1) // Already has 1 decimal digit
        {
            return; // Block additional decimal digits
        }
    }
    txtDisplay.Text += value;
}

// All button handlers route through centralized validation:
private void btnDigit_Click(object sender, EventArgs e) 
    => AddToInput(((Button)sender).Text);

private void btnDot_Click(object sender, EventArgs e) 
    => AddToInput(".");

How Senior Engineers Fix It

  1. Implement input centralization:
    • Create single validation method for all input sources
    • Route all button handlers through unified logic
  2. Track input state:
    • Maintain explicit decimal presence flag
    • Monitor current decimal precision level
  3. Apply defensive parsing:
    • Use decimal.TryParse for type safety
    • Combine with regex validation (e.g., ^\d*\.?\d{0,1}$)
  4. Implement input masking:
    • Use TextBox events (TextChanged) for real-time filtering
  5. Design boundary tests:
    • Verify edge cases (0.0, 0.5, 10.9, 123.0)
    • Simulate rapid decimal/digit alternation

Why Juniors Miss It

  1. Event-centric thinking: Focuses on individual button events rather than holistic input state
  2. UI illusion: Assumes input controls inherently restrict invalid states
  3. Scope misalignment: Solves for preventing duplicate decimals but ignores digit behavior
  4. Boundary blindness: Fails to test single-digit post-decimal scenarios
  5. State-tracking avoidance: Prefers simple boolean checks over precise digit counting