package calc import ( "errors" "math" ) // Base represents the current display formatting mode. type Base string const ( BaseDEC Base = "DEC" BaseHEX Base = "HEX" BaseBIN Base = "BIN" BaseOCT Base = "OCT" ) var basesCycle = []Base{BaseDEC, BaseHEX, BaseBIN, BaseOCT} // ErrConversionNotPossible is returned by CycleBase when the current value // has a fractional part and cannot be cleanly displayed in a non-DEC base. var ErrConversionNotPossible = errors.New("conversion not possible (CERR)") // Engine holds the calculator state. All math is decimal (float64). // Bases affect only formatting via FormatForDisplay and the small row. type Engine struct { value float64 base Base pendingOp string // "+", "-", "*", "/", "mod", or "" pendingVal float64 // entry buffer / more state will be added in engine implementation } // NewEngine creates a fresh calculator engine starting in DEC. func NewEngine() *Engine { return &Engine{ value: 0, base: BaseDEC, } } // CurrentBase returns the active display base for the small row. func (e *Engine) CurrentBase() Base { return e.base } // IsInteger reports whether the current value is effectively an integer // (within epsilon to tolerate fp noise from operations like 1/3). func (e *Engine) IsInteger() bool { _, frac := math.Modf(e.value) return math.Abs(frac) < 1e-10 } // CycleBase advances to the next base in the cycle (DEC→HEX→BIN→OCT→DEC). // Returns ErrConversionNotPossible (and does not change base) if the value // has a fractional part. The caller (UI) should trigger a "CERR" flash. func (e *Engine) CycleBase() error { if !e.IsInteger() { return ErrConversionNotPossible } for i, b := range basesCycle { if b == e.base { e.base = basesCycle[(i+1)%len(basesCycle)] return nil } } // fallback e.base = BaseDEC return nil } // FormatForDisplay returns the string to show in the large number area // according to the current base. For MVP only integer values are expected // in non-DEC bases (callers should have checked via CycleBase or IsInteger). func (e *Engine) FormatForDisplay() string { // Placeholder implementation. Real version in phase 2 will handle // proper integer formatting for hex/bin/oct (no 0x prefix, etc.) // and fall back gracefully. if e.base == BaseDEC { // simple for skeleton return "0" } return "0" } // TODO (phase 2): Add EnterDigit, SetOperator, Equals, Mod, ClearEntry, // AllClear, ChangeSign, Backspace, etc. + full FormatForDisplay.