Re: moving min and max to Scheme?

"Dr. Arne Babenhauserheide" <[email protected]> Sun, 12 Oct 2025 23:48:42 +0200
Newsgroups gmane.lisp.guile.devel
Message-ID <[email protected]>
Hi,

Tomas Volf <[email protected]> writes:

>> I lack the background information about numbers to judge what’s correct
>> here. Can you judge that?
>
> Returning NaN is consistent with Chez, but I am not qualified to say
> whether it is correct.  I just did basically white-box testing of both
> yours and the built-in version and took note of the differences. ^_^
>
>>
>> It seems like we’ll need a full set of tests for min and max before we
>> can replace them.

I just realized that we actually have quite a few tests.

The following is a slower version, but it passes all tests in our
numbers.test. It looks *much* slower, but in my test with one million
numbers, it’s factor 2 slower than min in C -- on the slow path. The
syntax-case part is faster than C.

The tests only check with small numbers of arguments, so only the
syntax-case is actually tested, the rest copied over in the hope to make
no mistake:


(import (ice-9 match))
(define-syntax-rule (define-minmax-expansion f %f compare)
  (begin
    (define %f
      (case-lambda
        (() (error 'wrong-num-args))
        ((x) x)
        ((x y) (cond ((compare x y) (if (inexact? y) (exact->inexact x) x))
                     ((compare y x) (if (inexact? x) (exact->inexact y) y))
                     ((eqv? x y) x) ;; not for -0. 0.
                     ((= x y) (if (zero? x) 0. x)) ;; -0. and 0.
                     (else (nan))))
        ((x . rest)
         (let lp ((m x)
                  (rest rest)
                  (inex? (inexact? x)))
           (match rest
             (() (if (real? m)
                     (if inex? (exact->inexact m) m)
                     (error "Wrong type argument" 'wrong-type-arg)))
             ((x . rest)
              (cond
               ((compare x m) (lp x rest (or inex? (inexact? x))))
               ((compare m x) (lp m rest (or inex? (inexact? x))))
               ((eqv? m x) (lp m rest (or inex? (inexact? x)))) ;; not for -0. 0.
               ((= m x) (lp (if (zero? m) 0. m) rest (or inex? (inexact? x)))) ;; -0. and 0.
               ;; neither > nor < nor = means (nan): nothing more to do
               (else
                (nan)))))))))
    (define-syntax f
      (lambda (stx)
        (syntax-case stx ()
          (f (identifier? #'f) #'%f)
          ((_) #'(error "Wrong number of arguments to" 'wrong-num-args))
          ((_ x) #'(if (real? x) x (error "Wrong type argument" 'wrong-type-arg)))
          ((_ x y)
           #'(let ((x* x)
                   (y* y)
                   (inex? (or (inexact? x) (inexact? y))))
               (cond ((compare x* y*) (if inex? (exact->inexact x*) x*))
                     ((compare y* x*) (if inex? (exact->inexact y*) y*))
                     ((eqv? x* y*) x*) ;; not for -0. 0.
                     ((= x* y*) (if (zero? x*) 0. x*)) ;; -0. and 0.
                     (else (nan)))))
          ((_ x . y)
           #'(f x (f . y))))))))

(define-minmax-expansion max %max >)
(define-minmax-expansion min %min <)

> I believe this simplifies it a bit too much.  The 2019 version (well,
> the draft, I am not buying the final document, and consider the very
> need to *buy* a standard to be absurd) specifies both `minimum' and
> `minimumNumber' with different behavior regarding NaNs (section 9.6).  I
> do not know whether r7rs prescribes which of those shall be used, but
> probably not.

I don’t think it does.

>> But from r7rs:
>>
>>     An arithmetic operation where one operand is NaN
>>     returns NaN, unless the implementation can prove that the
>>     result would be the same if the NaN were replaced by any
>>     rational number.
>>
>> Are min and max arithmetic operations?
>
> ¯\_(ツ)_/¯

:-)

I think this means: don’t change behavior.

>> Do I need to import something?
>
> Ah, my bad, it is an extension[0] from my library, it helps to make
> large numbers more readable.  It is sourced automatically on REPL start
> and I did not realize I have used it.  You can just drop all of the _,
> including the #_ prefix, and it will work.  Sorry about that.
>
> 0: https://git.wolfsden.cz/guile-wolfsden/tree/wolfsden/x/underscore.scm

No worries, that looks pretty useful.

> In all the above I see large variances in GC time, so I wonder whether
> that had some effect on the result.  In my tests I have always executed
> ,gc first, so you can see the GC is always 0 (min/max should not really
> need to trigger GC anyway).  I am unsure how large the effect of that is
> though, possibly not large.

In the benchmarks, gc made a difference for the very large lists. So it
may have some optimization to do.

Best wishes,
Arne
-- 
Unpolitisch sein
heißt politisch sein,
ohne es zu merken.
draketo.de
signature.asc (application/pgp-signature, 1.1 KB)
-----BEGIN PGP SIGNATURE-----

iQJEBAEBCAAuFiEE801qEjXQSQPNItXAE++NRSQDw+sFAmjsIjoQHGFybmVfYmFi
QHdlYi5kZQAKCRAT741FJAPD610JEACcEFqb/XG1ES+m59DZpq+FjixJlOhSyfLP
024ugWD7U5up8znQhYkjhSs6QtPP0aAZV4H1NYd8ArtjC9/YB49yZOEzom6oAaQv
W/0+tfEILjeiTC8fU1K3YsRj7hazkmw+9eSjjQBc7Nqf1qHjy9OVaDC3WkqUJBJM
oPH8rTQ9gNu03bOSv3WUZV2Ch66y5JfT1ElwkvS9eICb2xNtT7oTiBjrQdGwYepd
EI6gRFBfkd8+y0CftjQm9vRl+VbUa4zVGMO/EOwyksPDUo6XdvNKsa0vfKfwhCpH
J1j15VSxTFMKpGF/ljgq2Zp8+N/X30ixNsovSDwM8gjCaCiU4ulMTN//9mzzLIPy
D6Tn293GF/Fnt4hdsW7v3g3C8Z0khRxu/fhswf6NAE4e/Q8TMtquFaJvnV/2u3fP
b+dCQbqtr/KyGnWyroeSD1FUXfj0ym9aIMTuVpFLPFY0/fYAImUitFmEfb8qOhub
BcEDLR5E89K+sCBR/voGA6mPfRViuwJEQvorEQsCnllg76FozU0RsL+Z59r0qNB6
8h2ajS092UAnmaYIpHe45YMRl8DyfA7F5JoFMjvd+rMsmIl5V8R4L4UTlAg0kuIy
oBAPpuBQWvuGp4QwrnKmYwrpFB/WGEojzEfxGa2Qaq5UDn8w+cU41mRcZuyPZfgI
Wv9/icp99YjEBAEBCAAuFiEE3Si95tmHXKvOSosd3M8NswvBBUgFAmjsIjoQHGFy
bmVfYmFiQHdlYi5kZQAKCRDczw2zC8EFSKIzBACaxCGna+vVLnii6TJnwtwQ54xN
BIVF8pefNYIlEif2VRhCLKpEjgkur615FSkYl3BRO4PoMWdlHMdQI9ODWr3ZReGo
RhcVz131P991BoUrD3Jx3JhSPccIhGrGWqjSiOe5+IGDQRhDxvl4hZamgyIaycw9
dAeuBt4YNYxHfD3EWw==
=9Au3
-----END PGP SIGNATURE-----