As reported on bugs@ our ilogb(3) implementation is somewhat buggy.

There are several issues:

- The amd64 and i386 assembler versions not only return results that
  don't match the FP_ILOGB0 and FP_ILOGBNAN defines in <math.h> but
  ultimately return results that are incompatible with C99 and C11.

- The C versions don't use the FP_ILOGB0 and FP_ILOGBNAN defines.

- The ilogbl(3) implementation turns into an infinite loop with
  182-bit long doubles in some cases.

Here is a diff that fixes those issues by:

- Dropping the amd64 and i386 versions.  Fixing the corner cases in
  assembly is hard, and the C implementation should be fast enough for
  regular floating-point values.

- Replaces the broken C implementation of ilogbl(3) with separate
  128-bit and 80-bit long double implementations.

- Add the regress test from FreeBSD.

Technically, this should be a libm major bump since the behaviour
changes on amd64 and i386.  But given that the corner cases were so
blatantly wrong on those platforms, I don't think that's necessary.

ok?


Index: lib/libm/Makefile
===================================================================
RCS file: /cvs/src/lib/libm/Makefile,v
retrieving revision 1.120
diff -u -p -r1.120 Makefile
--- lib/libm/Makefile   28 Jun 2020 08:22:57 -0000      1.120
+++ lib/libm/Makefile   31 Oct 2020 14:52:14 -0000
@@ -23,7 +23,7 @@ ARCH_SRCS = e_acos.S e_asin.S e_atan2.S 
            invtrig.c \
            s_atan.S s_atanf.S s_ceil.S s_ceilf.S s_copysign.S s_copysignf.S \
            s_cos.S s_cosf.S s_floor.S s_floorf.S \
-           s_ilogb.S s_ilogbf.S s_log1p.S s_log1pf.S s_logb.S s_logbf.S \
+           s_log1p.S s_log1pf.S s_logb.S s_logbf.S \
            s_llrint.S s_llrintf.S s_lrint.S s_lrintf.S s_rint.S s_rintf.S\
            s_scalbnf.S s_significand.S s_significandf.S \
            s_sin.S s_sinf.S s_tan.S s_tanf.S
@@ -36,7 +36,7 @@ ARCH_SRCS = e_acos.S e_asin.S e_atan2.S 
            invtrig.c \
            s_atan.S s_atanf.S s_ceil.S s_ceilf.S s_copysign.S s_copysignf.S \
            s_cos.S s_cosf.S s_floor.S s_floorf.S \
-           s_ilogb.S s_ilogbf.S s_log1p.S s_log1pf.S s_logb.S s_logbf.S \
+           s_log1p.S s_log1pf.S s_logb.S s_logbf.S \
            s_llrint.S s_llrintf.S s_lrint.S s_lrintf.S \
            s_rint.S s_rintf.S s_scalbnf.S s_significand.S \
            s_significandf.S s_sin.S s_sinf.S s_tan.S s_tanf.S
