This strikes me as conceptually similar to the problem of printing floating point numbers. A naive algorithm will convert the binary floating point to decimal and print that out, with potentially many trailing digits that are more or less garbage. The more human-friendly algorithms figure out the shortest/simplest decimal that would be rounded to the same binary floating point quantity, and prints that instead.
You and the rest of this thread's participants may be interested to know that the problem was solved a while ago; you want the "Dragon4" algorithm from "How to Print Floating-Point Numbers Accurately" [0]. Indeed, the problem is difficult, and having implemented the algorithm, I can confirm that it has earned its reputation. Numeric algorithms are hard and the only reward is accuracy.
My $.02: I’d prefer it say 3.9999999... than 4 so students learn of the inaccuracy of floating point. If one wants a rounded output, one should round before printing.
3.999... vs 4 is a bad example, because 4 is exactly representable, and if your calculation gives you a result of 3.999... [garbage digits omitted] then it means there was roundoff error along the way and your computed result is something that is strictly not equal to 4.
A better example would be something like 1/5, which for almost every purpose is better printed as 0.2 instead of 0.20000000298023223876953125. The latter is absurdly long and gives the misleading impression of precision far in excess of the ~7 decimal digits that single-precision floats are capable of representing.
The difference between 0.2 and that long string is due to a slightly different cause than a difference between 3.999... and 4: the latter is likely due to information loss during calculations and may be reduced and sometimes avoided entirely with careful ordering of calculations and using the right rounding modes. But 0.2 can never be exactly represented, even as the input to a chain of calculations. The loss of accuracy is an unavoidable first step of trying to put 0.2 into a binary FPU register.
Students should learn about the pitfalls of floating point arithmetic. But preferably in a way that doesn't leave them with the impression that it is a non-deterministic process that always leaves you with trailing garbage that needs to be ignored.
I sort of like the idea of avoiding exactly representable floats (you typed 4 but maybe you get 4-ε) to remind programmers that errors creep into most expressions and compound, and tight error bounds require numerical error analysis.
Students can be taught using the standard library printf() or whatever.
It's not as easy as "just round" though, because you don't know where the digits start to repeat. A smart function would also print 4.1249999999999 as 4.125.
A fancy unicode version could even include the mathematical notation to print 3.333333333333 as 3.3 with a line over the trailing 3. Or 4.2525252525 as 4.25 with the bar over the 25. Might be asking too much of a simple print function though, this might end up being an extension of the halting problem.
I don't think I'm the only one. I was thinking as I wrote the response that such a system might have to include a whole lot more math than it looks at the surface, which is probably why it doesn't exist as a general solution.
But maybe there could be some heuristics that get it right 99% of the time (with a don't use this library for serious number crunching disclaimer).
I've used various HP scientific and graphing calculators that have functions to convert a floating point number to a symbolic representation of a rational number, or a fraction of pi. These functions are approximate, and their tolerance for how large the denominator of the fraction may get is determined by the current setting for how many decimal digits the display of numbers is rounded to. In practice, this works pretty well, and they implemented this capability long before they had a full CAS on any of their calculators.
Maybe. I'm having difficulty thinking of a case where you would want to know that a number was 1/2 pi (as opposed to 1.57...), but you wouldn't want to know about 3/19 pi.
It sounds like what you want to do is print the number with the smallest denominator (from the range of numbers represented by this bit pattern). A quick search turns up a plausible formula for this: https://math.stackexchange.com/questions/2494774/questions-c...
Your floating-point calculator won't give you infinite trailing nines, but it may very well give you something like 3.99999976158 (approximately the largest float below exactly 4) or 3.99999999999999955591 (approximately the largest double below 4).