gralculator/internal/calc/calc.go

88 lines
2.4 KiB
Go
Raw Normal View History

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.