Re: Inconsistencies and special treatment of %e vs. %pi (and numer, %enumer mess)

David Scherfgen via Maxima-discuss <[email protected]> Wed, 10 Jun 2026 00:01:31 +0200
Newsgroups gmane.comp.mathematics.maxima.general
Message-ID <CAMTHLKgXFG22Zu70RcA-k5Su3hDVn1AuQogte+S0cNJGG8mzVA@mail.gmail.com>
Do you suggest that all the replacements of constants with their numeric
value should be done in the simplifier rather than in the evaluator?
Right now they're all handled by the evaluator, except for %e, which is
handled by the simplifier.
I don't really have an opinion on this, I've never really dug into the
evaluator. But it should be consistent. Having some handled by the one and
some by the other is just asking for trouble.

Am Di., 9. Juni 2026 um 22:15 Uhr schrieb Stavros Macrakis <
[email protected]>:

> Re *%pi^x*, I don't think that is ever really used in math, and so no one
> cares.
>
> Understood about idea vs. implementation, but I don't much like the idea
> of having mathematical transformations performed in evaluation. I've been
> trying for years to wean people off of the overuse of *ev*, and this
> gives a new reason to use *ev*. As far as I'm concerned, it should be
> possible to perform *all* mathematical operations without involving
> evaluation at all. We're not there yet -- for example, *nouns* today is
> bound up with evaluation (*ev(...,nouns,noeval)* doesn't replace noun
> forms with verb forms) -- but I'd like to move in that direction.
>
> On Tue, Jun 9, 2026 at 3:53 PM David Scherfgen <[email protected]>
> wrote:
>
>> Interesting observation regarding *bfloat* vs. *float*.
>>
>> As I said, I agree with the idea of leaving *%e^x* alone even if
>> *numer=true* unless *x* is numeric.
>>
>> (By the way, wouldn't that also apply to *%pi^x*? Is it ever desirable
>> to rewrite that as *3.141^x* unless *x* is purely numeric?)
>>
>> My main point is that while the *idea* is good, the current
>> implementation is incorrect (see examples I posted) and relies on lots of
>> intertwined special-case code. What I find the most "disturbing" is that
>> the *simplifier* turns *%e* into *2.718*, making it the only atom that
>> the simplifier can modify. This is bad. One would think that an atom is
>> inherently simplified, and it cannot have a *simp* tag.
>>
>> What if we rip out all the special treatment of *%e* in the *simplifier*
>> and leave it to the *evaluator* to replace it with its numeric value?
>> Then we can try to make *%enumer* work in the evaluator, i.e. prevent
>> *%e^x* with symbolic *x* from becoming *2.718^x*.
>>
>>
>> Stavros Macrakis <[email protected]> schrieb am Di., 9. Juni 2026,
>> 20:57:
>>
>>> My guess is that the purpose of special-casing *%e^x* is that people
>>> think of it as a notation for the function *exp(x)*. It is ever
>>> desirable to rewrite *%e^x* as *2.718^x *unless *x* is purely numeric?
>>> There is nothing analogous for *%pi *or other constants.
>>>
>>> By the way, I see that *bfloat(...)* preserves symbolic *%e *when it is
>>> the base of an exponentiation, because it does not work via simplification.
>>> *float(...)*, on the other hand, does not preserve it.
>>>
>>> I'll also note that *xxx,float == ex(xxx,float)* is equivalent to
>>> binding *float:true*, whereas *xxx,bfloat == ex(xxx,bfloat)* is
>>> equivalent to *bfloat(xxx)*... which in most cases results in similar
>>> results. But I wouldn't dare to make them consistent because I don't know
>>> how much user code it would break.
>>>
>>> On Tue, Jun 9, 2026 at 1:10 PM David Scherfgen via Maxima-discuss <
>>> [email protected]> wrote:
>>>
>>>> Dear all,
>>>>
>>>> First, please excuse the rather long mail. If it's too long for you to
>>>> read it completely, please read only the example codes that show the
>>>> problem, and then skip to "TLDR / Way forward?".
>>>>
>>>> While digging into the simplifier and evaluator, I noticed that %e
>>>> receives an almost ridiculous amount of special treatment compared to,
>>>> e.g., %pi.
>>>>
>>>>    - %e is the *only atom* that can be simplified to something
>>>>    different by the simplifier: If the global flags numer and %enumer
>>>>    are both true, %e is simplified to its numerical value. It's likely
>>>>    that there is code at other places that (wrongly, but understandably)
>>>>    assumes that atoms are inherently simplified and don't need to go through
>>>>    the simplifier, which would cause bugs. The other constants like %pi
>>>>    aren't handled by the simplifier.
>>>>    - There is some really ugly hackery going on in the evaluator that
>>>>    explicitly targets %e in conjunction with numer/%enumer.
>>>>
>>>> The global flag %enumer = false (default) is supposed to "protect" %e
>>>> from becoming a float when numer is true in cases of %e^x, where x is
>>>> symbolic. At first glance, this works:
>>>>
>>>> (%i1) %e^x + sin(%e), numer;
>>>> (%o1) %e^x + 0.41078129050290885
>>>>
>>>> Here, %e^x is correctly left alone, while sin(%e) becomes a float.
>>>> The following also works:
>>>>
>>>> (%i2) sin(%e^x), numer;
>>>> (%o2) sin(%e^x)
>>>>
>>>> But now let's resimplify that with numer on (classically, this would
>>>> be done with expand(%, 0, 0), but we have resimplify now):
>>>>
>>>> (%i3) resimplify(%), numer;
>>>> (%o3) sin(2.718281828459045^x)
>>>>
>>>> Oops! Now %e became a float, even though it's the base with a symbolic
>>>> exponent, which is exactly the case where it should have been "protected"
>>>> by %enumer = false.
>>>>
>>>> What's going on here?
>>>>
>>>>    - When typing just sin(%e^x), numer in the REPL, the evaluator
>>>>    starts by simplifying the leaves %e and x, then builds and
>>>>    simplifies %e^x, and finally builds and simplifies sin(%e^x). It
>>>>    works its way from the leaves to the root, *bottom-up*.
>>>>    - But when we resimplify, all simp tags are stripped from the
>>>>    entire expression, and the simplifier is called on the root, working in a
>>>>    *top-down* fashion. The sin simplifier calls simpcheck on its
>>>>    argument, which, due to a momentous commit described here
>>>>    <https://sourceforge.net/p/maxima/bugs/123/>, dynamically binds
>>>>    %enumer to numer (true). This "infects" the entire sub-tree, and
>>>>    all instances of %e inside it are converted to a float by the
>>>>    simplifier, no matter what.
>>>>
>>>> So we get a different outcome depending on whether we work bottom-up or
>>>> top-down. It doesn't end there, though. Here's an example that shows how
>>>> %e and %pi are treated in a fundamentally different way:
>>>>
>>>> /* OK, both become floats: */
>>>> (%i4) %pi*y + %e*x, numer;
>>>> (%o4) 3.141592653589793*y + 2.718281828459045*x
>>>>
>>>> /* Not OK, %e stays symbolic: */
>>>> (%i5) [%pi, %e], numer;
>>>> (%o5) [3.141592653589793, %e]
>>>>
>>>> /* If we write -%e instead of %e, it becomes a float: */
>>>> (%i6) [%pi, -%e], numer;
>>>> (%o6) [3.141592653589793, -2.718281828459045]
>>>>
>>>> *TLDR / Way forward?*
>>>>
>>>>    - In summary, the handling of %e, numer and %enumer is an
>>>>    inconsistent mess that needs lots of special cases in the code, and it
>>>>    doesn't even work as intended.
>>>>    - I think that, as much as possible, this extra treatment of %e
>>>>    should go away. It would make the code simpler and the results more
>>>>    consistent.
>>>>    - The idea behind %enumer = false isn't bad, but there's a
>>>>    fundamental problem with the way it's supposed to work: When working
>>>>    bottom-up, we first see %e, but *we don't have the context*, i.e.
>>>>    we don't know where that %e sits inside a parent expression (if
>>>>    any).
>>>>       - Is it the %e in %e^x? Then it must not become a float.
>>>>       - Is it any %e in %e^%e? Then it must become a float.
>>>>       - Is it the %e in sin(%e)? Then it must become a float.
>>>>       - Is it just a standalone %e? Then it must become a float.
>>>>    - There's one way it could work, although it comes with a catch:
>>>>       - We stop handling %e in simplifya entirely. *Nice side effect:
>>>>       Atoms are always simplified!*
>>>>       - Instead, we treat %e like any other constant, e.g. %pi, and
>>>>       let the evaluator convert it to float unconditionally when numer
>>>>       is true.
>>>>       - Afterwards, if %enumer is false, we look explicitly for
>>>>       2.718281828459045^(symbolic) and convert that back to
>>>>       %e^(symbolic). Note that this is an exact comparison to %e-val
>>>>       that will not be affected by floating point issues.
>>>>       - The catch: If the user manually enters 2.718281828459045^x, it
>>>>       will become %e^x. Maybe that's acceptable? I don't see any other
>>>>       way of achieving the desired behavior.
>>>>    - Alternatively, we could degrade %enumer to a selective switch
>>>>    just for %e. With %enumer = false, it would always prevent %e from
>>>>    becoming a float when numer is true. But that would include things
>>>>    like sin(%e), where the user would probably expect a float. This
>>>>    behavior would probably not be very useful, and I don't like the idea of
>>>>    changing the meaning of a flag in a significant way, as it would destroy
>>>>    backwards-compatibility.
>>>>
>>>> Looking forward to the community's opinions on this.
>>>>
>>>> Best regards
>>>> David Scherfgen
>>>> _______________________________________________
>>>> Maxima-discuss mailing list
>>>> [email protected]
>>>> https://lists.sourceforge.net/lists/listinfo/maxima-discuss
>>>>
>>>

_______________________________________________
Maxima-discuss mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/maxima-discuss