narrowing to support 32-bit floats
diff --git a/libasn1fix/asn1fix_constraint_compat.c b/libasn1fix/asn1fix_constraint_compat.c
index c2c9595..5f0ab22 100644
--- a/libasn1fix/asn1fix_constraint_compat.c
+++ b/libasn1fix/asn1fix_constraint_compat.c
@@ -9,9 +9,6 @@
 asn1constraint_compatible(asn1p_expr_type_e expr_type,
 	enum asn1p_constraint_type_e constr_type, int fbless_SIZE) {
 
-	if(expr_type == ASN_BASIC_REAL)
-		return -1;	/* Not yet supported */
-
 	/*
 	 * X.680-0207, Table 9.
 	 */
@@ -115,7 +112,7 @@
 
 #define DECL_RANGE(foo, lb, ub, ov, pv)     \
     static asn1cnst_range_t range_##foo = { \
-        {ARE_VALUE, 0, lb}, {ARE_VALUE, 0, ub}, 0, 0, 0, 0, 0, 0, ov, pv}
+        {ARE_VALUE, 0, lb}, {ARE_VALUE, 0, ub}, 0, 0, 0, 0, 0, 0, 0, ov, pv}
 
 #define DECL(name, lb, ub) DECL_RANGE(name, lb, ub, 0, 0)
 #define DECL_notOPV(name, lb, ub) DECL_RANGE(name, lb, ub, 1, 1)