Index: lib/libm/arch/amd64/s_ilogb.S
===================================================================
RCS file: lib/libm/arch/amd64/s_ilogb.S
diff -N lib/libm/arch/amd64/s_ilogb.S
--- lib/libm/arch/amd64/s_ilogb.S       3 Jul 2018 22:43:34 -0000       1.5
+++ /dev/null   1 Jan 1970 00:00:00 -0000
@@ -1,20 +0,0 @@
-/*     $OpenBSD: s_ilogb.S,v 1.5 2018/07/03 22:43:34 mortimer Exp $ */
-/*
- * Written by J.T. Conklin <[email protected]>.
- * Public domain.
- */
-
-#include <machine/asm.h>
-#include "abi.h"
-
-ENTRY(ilogb)
-       RETGUARD_SETUP(ilogb, r11)
-       movsd   %xmm0,-8(%rsp)
-       fldl    -8(%rsp)
-       fxtract
-       fstp    %st
-       fistpl  -8(%rsp)
-       movl    -8(%rsp),%eax
-       RETGUARD_CHECK(ilogb, r11)
-       ret
-END_STD(ilogb)
Index: lib/libm/arch/amd64/s_ilogbf.S
===================================================================
RCS file: lib/libm/arch/amd64/s_ilogbf.S
diff -N lib/libm/arch/amd64/s_ilogbf.S
--- lib/libm/arch/amd64/s_ilogbf.S      3 Jul 2018 22:43:34 -0000       1.5
+++ /dev/null   1 Jan 1970 00:00:00 -0000
@@ -1,20 +0,0 @@
-/*     $OpenBSD: s_ilogbf.S,v 1.5 2018/07/03 22:43:34 mortimer Exp $ */
-/*
- * Written by J.T. Conklin <[email protected]>.
- * Public domain.
- */
-
-#include <machine/asm.h>
-#include "abi.h"
-
-ENTRY(ilogbf)
-       RETGUARD_SETUP(ilogbf, r11)
-       movss   %xmm0,-4(%rsp)
-       flds    -4(%rsp)
-       fxtract
-       fstp    %st
-       fistpl  -4(%rsp)
-       movl    -4(%rsp),%eax
-       RETGUARD_CHECK(ilogbf, r11)
-       ret
-END_STD(ilogbf)
Index: lib/libm/arch/i387/s_ilogb.S
===================================================================
RCS file: lib/libm/arch/i387/s_ilogb.S
diff -N lib/libm/arch/i387/s_ilogb.S
--- lib/libm/arch/i387/s_ilogb.S        12 Sep 2016 19:47:02 -0000      1.6
+++ /dev/null   1 Jan 1970 00:00:00 -0000
@@ -1,23 +0,0 @@
-/*     $OpenBSD: s_ilogb.S,v 1.6 2016/09/12 19:47:02 guenther Exp $ */
-/*
- * Written by J.T. Conklin <[email protected]>.
- * Public domain.
- */
-
-#include "DEFS.h"
-
-ENTRY(ilogb)
-       pushl   %ebp
-       movl    %esp,%ebp
-       subl    $4,%esp
-
-       fldl    8(%ebp)
-       fxtract
-       fstp    %st
-
-       fistpl  -4(%ebp)
-       movl    -4(%ebp),%eax
-
-       leave
-       ret
-END_STD(ilogb)
Index: lib/libm/arch/i387/s_ilogbf.S
===================================================================
RCS file: lib/libm/arch/i387/s_ilogbf.S
diff -N lib/libm/arch/i387/s_ilogbf.S
--- lib/libm/arch/i387/s_ilogbf.S       12 Sep 2016 19:47:02 -0000      1.6
+++ /dev/null   1 Jan 1970 00:00:00 -0000
@@ -1,23 +0,0 @@
-/*     $OpenBSD: s_ilogbf.S,v 1.6 2016/09/12 19:47:02 guenther Exp $ */
-/*
- * Written by J.T. Conklin <[email protected]>.
- * Public domain.
- */
-
-#include "DEFS.h"
-
-ENTRY(ilogbf)
-       pushl   %ebp
-       movl    %esp,%ebp
-       subl    $4,%esp
-
-       flds    8(%ebp)
-       fxtract
-       fstp    %st
-
-       fistpl  -4(%ebp)
-       movl    -4(%ebp),%eax
-
-       leave
-       ret
-END_STD(ilogbf)
Index: lib/libm/src/s_ilogb.c
===================================================================
RCS file: /cvs/src/lib/libm/src/s_ilogb.c,v
retrieving revision 1.11
diff -u -p -r1.11 s_ilogb.c
--- lib/libm/src/s_ilogb.c      12 Sep 2016 19:47:02 -0000      1.11
+++ lib/libm/src/s_ilogb.c      31 Oct 2020 14:52:14 -0000
@@ -12,8 +12,9 @@
 
 /* ilogb(double x)
  * return the binary exponent of non-zero x
- * ilogb(0) = 0x80000001
- * ilogb(inf/NaN) = 0x7fffffff (no signal is raised)
+ * ilogb(0) = FP_ILOGB0
+ * ilogb(NaN) = FP_ILOGBNAN (no signal is raised)
+ * ilogb(inf) = INT_MAX (no signal is raised)
  */
 
 #include <float.h>
