Quadratic Upgrade 4  •  Analysis & Critique  •  10/07/2026
TNT | JavaQuadraticSaga — Teacher’s Analysis

Quadratic Upgrade 4 / Analysis & Critique

Seven improvements, one upgrade. The crown jewel: InvalidRootsException replaces System.exit(), giving the program a voice instead of a trapdoor. Plus constructor chaining, non-monic roots scaling, a cleaner toString(), an integer-indexed loop, and a compareTo() that mirrors the Java standard library.

APCS-Java Teacher & Java Developer Perspective
Overview

What Changed from Upgrade 3

Upgrade 3 did the hard mathematical work — it solved for coefficients from roots, introduced complex arithmetic, and built a defensive validation gate. Upgrade 4 does the engineering work: it replaces all the rough edges that the Upgrade 3 analysis flagged. Every one of the seven suggested improvements is implemented here.

Design philosophy shift: Upgrade 3 taught validate before you compute. Upgrade 4 teaches communicate, not terminate. The single biggest change is replacing System.exit(0) with a custom exception. The program no longer silences itself when it encounters invalid input — it speaks up and lets the caller decide.
Design Decision #1

InvalidRootsException

A checked exception is different from an unchecked one because the compiler enforces acknowledgement. If a class extends Exception, callers must either catch it or declare that they throw it onward. If the class instead extended RuntimeException, callers could ignore it at compile time. Upgrade 4 deliberately chooses the stricter path because this is exactly the kind of failure the driver should be forced to think about.

The constructor itself is tiny but meaningful. super(message) stores the explanation inside the inherited Exception machinery, which means the caller can later retrieve it with getMessage(). That makes the error portable, testable, and printable.

The key design difference is philosophical and practical: System.exit(0) kills the entire JVM. throw new InvalidRootsException(...) gives the caller a choice. In Test 5 of the driver, that choice is to print the message and continue to Test 6.

public class InvalidRootsException extends Exception {

    public InvalidRootsException(String message){
        super(message);
    }//end constructor

}//end class InvalidRootsException
try{
    ComplexOrderedPair z1 = new ComplexOrderedPair(2, 3);
    ComplexOrderedPair z2 = new ComplexOrderedPair(5, 3);   // wrong! must be (2,-3)
    Quadratic qBad = new Quadratic(z1, z2);
    System.out.println(qBad);   // this line is never reached
}catch(InvalidRootsException e){
    System.out.println("Caught: " + e.getMessage());
    System.out.println("Unlike System.exit(0), the program CONTINUES after this catch!");
}
Key Insight

A checked exception is the Java compiler enforcing a contract: “You MUST acknowledge this can fail.” System.exit() acknowledges nothing — it just stops everything. The exception says “something went wrong; here is the message; now you decide what to do.” In the driver, that decision is to print the message and continue.

Teacher note: A natural future step would be to declare public class Quadratic implements Comparable<Quadratic>. The current compareTo() method already follows the standard contract; implementing the interface would make that contract formal and type-safe.
Design Decision #2

Constructor Chaining

public Quadratic(OrderedPair r1, OrderedPair r2) throws InvalidRootsException {
    this(1.0, r1, r2);   // chain to non-monic with scale factor 1
}

this(...) is Java’s constructor-chaining keyword. It calls another constructor in the same class, and Java requires that it be the first statement in the constructor body. You cannot print, validate, or assign anything before it runs.

That rule matters here because the monic constructor is intentionally nothing more than a convenience shortcut. It says: “Run the entire non-monic constructor body, but plug in a = 1.0.” Without chaining, the class would need to duplicate the whole validation and coefficient-computation path, which would violate DRY (Don’t Repeat Yourself).

The throws InvalidRootsException clause also propagates naturally. If the delegated constructor can throw it, the one-line convenience constructor must acknowledge that fact too.

Verdict: Constructor chaining is one of the most underused Java patterns in introductory courses. It keeps the single-responsibility principle intact: the non-monic constructor does all the real work; the monic constructor is purely a convenience shortcut.