@@ -158,10 +155,11 @@
 	static asn1cnst_range_t range_notPERVisible = {
 			{ ARE_MIN, 0, 0 },
 			{ ARE_MAX, 0, 0 },
-			0, 0, 0, 0, 0, 0, 0, 1 };
+			0, 0, 0, 0, 0, 0, 0, 0, 1 };
 	static asn1cnst_range_t range_NumericString = {
 			{ ARE_VALUE, 0, 0x20 },
 			{ ARE_VALUE, 0, 0x39 },
+			NOT_NARROW,
 			range_NumericString_array,
 			sizeof(range_NumericString_array)
 				/sizeof(range_NumericString_array[0]),
@@ -169,6 +167,7 @@
 	static asn1cnst_range_t range_PrintableString = {
 			{ ARE_VALUE, 0, 0x20 },
 			{ ARE_VALUE, 0, 0x7a },
+			NOT_NARROW,
 			range_PrintableString_array,
 			sizeof(range_PrintableString_array)
 				/sizeof(range_PrintableString_array[0]),
@@ -176,10 +175,11 @@
 	static asn1cnst_range_t range_VisibleString = {
 			{ ARE_VALUE, 0, 0x20 },
 			{ ARE_VALUE, 0, 0x7e },
-			0, 0, 0, 0, 0, 0, 0, 0 };
+			NOT_NARROW, 0, 0, 0, 0, 0, 0, 0, 0 };
 	static asn1cnst_range_t range_UTCTime = {
 			{ ARE_VALUE, 0, 0x2b },
 			{ ARE_VALUE, 0, 0x5a },
+			NOT_NARROW,
 			range_UTCTime_array,
 			sizeof(range_UTCTime_array)
 				/sizeof(range_UTCTime_array[0]),
@@ -187,6 +187,7 @@
 	static asn1cnst_range_t range_GeneralizedTime = {
 			{ ARE_VALUE, 0, 0x2b },
 			{ ARE_VALUE, 0, 0x5a },
+			NOT_NARROW,
 			range_GeneralizedTime_array,
 			sizeof(range_GeneralizedTime_array)
 				/sizeof(range_GeneralizedTime_array[0]),
diff --git a/libasn1fix/asn1fix_crange.c b/libasn1fix/asn1fix_crange.c
index b4685ff..29d5158 100644
--- a/libasn1fix/asn1fix_crange.c
+++ b/libasn1fix/asn1fix_crange.c
@@ -9,8 +9,8 @@
 		fprintf(stderr, "\n");			\
 	} while(0)
 
-void
-asn1constraint_range_free(asn1cnst_range_t *cr) {
+static void
+asn1constraint_range_free_outer(asn1cnst_range_t *cr) {
 	if(cr) {
 		int i;
 		if(cr->elements) {
@@ -18,9 +18,16 @@
 				asn1constraint_range_free(cr->elements[i]);
 			free(cr->elements);
 		}
-		free(cr);
-	}
+        memset(cr, 0, sizeof(*cr));
+    }
 }
+
+void
+asn1constraint_range_free(asn1cnst_range_t *cr) {
+    asn1constraint_range_free_outer(cr);
+    free(cr);
+}
+
 #define	_range_free(foo)	asn1constraint_range_free(foo)
 
 static asn1cnst_range_t *_range_new() {
@@ -149,15 +156,15 @@
 	return ret;
 }
 
-static char *
-_edge_value(const asn1cnst_edge_t *edge) {
-	static char buf[128];
+static const char *
+_edge_print(const asn1cnst_edge_t *edge, char *buf, size_t size) {
 	*buf = '\0';
+    assert(size > 4);
 	switch(edge->type) {
 	case ARE_MIN:	strcpy(buf, "MIN"); break;
 	case ARE_MAX:	strcpy(buf, "MAX"); break;
 	case ARE_VALUE:
-		snprintf(buf, sizeof(buf), "%s", asn1p_itoa(edge->value));
+		snprintf(buf, size, "%s", asn1p_itoa(edge->value));
         break;
     default:
         assert(!"edge->type");
@@ -165,6 +172,23 @@
 	return buf;
 }
 
+static const char *
+_edge_string(const asn1cnst_edge_t *edge) {
+	static char buf[128];
+    return _edge_print(edge, buf, sizeof(buf));
+}
+
+static const char *
+_range_string(const asn1cnst_range_t *range) {
+    static char buf[128];
+    char buf0[128];
+    char buf1[128];
+    snprintf(buf, sizeof(buf), "(%s..%s)",
+             _edge_print(&range->left, buf0, sizeof(buf0)),
+             _edge_print(&range->right, buf1, sizeof(buf1)));
+    return buf;
+}
+
 static int
 _edge_is_within(const asn1cnst_range_t *range, const asn1cnst_edge_t *edge) {
 	int i;
@@ -191,9 +215,10 @@
 	if(!_edge_is_within(range, &r->left)) {
 		FATAL("Constraint value %s at line %d "
 			"is not within "
-			"a parent constraint range",
-			_edge_value(&r->left),
-			r->left.lineno
+			"a parent constraint range %s",
+			_edge_string(&r->left),
+			r->left.lineno,
+			_range_string(range)
 		);
 		return -1;
 	}
@@ -201,9 +226,10 @@
 	if(!_edge_is_within(range, &r->right)) {
 		FATAL("Constraint value %s at line %d "
 			"is not within "
-			"a parent constraint range",
-			_edge_value(&r->right),
-			r->right.lineno
+			"a parent constraint range %s",
+			_edge_string(&r->right),
+			r->left.lineno,
+			_range_string(range)
 		);
 		return -1;
 	}
@@ -211,6 +237,53 @@
 	return 0;
 }
 
+/*
+ * Combine narrowings to get the strongest (most specific).
+ */
+static enum asn1cnst_range_narrowing
+_strongest_narrowing(const asn1cnst_range_t *ar, const asn1cnst_range_t *br) {
+    enum asn1cnst_range_narrowing an = ar->narrowing;
+    enum asn1cnst_range_narrowing bn = br->narrowing;
+
+    if(!an) {
+        return bn;
+    } else if(!bn) {
+        return an;
+    } else {
+        return an > bn ? an : bn;
+    }
+}
+
+/*
+ * Combine narrowings to get the softest one.
+ */
+static enum asn1cnst_range_narrowing
+_softest_narrowing(const asn1cnst_range_t *ar, const asn1cnst_range_t *br) {
+    enum asn1cnst_range_narrowing an = ar->narrowing;
+    enum asn1cnst_range_narrowing bn = br->narrowing;
+
+    if(an) {
+        if(bn) {
+            return an < bn ? an : bn;
+        } else {
+            assert(bn == NOT_NARROW);
+            if(br->left.type == ARE_VALUE && br->right.type == ARE_VALUE) {
+                return an;
+            } else {
+                return NOT_NARROW;
+            }
+        }
+    } else if(bn) {
+        if(ar->left.type == ARE_VALUE && ar->right.type == ARE_VALUE) {
+            return bn;
+        } else {
+            return NOT_NARROW;
+        }
+    } else {
+        return NOT_NARROW;
+    }
+}
+
 static int _range_merge_in(asn1cnst_range_t *into, asn1cnst_range_t *cr) {
 	asn1cnst_range_t *r;
 	int prev_count = into->el_count;
@@ -222,6 +295,8 @@
 	if(into->extensible)
 		into->not_OER_visible = 1;
 
+    into->narrowing = _softest_narrowing(into, cr);
+
 	/*
 	 * Add the element OR all its children "into".
 	 */
@@ -548,6 +623,8 @@
     }
 	range->empty_constraint |= with->empty_constraint;
 
+    range->narrowing = _strongest_narrowing(range, with);
+
 	if(range->empty_constraint) {
 		/* No use in intersecting empty constraints */
 		return 0;
@@ -751,6 +828,128 @@
     return asn1constraint_compute_constraint_range(dbg_name, expr_type, ct, requested_ct_type, minmax, exmet, cpr_flags);
 }
 
+static asn1cnst_range_t *
+asn1f_real_range_from_WCOMPS(const char *dbg_name,
+                             const asn1p_constraint_t *ct) {
+    asn1cnst_range_t two = {
+        {ARE_VALUE, 0, 2}, {ARE_VALUE, 0, 2}, 0, NULL, 0, 0, 0, 0, 0, 0, 0};
+    asn1cnst_range_t ten = {
+        {ARE_VALUE, 0, 10}, {ARE_VALUE, 0, 10}, 0, NULL, 0, 0, 0, 0, 0, 0, 0};
+    asn1cnst_range_t *two_ten[] = {&two, &ten};
+    /* Interpretation of X.680 #21.5 */
+    asn1cnst_range_t mantissa_default_range = {
+        {ARE_MIN, 0, 0}, {ARE_MAX, 0, 0}, 0, NULL, 0, 0, 0, 0, 0, 0, 0};
+    asn1cnst_range_t exponent_default_range = {
+        {ARE_MIN, 0, 0}, {ARE_MAX, 0, 0}, 0, NULL, 0, 0, 0, 0, 0, 0, 0};
+    asn1cnst_range_t base_default_range = {
+        {ARE_VALUE, 0, 2}, {ARE_MAX, 0, 10}, 0, two_ten, 2, 0, 0, 0, 0, 0, 0};
+
+    asn1cnst_range_t *mantissa = _range_clone(&mantissa_default_range);
+    asn1cnst_range_t *exponent = _range_clone(&exponent_default_range);
+    asn1cnst_range_t *base = _range_clone(&base_default_range);
+    asn1cnst_range_t *range;
+
+#define FREE_MEB()             \
+    do {                       \
+        _range_free(mantissa); \
+        _range_free(exponent); \
+        _range_free(base);     \
+    } while(0)
+
+    (void)dbg_name;
+
+    assert(ct->type == ACT_CT_WCOMPS);
+
+    range = _range_new();
+    range->incompatible = 1;
+
+    for(unsigned i = 0; i < ct->el_count; i++) {
+        struct asn1p_constraint_s *el = ct->elements[i];
+        asn1p_value_t *value = 0;
+        asn1cnst_range_t **dst_range = 0;
+
+        switch(el->type) {
+        case ACT_EL_EXT:
+            /* Some components might not be defined. */
+            assert(range->incompatible);
+            FREE_MEB();
+            return range;
+        default:
+            assert(range->incompatible);
+            FREE_MEB();
+            return range;
+        case ACT_EL_VALUE:
+            value = el->value;
+            if(value->type != ATV_REFERENCED) {
+                FREE_MEB();
+                return range;
+            }
+            break;
+        }
+
+        if(strcmp(asn1p_ref_string(value->value.reference), "mantissa") == 0) {
+            dst_range = &mantissa;
+        } else if(strcmp(asn1p_ref_string(value->value.reference), "exponent")
+                  == 0) {
+            dst_range = &exponent;
+        } else if(strcmp(asn1p_ref_string(value->value.reference), "base")
+                  == 0) {
+            dst_range = &base;
+        } else {
+            FATAL("Invalid specification \"%s\" for REAL",
+                  asn1p_ref_string(value->value.reference));
+            FREE_MEB();
+            return range;
+        }
+
+        switch(el->el_count) {
+        case 0: continue;
+        case 1: break;
+        default:
+            FATAL("Too many constraints (%u) for \"%s\" for REAL", el->el_count,
+                  asn1p_ref_string(value->value.reference));
+            FREE_MEB();
+            return range;
+        }
+
+        asn1cnst_range_t *tmprange = asn1constraint_compute_constraint_range(
+            dbg_name, ASN_BASIC_INTEGER, el->elements[0], ACT_EL_RANGE,
+            *dst_range, 0, CPR_noflags);
+        assert(tmprange);
+        if(tmprange->incompatible) {
+            _range_free(tmprange);
+            FREE_MEB();
+            return range;
+        }
+        asn1constraint_range_free(*dst_range);
+        *dst_range = tmprange;
+    }
+
+    range->incompatible = 0;
+
+    /* X.696 #12.2 */
+    if(mantissa->left.type == ARE_VALUE && mantissa->right.type == ARE_VALUE
+       && mantissa->left.value >= -16777215 && mantissa->right.value <= 16777215
+       && base->left.type == ARE_VALUE && base->right.type == ARE_VALUE
+       && base->left.value == 2 && base->right.value == 2
+       && exponent->left.type == ARE_VALUE && exponent->right.type == ARE_VALUE
+       && exponent->left.value >= -126 && exponent->right.value <= 127) {
+        range->narrowing = NARROW_FLOAT32;
+    } else /* X.696 #12.3 */
+    if(mantissa->left.type == ARE_VALUE && mantissa->right.type == ARE_VALUE
+       && mantissa->left.value >= -9007199254740991ll
+       && mantissa->right.value <= 9007199254740991ull
+       && base->left.type == ARE_VALUE && base->right.type == ARE_VALUE
+       && base->left.value == 2 && base->right.value == 2
+       && exponent->left.type == ARE_VALUE && exponent->right.type == ARE_VALUE
+       && exponent->left.value >= -1022 && exponent->right.value <= 1023) {
+        range->narrowing = NARROW_FLOAT64;
+    }
+
+    FREE_MEB();
+    return range;
+}
+
 asn1cnst_range_t *
 asn1constraint_compute_constraint_range(
     const char *dbg_name, asn1p_expr_type_e expr_type,
@@ -857,7 +1056,6 @@
 		break;
 	case ACT_EL_EXT:
 		if(!*exmet) {
-			range->incompatible = 1;
 			range->extensible = 1;
 			range->not_OER_visible = 1;
 		} else {
@@ -1089,6 +1287,12 @@
 		range = asn1constraint_compute_constraint_range(dbg_name, expr_type,
 			ct->elements[0], requested_ct_type, minmax, exmet, cpr_flags);
 		return range;
+	case ACT_CT_WCOMPS:
+        if(expr_type == ASN_BASIC_REAL) {
+            return asn1f_real_range_from_WCOMPS(dbg_name, ct);
+        }
+		range->incompatible = 1;
+		return range;
 	default:
 		range->incompatible = 1;
 		return range;
@@ -1102,7 +1306,13 @@
 		return range;
 	}
 
-	_range_free(range);
+    if(expr_type == ASN_BASIC_REAL
+       && (vmin->type == ATV_REAL || vmax->type == ATV_REAL)) {
+        range->incompatible = 1;
+        return range;
+    }
+
+    _range_free(range);
 	range = _range_new();
 
     enum range_fill_result rfr;
@@ -1178,23 +1388,23 @@
     assert(r->elements[2]->elements == NULL);
 
     /* (MIN..9) */
-    fprintf(stderr, "[0].left = %s\n", _edge_value(&r->elements[0]->left));
-    fprintf(stderr, "[0].right = %s\n", _edge_value(&r->elements[0]->right));
+    fprintf(stderr, "[0].left = %s\n", _edge_string(&r->elements[0]->left));
+    fprintf(stderr, "[0].right = %s\n", _edge_string(&r->elements[0]->right));
     assert(r->elements[0]->left.type == ARE_MIN);
     assert(r->elements[0]->right.type == ARE_VALUE);
     assert(r->elements[0]->right.value == 9);
 
     /* (10..15) */
-    fprintf(stderr, "[1].left = %s\n", _edge_value(&r->elements[1]->left));
-    fprintf(stderr, "[1].right = %s\n", _edge_value(&r->elements[1]->right));
+    fprintf(stderr, "[1].left = %s\n", _edge_string(&r->elements[1]->left));
+    fprintf(stderr, "[1].right = %s\n", _edge_string(&r->elements[1]->right));
     assert(r->elements[1]->left.type == ARE_VALUE);
     assert(r->elements[1]->left.value == 10);
     assert(r->elements[1]->right.type == ARE_VALUE);
     assert(r->elements[1]->right.value == 15);
 
     /* (16..20) */
-    fprintf(stderr, "[2].left = %s\n", _edge_value(&r->elements[2]->left));
-    fprintf(stderr, "[2].right = %s\n", _edge_value(&r->elements[2]->right));
+    fprintf(stderr, "[2].left = %s\n", _edge_string(&r->elements[2]->left));
+    fprintf(stderr, "[2].right = %s\n", _edge_string(&r->elements[2]->right));
     assert(r->elements[2]->left.type == ARE_VALUE);
     assert(r->elements[2]->left.value == 16);
     assert(r->elements[2]->right.type == ARE_VALUE);
@@ -1224,16 +1434,16 @@
     assert(r->elements[1]->elements == NULL);
 
     /* (<min>..16) */
-    fprintf(stderr, "[0].left = %s\n", _edge_value(&r->elements[0]->left));
-    fprintf(stderr, "[0].right = %s\n", _edge_value(&r->elements[0]->right));
+    fprintf(stderr, "[0].left = %s\n", _edge_string(&r->elements[0]->left));
+    fprintf(stderr, "[0].right = %s\n", _edge_string(&r->elements[0]->right));
     assert(r->elements[0]->left.type == ARE_VALUE);
     assert(r->elements[0]->left.value == INTMAX_MIN);
     assert(r->elements[0]->right.type == ARE_VALUE);
     assert(r->elements[0]->right.value == 15);
 
     /* (16..20) */
-    fprintf(stderr, "[1].left = %s\n", _edge_value(&r->elements[1]->left));
-    fprintf(stderr, "[1].right = %s\n", _edge_value(&r->elements[1]->right));
+    fprintf(stderr, "[1].left = %s\n", _edge_string(&r->elements[1]->left));
+    fprintf(stderr, "[1].right = %s\n", _edge_string(&r->elements[1]->right));
     assert(r->elements[1]->left.type == ARE_VALUE);
     assert(r->elements[1]->left.value == 16);
     assert(r->elements[1]->right.type == ARE_VALUE);
diff --git a/libasn1fix/asn1fix_crange.h b/libasn1fix/asn1fix_crange.h
index a539508..df1bfaa 100644
--- a/libasn1fix/asn1fix_crange.h
+++ b/libasn1fix/asn1fix_crange.h
@@ -15,7 +15,14 @@
 	asn1cnst_edge_t left;	/* MIN from (MIN..10) */
 	asn1cnst_edge_t right;	/* 10 from (MIN..10) */
 
-	/* If range is split in parts, these are the parts */
+    enum asn1cnst_range_narrowing {
+        /* Sorted from softest to hardest narrowing */
+        NOT_NARROW,
+        NARROW_FLOAT64,
+        NARROW_FLOAT32,
+    } narrowing; /* Constrained to a known narrow type */
+
+    /* If range is split in parts, these are the parts */
 	struct asn1cnst_range_s **elements;
 	int el_count;
 	int el_size;