@@ -31,7 +32,7 @@ ilogb(double x)
        if(hx<0x00100000) {
            GET_LOW_WORD(lx,x);
            if((hx|lx)==0) 
-               return 0x80000001;      /* ilogb(0) = 0x80000001 */
+               return FP_ILOGB0;       /* ilogb(0) = FP_ILOGB0 */
            else                        /* subnormal x */
                if(hx==0) {
                    for (ix = -1043; lx>0; lx<<=1) ix -=1;
@@ -41,7 +42,8 @@ ilogb(double x)
            return ix;
        }
        else if (hx<0x7ff00000) return (hx>>20)-1023;
-       else return 0x7fffffff;
+       else if (hx>0x7ff00000 || lx!=0) return FP_ILOGBNAN;
+       else return INT_MAX;
 }
 DEF_STD(ilogb);
 LDBL_MAYBE_CLONE(ilogb);
Index: lib/libm/src/s_ilogbf.c
===================================================================
RCS file: /cvs/src/lib/libm/src/s_ilogbf.c,v
retrieving revision 1.4
diff -u -p -r1.4 s_ilogbf.c
--- lib/libm/src/s_ilogbf.c     12 Sep 2016 19:47:02 -0000      1.4
+++ lib/libm/src/s_ilogbf.c     31 Oct 2020 14:52:14 -0000
@@ -25,12 +25,13 @@ ilogbf(float x)
        hx &= 0x7fffffff;
        if(hx<0x00800000) {
            if(hx==0) 
-               return 0x80000001;      /* ilogb(0) = 0x80000001 */
+               return FP_ILOGB0;       /* ilogb(0) = FP_ILOGB */
            else                        /* subnormal x */
                for (ix = -126,hx<<=8; hx>0; hx<<=1) ix -=1;
            return ix;
        }
        else if (hx<0x7f800000) return (hx>>23)-127;
-       else return 0x7fffffff;
+       else if (hx>0x7f800000) return FP_ILOGBNAN;
+       else return INT_MAX;
 }
 DEF_STD(ilogbf);
