as so often, the really preferable solution would be to make it impossible to code the wrong thing from the start:
- a sum type (or some wrapper type) `number | DIVISION_BY_ZERO` forces you to explicitly handle the case of having divided by zero
- alternatively, if the division operator only accepted the set `number - 0` as type for the denominator you'd have to explicitly handle it ahead of the division. Probably better as you don't even try to divide by zero, but not sure how many languages can represent `number - 0` as a type.
All Rust's primitive integer types have a corresponding non-zero variant, NonZeroU8, NonZeroI32, NonZeroU64, NonZeroI128 etc. and indeed NonZero<T> is the corresponding type, for any primitive type T if that's useful in your generic code.
Remember that "almost all" of the Reals are unrepresentable using finite sequences of symbols, since the latter are "only" countably infinite. The next logical step is probably the Radicals (i.e. nth roots, or fractional powers).
I know that nested radicals can't always be un-nested, so I don't think larger sets (like the Algebraic numbers) can be reduced to a unique normal form. That makes comparing them for equality harder, since we can't just compare them syntactically. For large sets like the Computable numbers, many of their operations become undecidable. For example, say we represent Computable numbers as functions from N -> Q, where calling such a function with argument x will return a rational approximation with error smaller than 1/x. We can write an addition function for these numbers (which, given some precision argument, calls the two summand functions with ever-smaller arguments until they're within the requested bound), but we can't write an equality function or even a comparison function, since we don't know when to "give up" comparing numbers like 0.000... == 0.000....