Design Decision #3

Non-monic Roots Constructor

For the monic case, the algebra is:

(x-r1)(x-r2) = x2 - (r1+r2)x + r1*r2

For the non-monic case, the scale factor multiplies through the entire expression:

a(x-r1)(x-r2) = ax2 - a(r1+r2)x + a*r1*r2

So:
b = -a*(r1+r2)
c = a*r1*r2

The driver demonstration is concrete and convincing: Quadratic(3.0, r1, r2) with roots 2 and 5 produces 3x2 - 21x + 30. That is exactly what the algebra predicts.

The private helper initFromRoots(double scaleA, ...) is the unsung hero. Both the monic and non-monic constructors flow through it, which means there is exactly one validation path and exactly one coefficient-computation path to maintain.

Verdict: The non-monic constructor completes the roots-constructor story. Upgrade 3’s monic-only constructor was mathematically correct but limited to parabolas with leading coefficient 1. Upgrade 4 opens it to any non-zero a.

Design Decision #4

Improved toString()

QuadraticUpgrade 3 toString()Upgrade 4 toString()
x2y = f(x) = 1.0x2 + 0.0x + 0.0y = f(x) = x2
2x2 - 6xy = f(x) = 2.0x2 + -6.0x + 0.0y = f(x) = 2x2 - 6x
x2 - 9y = f(x) = 1.0x2 + 0.0x + -9.0y = f(x) = x2 - 9
-x2 + 4xy = f(x) = -1.0x2 + 4.0x + 0.0y = f(x) = -x2 + 4x

This is the cleanest visible upgrade. StringBuilder is the professional Java habit here because the method conditionally assembles multiple fragments. Repeated string concatenation would work, but it creates many temporary string objects. StringBuilder is purpose-built for this job.

The suppression rules are well chosen: skip the bx term if b ≈ 0, skip the constant if c ≈ 0, and suppress the digit for coefficients of 1 and -1. The existing isEffectivelyZero() method does double duty by powering both the zero checks and the near-±1 tests.

Verdict: The cleanest visible improvement in Upgrade 4. Students instantly notice the difference in output. The use of StringBuilder is the correct Java pattern for conditional string assembly — a detail worth calling out in code review.

Design Decision #5

Integer-indexed Loop

// Old (Upgrade 3): floating-point counter accumulates error
for(double x = xmin; x <= xmax; x += xinc){ ... }
// New (Upgrade 4): integer counter, exact multiplication each step
int steps = (int) Math.round((xmax - xmin) / xinc);
for(int i = 0; i <= steps; i++){
    double x = xmin + i * xinc;   // exact: multiply, not accumulate
    ...
}

This is one of those engineering upgrades that looks small but matters deeply. In IEEE 754 floating-point arithmetic, 0.1 + 0.1 + 0.1 is not exactly 0.3. The error is tiny, but if you repeat that addition many times, the loop boundary can drift. That can cause the last x-value to be missed or duplicated.

Math.round((xmax-xmin)/xinc) computes the step count once, then each x-value is reconstructed by multiplication. That is far more stable than repeated addition.

Teacher note: This is the first place in the Quadratic series where floating-point representation — not just comparison — is the source of the DWR. EPSILON has protected comparisons since Upgrade 1. Now the same discipline extends to loop arithmetic.
Design Decision #6

compareTo()

The method returns negative/zero/positive exactly the way Comparable<T> expects: negative if this quadratic’s vertex is lower, zero if the heights match within EPSILON, and positive if this vertex is higher. The ordering criterion is the vertex’s k-value — the y-coordinate of the vertex.

The equality case wisely reuses isEffectivelyZero(), which keeps the entire class internally consistent about what “equal enough” means for doubles.

The compareTo() convention: negative / zero / positive is used all across the Java standard library: String.compareTo(), Integer.compareTo(), and the algorithms behind Collections.sort(). Writing compareTo() here prepares students to implement Comparable<Quadratic> later, which would make Quadratic objects sortable by any Java sorting algorithm without extra code.