Index: lib/libm/src/s_ilogbl.c
===================================================================
RCS file: lib/libm/src/s_ilogbl.c
diff -N lib/libm/src/s_ilogbl.c
--- lib/libm/src/s_ilogbl.c     12 Sep 2016 19:47:02 -0000      1.2
+++ /dev/null   1 Jan 1970 00:00:00 -0000
@@ -1,79 +0,0 @@
-/*     $OpenBSD: s_ilogbl.c,v 1.2 2016/09/12 19:47:02 guenther Exp $   */
-/*
- * From: @(#)s_ilogb.c 5.1 93/09/24
- * ====================================================
- * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
- *
- * Developed at SunPro, a Sun Microsystems, Inc. business.
- * Permission to use, copy, modify, and distribute this
- * software is freely granted, provided that this notice
- * is preserved.
- * ====================================================
- */
-
-#include <sys/types.h>
-#include <machine/ieee.h>
-#include <float.h>
-#include <limits.h>
-#include <math.h>
-
-int
-ilogbl(long double x)
-{
-       struct ieee_ext *p = (struct ieee_ext *)&x;
-       unsigned long m;
-       int b;
-
-       if (p->ext_exp == 0) {
-               if ((p->ext_fracl
-#ifdef EXT_FRACLMBITS
-                       | p->ext_fraclm
-#endif /* EXT_FRACLMBITS */
-#ifdef EXT_FRACHMBITS
-                       | p->ext_frachm
-#endif /* EXT_FRACHMBITS */
-                       | p->ext_frach) == 0)
-                       return (FP_ILOGB0);
-               /* denormalized */
-               if (p->ext_frach == 0
-#ifdef EXT_FRACHMBITS
-                       && p->ext_frachm == 0
-#endif
-                       ) {
-                       m = 1lu << (EXT_FRACLBITS - 1);
-                       for (b = EXT_FRACHBITS; !(p->ext_fracl & m); m >>= 1)
-                               b++;
-#if defined(EXT_FRACHMBITS) && defined(EXT_FRACLMBITS)
-                       m = 1lu << (EXT_FRACLMBITS - 1);
-                       for (b += EXT_FRACHMBITS; !(p->ext_fraclm & m); m >>= 1)
-                               b++;
-#endif /* defined(EXT_FRACHMBITS) && defined(EXT_FRACLMBITS) */
-               } else {
-                       m = 1lu << (EXT_FRACHBITS - 1);
-                       for (b = 0; !(p->ext_frach & m); m >>= 1)
-                               b++;
-#ifdef EXT_FRACHMBITS
-                       m = 1lu << (EXT_FRACHMBITS - 1);
-                       for (; !(p->ext_frachm & m); m >>= 1)
-                               b++;
-#endif /* EXT_FRACHMBITS */
-               }
-#ifdef EXT_IMPLICIT_NBIT
-               b++;
-#endif
-               return (LDBL_MIN_EXP - b - 1);
-       } else if (p->ext_exp < (LDBL_MAX_EXP << 1) - 1)
-               return (p->ext_exp - LDBL_MAX_EXP + 1);
-       else if (p->ext_fracl != 0
-#ifdef EXT_FRACLMBITS
-               || p->ext_fraclm != 0
-#endif /* EXT_FRACLMBITS */
-#ifdef EXT_FRACHMBITS
-               || p->ext_frachm != 0
-#endif /* EXT_FRACHMBITS */
-               || p->ext_frach != 0)
-               return (FP_ILOGBNAN);
-       else
-               return (INT_MAX);
-}
-DEF_STD(ilogbl);
Index: lib/libm/src/ld128/s_ilogbl.c
===================================================================
RCS file: lib/libm/src/ld128/s_ilogbl.c
diff -N lib/libm/src/ld128/s_ilogbl.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/libm/src/ld128/s_ilogbl.c       31 Oct 2020 14:52:14 -0000
@@ -0,0 +1,51 @@
+/* s_ilogbl.c -- long double version of s_ilogb.c.
+ * Conversion to 128-bit long double by Mark Kettenis, [email protected].
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+/* ilogbl(long double x)
+ * return the binary exponent of non-zero x
+ * ilogb(0) = FP_ILOGB0
+ * ilogb(NaN) = FP_ILOGBNAN (no signal is raised)
+ * ilogb(inf) = INT_MAX (no signal is raised)
+ */
+
+#include <float.h>
+#include <math.h>
+
+#include "math_private.h"
+
+int
+ilogbl(long double x)
+{
+       int64_t hx,lx;
+       int32_t ix;
+
+       GET_LDOUBLE_WORDS64(hx,lx,x);
+       hx &= 0x7fffffffffffffffLL;
+       if(hx<0x0001000000000000LL) {
+           if((hx|lx)==0) 
+               return FP_ILOGB0;       /* ilogb(0) = FP_ILOGB0 */
+           else                        /* subnormal x */
+               if(hx==0) {
+                   for (ix = -16431; lx>0; lx<<=1) ix -=1;
+               } else {
+                   for (ix = -16382, hx<<=15; hx>0; hx<<=1) ix -=1;
+               }
+           return ix;
+       }
+       else if (hx<0x7fff000000000000LL) return (hx>>48)-16383;
+       else if (hx>0x7fff000000000000LL || lx!=0) return FP_ILOGBNAN;
+       else return INT_MAX;
+}
+DEF_STD(ilogbl);
Index: lib/libm/src/ld80/s_ilogbl.c
===================================================================
RCS file: lib/libm/src/ld80/s_ilogbl.c
diff -N lib/libm/src/ld80/s_ilogbl.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ lib/libm/src/ld80/s_ilogbl.c        31 Oct 2020 14:52:14 -0000
@@ -0,0 +1,50 @@
+/* s_ilogbl.c -- long double version of s_ilogb.c.
+ * Conversion to 80-bit long double by Mark Kettenis, [email protected].
+ */
+
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice 
+ * is preserved.
+ * ====================================================
+ */
+
+/* ilogbl(long double x)
+ * return the binary exponent of non-zero x
+ * ilogb(0) = FP_ILOGB0
+ * ilogb(NaN) = FP_ILOGBNAN (no signal is raised)
+ * ilogb(inf) = INT_MAX (no signal is raised)
+ */
+
+#include <float.h>
+#include <math.h>
+
+#include "math_private.h"
+
+int
+ilogbl(long double x)
+{
+       int32_t esx,hx,lx,ix;
+
+       GET_LDOUBLE_WORDS(esx,hx,lx,x);
+       esx &= 0x7fff;
+       if(esx==0) {
+           if((hx|lx)==0) 
+               return FP_ILOGB0;       /* ilogb(0) = FP_ILOGB0 */
+           else                        /* subnormal x */
+               if(hx==0) {
+                   for (ix = -16414; lx>0; lx<<=1) ix -=1;
+               } else {
+                   for (ix = -16382; hx>0; hx<<=1) ix -=1;
+               }
+           return ix;
+       }
+       else if (esx<0x7fff) return (esx)-16383;
+       else if ((hx&0x7fffffff|lx)!=0) return FP_ILOGBNAN;
+       else return INT_MAX;
+}
+DEF_STD(ilogbl);
Index: regress/lib/libm/msun/Makefile
===================================================================
RCS file: /cvs/src/regress/lib/libm/msun/Makefile,v
retrieving revision 1.1.1.1
diff -u -p -r1.1.1.1 Makefile
--- regress/lib/libm/msun/Makefile      21 Feb 2019 16:14:03 -0000      1.1.1.1
+++ regress/lib/libm/msun/Makefile      31 Oct 2020 14:52:14 -0000
@@ -3,6 +3,7 @@
 TESTS =
 TESTS +=       conj_test
 TESTS +=       fenv_test