Future note: the next professional step would be public class Quadratic implements Comparable<Quadratic>. Then Collections.sort(list) would just work.

Design Decision #7

final Restored + Breadcrumb Fixed

These are the two smallest but most disciplined fixes in the upgrade.

final restoration: three simple characters prevent an entire class of bugs. Any accidental DF = null; or EPSILON = 0; anywhere in the codebase would silently corrupt behavior. final moves that failure to compile time instead of midnight debugging.

// Before (Upgrade 3)
public static boolean SHOULD_SHOW_BREADCRUMBS = false;
public static DecimalFormat DF = decimalFormatManager(3);
public static double EPSILON = 1E-9;

// After (Upgrade 4)
public static final boolean SHOULD_SHOW_BREADCRUMBS = false;
public static final DecimalFormat DF = decimalFormatManager(3);
public static final double EPSILON = 1E-9;

Breadcrumb fix: Upgrade 3 accidentally printed the complex-roots debug line even when breadcrumbs were off. Upgrade 4 correctly gates it.

// Upgrade 3 (bug): always printed, even with breadcrumbs off
System.out.println("...they are complex roots...");

// Upgrade 4 (fixed): properly gated
if(QuadraticDriver.SHOULD_SHOW_BREADCRUMBS)
    System.out.println("...they are complex conjugate roots...");

Verdict: Both are one-line fixes. Both are exactly the kind of thing a code reviewer would catch and a senior developer would expect to see fixed before merging. In an APCS context, they teach that quality code does not just “work” — it behaves predictably under all settings.

Overall Assessment

Upgrade 4 as a Teaching Milestone

Pros
  • InvalidRootsException is the most significant design upgrade in the series — from fatal to graceful
  • Constructor chaining eliminates code duplication correctly
  • Non-monic constructor completes the roots-constructor story
  • toString() is now production-quality output
  • Integer loop is the correct engineering solution to the floating-point problem
  • compareTo() mirrors the Java standard library convention perfectly
  • final and the breadcrumb fix are small changes that demonstrate professional discipline
Cons / Remaining Rough Edges
  • The a,b,c constructor and setA() still use System.exit(0) for invalid a, which is inconsistent with the new exception philosophy
  • compareTo() does not formally implement Comparable<Quadratic> yet, so Collections.sort() still will not work directly
  • toString() uses isEffectivelyZero(a - 1.0), which is slightly fragile if coefficients come from noisy computations
Bottom line: Upgrade 4 is the most professionally disciplined of the four. It does not add mathematical complexity — it adds engineering quality. The seven improvements collectively move the Quadratic class from teaching-context code that works to production-quality code that a professional team would be comfortable maintaining.
The Complete Upgrade Progression

What Each Upgrade Teaches

UpgradeKey TeachingNew Java Concepts
1Store derived values as typed objects; EPSILON for doublesOrderedPair composition, polymorphism, DecimalFormat
2Memory model; aliasing vs copy constructorsfinal, extends Object, super.toString(), memoryAddress ivar
3Design from roots; complex arithmetic; defensive programmingRoots constructor, Vieta’s Formulas, instanceof, cast operator precedence
4Communicate errors, not terminate; constructor chaining; professional outputCustom checked exception, this(...), StringBuilder, Comparable convention
Suggestions for Future Upgrades

Where Upgrade 5 Could Go

  • Formally implement Comparable<Quadratic> so Collections.sort() works
  • Replace System.exit(0) in the a,b,c constructor and setA() with IllegalArgumentException thrown directly (no inner try/catch needed)
  • Add a getB() getter (currently missing!)
  • Add a getC() getter (also missing!)
  • Add intersects(Quadratic other) — find where two parabolas cross
  • Add toLatex() — return a LaTeX-formatted string for embedding in documents
  • Add unit tests using JUnit 5