+TESTS +=       ilogb_test
 TESTS +=       lrint_test
 
 PROGS= ${TESTS}
Index: regress/lib/libm/msun/ilogb_test.c
===================================================================
RCS file: regress/lib/libm/msun/ilogb_test.c
diff -N regress/lib/libm/msun/ilogb_test.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ regress/lib/libm/msun/ilogb_test.c  31 Oct 2020 14:52:14 -0000
@@ -0,0 +1,83 @@
+/*-
+ * Copyright (c) 2004 Stefan Farfeleder
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: head/lib/msun/tests/ilogb_test.c 292328 2015-12-16 09:11:11Z ngie 
$
+ */
+
+#include <assert.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(void)
+{
+       char buf[128], *end;
+       double d;
+       float f;
+       long double ld;
+       int e, i;
+
+       printf("1..3\n");
+       assert(ilogb(0) == FP_ILOGB0);
+       assert(ilogb(NAN) == FP_ILOGBNAN);
+       assert(ilogb(INFINITY) == INT_MAX);
+       for (e = DBL_MIN_EXP - DBL_MANT_DIG; e < DBL_MAX_EXP; e++) {
+               snprintf(buf, sizeof(buf), "0x1.p%d", e);
+               d = strtod(buf, &end);
+               assert(*end == '\0');
+               i = ilogb(d);
+               assert(i == e);
+       }
+       printf("ok 1 - ilogb\n");
+
+       assert(ilogbf(0) == FP_ILOGB0);
+       assert(ilogbf(NAN) == FP_ILOGBNAN);
+       assert(ilogbf(INFINITY) == INT_MAX);
+       for (e = FLT_MIN_EXP - FLT_MANT_DIG; e < FLT_MAX_EXP; e++) {
+               snprintf(buf, sizeof(buf), "0x1.p%d", e);
+               f = strtof(buf, &end);
+               assert(*end == '\0');
+               i = ilogbf(f);
+               assert(i == e);
+       }
+       printf("ok 2 - ilogbf\n");
+
+       assert(ilogbl(0) == FP_ILOGB0);
+       assert(ilogbl(NAN) == FP_ILOGBNAN);
+       assert(ilogbl(INFINITY) == INT_MAX);
+       for (e = LDBL_MIN_EXP - LDBL_MANT_DIG; e < LDBL_MAX_EXP; e++) {
+               snprintf(buf, sizeof(buf), "0x1.p%d", e);
+               ld = strtold(buf, &end);
+               assert(*end == '\0');
+               i = ilogbl(ld);
+               assert(i == e);
+       }
+       printf("ok 3 - ilogbl\n");
+
+       return (0);
+}

Reply via email to