/*
 * Copyright (c) 2014,2018 Red Hat.
 * Copyright (c) 1995 Silicon Graphics, Inc.  All Rights Reserved.
 * 
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 */

#include "pmapi.h"
#include <inttypes.h>
#include <assert.h>
#include <ctype.h>

#if defined(HAVE_MATH_H)
#include <math.h>
#endif

#if !defined(ABS)
#define    ABS(a)          ((a) < 0 ? -(a) : (a))
#endif

#if defined(HAVE_CONST_LONGLONG)
#define SIGN_64_MASK 0x8000000000000000LL
#else
#define SIGN_64_MASK 0x8000000000000000
#endif

/*
 * pmAtomValue -> string, max length is 80 bytes
 *
 * To avoid alignment problems, avp _must_ be aligned appropriately
 * for a pmAtomValue pointer by the caller.
 */
char *
pmAtomStr_r(const pmAtomValue *avp, int type, char *buf, int buflen)
{
    int i;
    int vlen;
    char strbuf[40];
    const char errmsg[] = "Error: unexpected type";

    switch (type) {
	case PM_TYPE_32:
	    pmsprintf(buf, buflen, "%d", avp->l);
	    break;
	case PM_TYPE_U32:
	    pmsprintf(buf, buflen, "%u", avp->ul);
	    break;
	case PM_TYPE_64:
	    pmsprintf(buf, buflen, "%" PRIi64, avp->ll);
	    break;
	case PM_TYPE_U64:
	    pmsprintf(buf, buflen, "%" PRIu64, avp->ull);
	    break;
	case PM_TYPE_FLOAT:
	    pmsprintf(buf, buflen, "%e", (double) avp->f);
	    break;
	case PM_TYPE_DOUBLE:
	    pmsprintf(buf, buflen, "%e", avp->d);
	    break;
	case PM_TYPE_STRING:
	    if (avp->cp == NULL)
		pmsprintf(buf, buflen, "<null>");
	    else {
		i = (int) strlen(avp->cp);
		if (i < 38)
		    pmsprintf(buf, buflen, "\"%s\"", avp->cp);
		else
		    pmsprintf(buf, buflen, "\"%34.34s...\"", avp->cp);
	    }
	    break;
	case PM_TYPE_AGGREGATE:
	case PM_TYPE_AGGREGATE_STATIC:
	    if (avp->vbp == NULL) {
		pmsprintf(buf, buflen, "<null>");
		break;
	    }
	    vlen = avp->vbp->vlen - PM_VAL_HDR_SIZE;
	    if (vlen == 0)
		pmsprintf(buf, buflen, "[type=%s len=%d]", pmTypeStr_r(avp->vbp->vtype, strbuf, sizeof(strbuf)), vlen);
	    else {
		char *cp;
		char *bp;
		pmsprintf(buf, buflen, "[type=%s len=%d]", pmTypeStr_r(avp->vbp->vtype, strbuf, sizeof(strbuf)), vlen);
		cp = (char *) avp->vbp->vbuf;
		for (i = 0; i < vlen && i < 12; i++) {
		    bp = &buf[strlen(buf)];
		    if ((i % 4) == 0)
			pmsprintf(bp, buflen - (bp - buf), " %02x", *cp & 0xff);
		    else
			pmsprintf(bp, buflen - (bp - buf), "%02x", *cp & 0xff);
		    cp++;
		}
		if (vlen > 12) {
		    bp = &buf[strlen(buf)];
		    pmsprintf(bp, buflen - (bp - buf), " ...");
		}
	    }
	    break;
	case PM_TYPE_EVENT: {
	    /* have to assume alignment is OK in this case */
	    pmEventArray *eap = (pmEventArray *) avp->vbp;
	    if (eap->ea_nrecords == 1)
		pmsprintf(buf, buflen, "[1 event record]");
	    else
		pmsprintf(buf, buflen, "[%d event records]", eap->ea_nrecords);
	    break;
	}
	case PM_TYPE_HIGHRES_EVENT: {
	    /* have to assume alignment is OK in this case */
	    pmHighResEventArray *hreap = (pmHighResEventArray *) avp->vbp;
	    if (hreap->ea_nrecords == 1)
		pmsprintf(buf, buflen, "[1 event record]");
	    else
		pmsprintf(buf, buflen, "[%d event records]", hreap->ea_nrecords);
	    break;
	}
	case PM_TYPE_NOSUPPORT:
	    pmsprintf(buf, buflen, "%s: Not Supported", errmsg);
	    break;
	case PM_TYPE_UNKNOWN:
	    pmsprintf(buf, buflen, "%s: Unknown", errmsg);
	    break;
	default:
	    pmsprintf(buf, buflen, "%s: Illegal type=%d", errmsg, type);
	    break;
    }
    return buf;
}

/*
 * To avoid alignment problems, avp _must_ be aligned appropriately
 * for a pmAtomValue pointer by the caller.
 */
const char *
pmAtomStr(const pmAtomValue *avp, int type)
{
    static char abuf[80];

    pmAtomStr_r(avp, type, abuf, sizeof(abuf));
    return abuf;
}

/* scale+units -> string, max length is 60 bytes */
char *
pmUnitsStr_r(const pmUnits *pu, char *buf, int buflen)
{
    char *spacestr = NULL;
    char *timestr = NULL;
    char *countstr = NULL;
    char *p;
    char sbuf[20];
    char tbuf[20];
    char cbuf[20];

    /*
     * must be at least 60 bytes, then we don't need to pollute the code
     * below with a check every time we call pmsprintf() or increment p
     */
    if (buflen < 60)
	return NULL;

    buf[0] = '\0';

    if (pu->dimSpace) {
	switch (pu->scaleSpace) {
	    case PM_SPACE_BYTE:
		spacestr = "byte";
		break;
	    case PM_SPACE_KBYTE:
		spacestr = "Kbyte";
		break;
	    case PM_SPACE_MBYTE:
		spacestr = "Mbyte";
		break;
	    case PM_SPACE_GBYTE:
		spacestr = "Gbyte";
		break;
	    case PM_SPACE_TBYTE:
		spacestr = "Tbyte";
		break;
	    case PM_SPACE_PBYTE:
		spacestr = "Pbyte";
		break;
	    case PM_SPACE_EBYTE:
		spacestr = "Ebyte";
		break;
	    case PM_SPACE_ZBYTE:
		spacestr = "Zbyte";
		break;
	    case PM_SPACE_YBYTE:
		spacestr = "Ybyte";
		break;
	    default:
		pmsprintf(sbuf, sizeof(sbuf), "space-%d", pu->scaleSpace);
		spacestr = sbuf;
		break;
	}
    }
    if (pu->dimTime) {
	switch (pu->scaleTime) {
	    case PM_TIME_NSEC:
		timestr = "nanosec";
		break;
	    case PM_TIME_USEC:
		timestr = "microsec";
		break;
	    case PM_TIME_MSEC:
		timestr = "millisec";
		break;
	    case PM_TIME_SEC:
		timestr = "sec";
		break;
	    case PM_TIME_MIN:
		timestr = "min";
		break;
	    case PM_TIME_HOUR:
		timestr = "hour";
		break;
	    default:
		pmsprintf(tbuf, sizeof(tbuf), "time-%d", pu->scaleTime);
		timestr = tbuf;
		break;
	}
    }
    if (pu->dimCount) {
	switch (pu->scaleCount) {
	    case 0:
		countstr = "count";
		break;
	    case 1:
		pmsprintf(cbuf, sizeof(cbuf), "count x 10");
		countstr = cbuf;
		break;
	    default:
		pmsprintf(cbuf, sizeof(cbuf), "count x 10^%d", pu->scaleCount);
		countstr = cbuf;
		break;
	}
    }

    p = buf;

    if (pu->dimSpace > 0) {
	if (pu->dimSpace == 1)
	    pmsprintf(p, buflen, "%s", spacestr);
	else
	    pmsprintf(p, buflen, "%s^%d", spacestr, pu->dimSpace);
	while (*p)
	    p++;
	*p++ = ' ';
    }
    if (pu->dimTime > 0) {
	if (pu->dimTime == 1)
	    pmsprintf(p, buflen - (p - buf), "%s", timestr);
	else
	    pmsprintf(p, buflen - (p - buf), "%s^%d", timestr, pu->dimTime);
	while (*p)
	    p++;
	*p++ = ' ';
    }
    if (pu->dimCount > 0) {
	if (pu->dimCount == 1)
	    pmsprintf(p, buflen - (p - buf), "%s", countstr);
	else
	    pmsprintf(p, buflen - (p - buf), "%s^%d", countstr, pu->dimCount);
	while (*p)
	    p++;
	*p++ = ' ';
    }
    if (pu->dimSpace < 0 || pu->dimTime < 0 || pu->dimCount < 0) {
	*p++ = '/';
	*p++ = ' ';
	if (pu->dimSpace < 0) {
	    if (pu->dimSpace == -1)
		pmsprintf(p, buflen - (p - buf), "%s", spacestr);
	    else
		pmsprintf(p, buflen - (p - buf), "%s^%d", spacestr, -pu->dimSpace);
	    while (*p)
		p++;
	    *p++ = ' ';
	}
	if (pu->dimTime < 0) {
	    if (pu->dimTime == -1)
		pmsprintf(p, buflen - (p - buf), "%s", timestr);
	    else
		pmsprintf(p, buflen - (p - buf), "%s^%d", timestr, -pu->dimTime);
	    while (*p)
		p++;
	    *p++ = ' ';
	}
	if (pu->dimCount < 0) {
	    if (pu->dimCount == -1)
		pmsprintf(p, buflen - (p - buf), "%s", countstr);
	    else
		pmsprintf(p, buflen - (p - buf), "%s^%d", countstr, -pu->dimCount);
	    while (*p)
		p++;
	    *p++ = ' ';
	}
    }

    if (buf[0] == '\0') {
	/*
	 * dimension is all 0, but scale maybe specified ... small
	 * anomaly here as we would expect dimCount to be 1 not
	 * 0 for these cases, but support maintained for historical
	 * behaviour
	 */
	if (pu->scaleCount == 1)
	    pmsprintf(buf, buflen, "x 10");
	else if (pu->scaleCount != 0)
	    pmsprintf(buf, buflen, "x 10^%d", pu->scaleCount);
    }
    else {
	p--;
	*p = '\0';
    }

    return buf;
}

const char *
pmUnitsStr(const pmUnits *pu)
{
    static char ubuf[60];

    pmUnitsStr_r(pu, ubuf, sizeof(ubuf));
    return ubuf;
}

/* Scale conversion, based on value format, value type and scale */
int
pmConvScale(int type, const pmAtomValue * ival, const pmUnits * iunit, pmAtomValue * oval, const pmUnits * ounit)
{
    int sts;
    int k;
    __int64_t div, mult;
    __int64_t d, m;
    char strbuf[80];

    if (pmDebugOptions.value) {
	fprintf(stderr, "pmConvScale: %s", pmAtomStr_r(ival, type, strbuf, sizeof(strbuf)));
	fprintf(stderr, " [%s]", pmUnitsStr_r(iunit, strbuf, sizeof(strbuf)));
    }

    if (iunit->dimSpace != ounit->dimSpace || iunit->dimTime != ounit->dimTime || iunit->dimCount != ounit->dimCount) {
	sts = PM_ERR_CONV;
	goto bad;
    }

    div = mult = 1;

    if (iunit->dimSpace) {
	d = 1;
	m = 1;
	switch (iunit->scaleSpace) {
	    case PM_SPACE_BYTE:
		d = 1024 * 1024;
		break;
	    case PM_SPACE_KBYTE:
		d = 1024;
		break;
	    case PM_SPACE_MBYTE:
		/* the canonical unit */
		break;
	    case PM_SPACE_GBYTE:
		m = 1024;
		break;
	    case PM_SPACE_TBYTE:
		m = 1024 * 1024;
		break;
	    case PM_SPACE_PBYTE:
		m = 1024 * 1024 * 1024;
		break;
	    case PM_SPACE_EBYTE:
		m = (__int64_t) 1024 *1024 * 1024 * 1024;
		break;
	    default:
		sts = PM_ERR_UNIT;
		goto bad;
	}
	switch (ounit->scaleSpace) {
	    case PM_SPACE_BYTE:
		m *= 1024 * 1024;
		break;
	    case PM_SPACE_KBYTE:
		m *= 1024;
		break;
	    case PM_SPACE_MBYTE:
		/* the canonical unit */
		break;
	    case PM_SPACE_GBYTE:
		d *= 1024;
		break;
	    case PM_SPACE_TBYTE:
		d *= 1024 * 1024;
		break;
	    case PM_SPACE_PBYTE:
		d *= 1024 * 1024 * 1024;
		break;
	    case PM_SPACE_EBYTE:
		d *= (__int64_t) 1024 *1024 * 1024 * 1024;
		break;
	    default:
		sts = PM_ERR_UNIT;
		goto bad;
	}
	if (iunit->dimSpace > 0) {
	    for (k = 0; k < iunit->dimSpace; k++) {
		div *= d;
		mult *= m;
	    }
	}
	else {
	    for (k = iunit->dimSpace; k < 0; k++) {
		mult *= d;
		div *= m;
	    }
	}
    }

    if (iunit->dimTime) {
	d = 1;
	m = 1;
	switch (iunit->scaleTime) {
	    case PM_TIME_NSEC:
		d = 1000000000;
		break;
	    case PM_TIME_USEC:
		d = 1000000;
		break;
	    case PM_TIME_MSEC:
		d = 1000;
		break;
	    case PM_TIME_SEC:
		/* the canonical unit */
		break;
	    case PM_TIME_MIN:
		m = 60;
		break;
	    case PM_TIME_HOUR:
		m = 3600;
		break;
	    default:
		sts = PM_ERR_UNIT;
		goto bad;
	}
	switch (ounit->scaleTime) {
	    case PM_TIME_NSEC:
		m *= 1000000000;
		break;
	    case PM_TIME_USEC:
		m *= 1000000;
		break;
	    case PM_TIME_MSEC:
		m *= 1000;
		break;
	    case PM_TIME_SEC:
		/* the canonical unit */
		break;
	    case PM_TIME_MIN:
		d *= 60;
		break;
	    case PM_TIME_HOUR:
		d *= 3600;
		break;
	    default:
		sts = PM_ERR_UNIT;
		goto bad;
	}
	if (iunit->dimTime > 0) {
	    for (k = 0; k < iunit->dimTime; k++) {
		div *= d;
		mult *= m;
	    }
	}
	else {
	    for (k = iunit->dimTime; k < 0; k++) {
		mult *= d;
		div *= m;
	    }
	}
    }

    if (iunit->dimCount || (iunit->dimSpace == 0 && iunit->dimTime == 0)) {
	d = 1;
	m = 1;
	if (iunit->scaleCount < 0) {
	    for (k = iunit->scaleCount; k < 0; k++)
		d *= 10;
	}
	else if (iunit->scaleCount > 0) {
	    for (k = 0; k < iunit->scaleCount; k++)
		m *= 10;
	}
	if (ounit->scaleCount < 0) {
	    for (k = ounit->scaleCount; k < 0; k++)
		m *= 10;
	}
	else if (ounit->scaleCount > 0) {
	    for (k = 0; k < ounit->scaleCount; k++)
		d *= 10;
	}
	if (iunit->dimCount > 0) {
	    for (k = 0; k < iunit->dimCount; k++) {
		div *= d;
		mult *= m;
	    }
	}
	else if (iunit->dimCount < 0) {
	    for (k = iunit->dimCount; k < 0; k++) {
		mult *= d;
		div *= m;
	    }
	}
	else {
	    mult = m;
	    div = d;
	}
    }

    if (mult % div == 0) {
	mult /= div;
	div = 1;
    }

    switch (type) {
	case PM_TYPE_32:
	    oval->l = (__int32_t) ((ival->l * mult + div / 2) / div);
	    break;
	case PM_TYPE_U32:
	    oval->ul = (__uint32_t) ((ival->ul * mult + div / 2) / div);
	    break;
	case PM_TYPE_64:
	    oval->ll = (ival->ll * mult + div / 2) / div;
	    break;
	case PM_TYPE_U64:
	    oval->ull = (ival->ull * mult + div / 2) / div;
	    break;
	case PM_TYPE_FLOAT:
	    oval->f = ival->f * ((float) mult / (float) div);
	    break;
	case PM_TYPE_DOUBLE:
	    oval->d = ival->d * ((double) mult / (double) div);
	    break;
	default:
	    sts = PM_ERR_CONV;
	    goto bad;
    }

    if (pmDebugOptions.value) {
	fprintf(stderr, " -> %s", pmAtomStr_r(oval, type, strbuf, sizeof(strbuf)));
	fprintf(stderr, " [%s]\n", pmUnitsStr_r(ounit, strbuf, sizeof(strbuf)));
    }
    return 0;

bad:
    if (pmDebugOptions.value) {
	char errmsg[PM_MAXERRMSGLEN];
	fprintf(stderr, " -> Error: %s", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
	fprintf(stderr, " [%s]\n", pmUnitsStr_r(ounit, strbuf, sizeof(strbuf)));
    }
    return sts;
}

/* Value extract from pmValue and type conversion */
int
pmExtractValue(int valfmt, const pmValue * ival, int itype, pmAtomValue * oval, int otype)
{
    void *avp;
    pmAtomValue av;
    int sts = 0;
    int len;
    const char *vp;
    char buf[80];
    char strbuf[80];

    if (pmDebugOptions.value) {
	fprintf(stderr, "pmExtractValue: ");
	vp = "???";
    }

    oval->ll = 0;
    if (valfmt == PM_VAL_INSITU) {
	av.l = ival->value.lval;
	if (pmDebugOptions.value)
	    vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
	switch (itype) {

	    case PM_TYPE_32:
	    case PM_TYPE_UNKNOWN:
		switch (otype) {
		    case PM_TYPE_32:
			oval->l = av.l;
			break;
		    case PM_TYPE_U32:
			if (av.l < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ul = (__uint32_t) av.l;
			break;
		    case PM_TYPE_64:
			oval->ll = (__int64_t) av.l;
			break;
		    case PM_TYPE_U64:
			if (av.l < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ull = (__uint64_t) av.l;
			break;
		    case PM_TYPE_FLOAT:
			oval->f = (float) av.l;
			break;
		    case PM_TYPE_DOUBLE:
			oval->d = (double) av.l;
			break;
		    default:
			sts = PM_ERR_CONV;
		}
		break;

	    case PM_TYPE_U32:
		switch (otype) {
		    case PM_TYPE_32:
			if (av.ul > 0x7fffffff)
			    sts = PM_ERR_TRUNC;
			else
			    oval->l = (__int32_t) av.ul;
			break;
		    case PM_TYPE_U32:
			oval->ul = (__uint32_t) av.ul;
			break;
		    case PM_TYPE_64:
			oval->ll = (__int64_t) av.ul;
			break;
		    case PM_TYPE_U64:
			oval->ull = (__uint64_t) av.ul;
			break;
		    case PM_TYPE_FLOAT:
			oval->f = (float) av.ul;
			break;
		    case PM_TYPE_DOUBLE:
			oval->d = (double) av.ul;
			break;
		    default:
			sts = PM_ERR_CONV;
		}
		break;

		/*
		 * Notes on conversion to FLOAT ... because of the limited
		 * precision of the mantissa, more than one integer value
		 * maps to the same floating point value ... hence the
		 * >= (float)max-int-value style of tests
		 */
	    case PM_TYPE_FLOAT:	/* old style insitu encoding */
		switch (otype) {
		    case PM_TYPE_32:
			if ((float) ABS(av.f) >= (float) 0x7fffffff)
			    sts = PM_ERR_TRUNC;
			else
			    oval->l = (__int32_t) av.f;
			break;
		    case PM_TYPE_U32:
			if (av.f >= (float) ((unsigned) 0xffffffff))
			    sts = PM_ERR_TRUNC;
			else if (av.f < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ul = (__uint32_t) av.f;
			break;
		    case PM_TYPE_64:
#if defined(HAVE_CONST_LONGLONG)
			if (av.f >= (float) 0x7fffffffffffffffLL)
			    sts = PM_ERR_TRUNC;
#else
			if (av.f >= (float) 0x7fffffffffffffff)
			    sts = PM_ERR_TRUNC;
#endif
			else
			    oval->ll = (__int64_t) av.f;
			break;
		    case PM_TYPE_U64:
#if defined(HAVE_CONST_LONGLONG)
			if (av.f >= (float) ((__uint64_t) 0xffffffffffffffffLL))
			    sts = PM_ERR_TRUNC;
#else
			if (av.f >= (float) ((__uint64_t) 0xffffffffffffffff))
			    sts = PM_ERR_TRUNC;
#endif
			else if (av.f < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ull = (__uint64_t) av.f;
			break;
		    case PM_TYPE_FLOAT:
			oval->f = av.f;
			break;
		    case PM_TYPE_DOUBLE:
			oval->d = (double) av.f;
			break;
		    default:
			sts = PM_ERR_CONV;
		}
		break;

	    case PM_TYPE_64:
	    case PM_TYPE_U64:
	    case PM_TYPE_DOUBLE:
	    case PM_TYPE_STRING:
	    case PM_TYPE_AGGREGATE:
	    case PM_TYPE_EVENT:
	    case PM_TYPE_HIGHRES_EVENT:
	    default:
		sts = PM_ERR_CONV;
	}
    }
    else if (valfmt == PM_VAL_DPTR || valfmt == PM_VAL_SPTR) {
	__int64_t src;
	__uint64_t usrc;
	double dsrc;
	float fsrc;
	switch (itype) {

	    case PM_TYPE_64:
		if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(__int64_t)
		    || (ival->value.pval->vtype != PM_TYPE_64 && ival->value.pval->vtype != 0)) {
		    sts = PM_ERR_CONV;
		    break;
		}
		avp = (void *) &ival->value.pval->vbuf;
		memcpy((void *) &av.ll, avp, sizeof(av.ll));
		if (pmDebugOptions.value)
		    vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
		src = av.ll;
		switch (otype) {
		    case PM_TYPE_32:
			if (src > 0x7fffffff)
			    sts = PM_ERR_TRUNC;
			else
			    oval->l = (__int32_t) src;
			break;
		    case PM_TYPE_U32:
			if (src > (unsigned) 0xffffffff)
			    sts = PM_ERR_TRUNC;
			else if (src < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ul = (__uint32_t) src;
			break;
		    case PM_TYPE_64:
			oval->ll = src;
			break;
		    case PM_TYPE_U64:
			if (src < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ull = (__uint64_t) src;
			break;
		    case PM_TYPE_FLOAT:
			oval->f = (float) src;
			break;
		    case PM_TYPE_DOUBLE:
			oval->d = (double) src;
			break;
		    default:
			sts = PM_ERR_CONV;
		}
		break;

	    case PM_TYPE_U64:
		if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(__uint64_t)
		    || (ival->value.pval->vtype != PM_TYPE_U64 && ival->value.pval->vtype != 0)) {
		    sts = PM_ERR_CONV;
		    break;
		}
		avp = (void *) &ival->value.pval->vbuf;
		memcpy((void *) &av.ull, avp, sizeof(av.ull));
		if (pmDebugOptions.value)
		    vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
		usrc = av.ull;
		switch (otype) {
		    case PM_TYPE_32:
			if (usrc > 0x7fffffff)
			    sts = PM_ERR_TRUNC;
			else
			    oval->l = (__int32_t) usrc;
			break;
		    case PM_TYPE_U32:
			if (usrc > (unsigned) 0xffffffff)
			    sts = PM_ERR_TRUNC;
			else
			    oval->ul = (__uint32_t) usrc;
			break;
		    case PM_TYPE_64:
#if defined(HAVE_CONST_LONGLONG)
			if (usrc > (__int64_t) 0x7fffffffffffffffLL)
			    sts = PM_ERR_TRUNC;
#else
			if (usrc > (__int64_t) 0x7fffffffffffffff)
			    sts = PM_ERR_TRUNC;
#endif
			else
			    oval->ll = (__int64_t) usrc;
			break;
		    case PM_TYPE_U64:
			oval->ull = usrc;
			break;
		    case PM_TYPE_FLOAT:
#if !defined(HAVE_CAST_U64_DOUBLE)
			if (SIGN_64_MASK & usrc)
			    oval->f = (float) (__int64_t) (usrc & (~SIGN_64_MASK)) + (__uint64_t) SIGN_64_MASK;
			else
			    oval->f = (float) (__int64_t) usrc;
#else
			oval->f = (float) usrc;
#endif
			break;
		    case PM_TYPE_DOUBLE:
#if !defined(HAVE_CAST_U64_DOUBLE)
			if (SIGN_64_MASK & usrc)
			    oval->d = (double) (__int64_t) (usrc & (~SIGN_64_MASK)) + (__uint64_t) SIGN_64_MASK;
			else
			    oval->d = (double) (__int64_t) usrc;
#else
			oval->d = (double) usrc;
#endif
			break;
		    default:
			sts = PM_ERR_CONV;
		}
		break;

	    case PM_TYPE_DOUBLE:
		if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(double)
		    || (ival->value.pval->vtype != PM_TYPE_DOUBLE && ival->value.pval->vtype != 0)) {
		    sts = PM_ERR_CONV;
		    break;
		}
		avp = (void *) &ival->value.pval->vbuf;
		memcpy((void *) &av.d, avp, sizeof(av.d));
		if (pmDebugOptions.value)
		    vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
		dsrc = av.d;
		switch (otype) {
		    case PM_TYPE_32:
			if (ABS(dsrc) >= (double) 0x7fffffff)
			    sts = PM_ERR_TRUNC;
			else
			    oval->l = (__int32_t) dsrc;
			break;
		    case PM_TYPE_U32:
			if (dsrc >= (double) ((unsigned) 0xffffffff))
			    sts = PM_ERR_TRUNC;
			else if (dsrc < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ul = (__uint32_t) dsrc;
			break;
		    case PM_TYPE_64:
#if defined(HAVE_CONST_LONGLONG)
			if (dsrc >= (double) 0x7fffffffffffffffLL)
			    sts = PM_ERR_TRUNC;
#else
			if (dsrc >= (double) 0x7fffffffffffffff)
			    sts = PM_ERR_TRUNC;
#endif
			else
			    oval->ll = (__int64_t) dsrc;
			break;
		    case PM_TYPE_U64:
#if defined(HAVE_CONST_LONGLONG)
			if (dsrc >= (double) ((__uint64_t) 0xffffffffffffffffLL))
			    sts = PM_ERR_TRUNC;
#else
			if (dsrc >= (double) ((__uint64_t) 0xffffffffffffffff))
			    sts = PM_ERR_TRUNC;
#endif
			else if (dsrc < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ull = (__uint64_t) dsrc;
			break;
		    case PM_TYPE_FLOAT:
			oval->f = (float) dsrc;
			break;
		    case PM_TYPE_DOUBLE:
			oval->d = dsrc;
			break;
		    default:
			sts = PM_ERR_CONV;
		}
		break;

	    case PM_TYPE_FLOAT:	/* new style pmValueBlock encoding */
		if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(float)
		    || (ival->value.pval->vtype != PM_TYPE_FLOAT && ival->value.pval->vtype != 0)) {
		    sts = PM_ERR_CONV;
		    break;
		}
		avp = (void *) &ival->value.pval->vbuf;
		memcpy((void *) &av.f, avp, sizeof(av.f));
		if (pmDebugOptions.value)
		    vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
		fsrc = av.f;
		switch (otype) {
		    case PM_TYPE_32:
			if ((float) ABS(fsrc) >= (float) 0x7fffffff)
			    sts = PM_ERR_TRUNC;
			else
			    oval->l = (__int32_t) fsrc;
			break;
		    case PM_TYPE_U32:
			if (fsrc >= (float) ((unsigned) 0xffffffff))
			    sts = PM_ERR_TRUNC;
			else if (fsrc < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ul = (__uint32_t) fsrc;
			break;
		    case PM_TYPE_64:
#if defined(HAVE_CONST_LONGLONG)
			if (fsrc >= (float) 0x7fffffffffffffffLL)
			    sts = PM_ERR_TRUNC;
#else
			if (fsrc >= (float) 0x7fffffffffffffff)
			    sts = PM_ERR_TRUNC;
#endif
			else
			    oval->ll = (__int64_t) fsrc;
			break;
		    case PM_TYPE_U64:
#if defined(HAVE_CONST_LONGLONG)
			if (fsrc >= (float) ((__uint64_t) 0xffffffffffffffffLL))
			    sts = PM_ERR_TRUNC;
#else
			if (fsrc >= (float) ((__uint64_t) 0xffffffffffffffff))
			    sts = PM_ERR_TRUNC;
#endif
			else if (fsrc < 0)
			    sts = PM_ERR_SIGN;
			else
			    oval->ull = (__uint64_t) fsrc;
			break;
		    case PM_TYPE_FLOAT:
			oval->f = fsrc;
			break;
		    case PM_TYPE_DOUBLE:
			oval->d = (float) fsrc;
			break;
		    default:
			sts = PM_ERR_CONV;
		}
		break;

	    case PM_TYPE_STRING:
		if (ival->value.pval->vtype != PM_TYPE_STRING && ival->value.pval->vtype != 0) {
		    sts = PM_ERR_CONV;
		    break;
		}
		len = ival->value.pval->vlen - PM_VAL_HDR_SIZE;
		if (pmDebugOptions.value) {
		    if (ival->value.pval->vbuf[0] == '\0')
			vp = "<null>";
		    else {
			int i;
			i = (int) strlen(ival->value.pval->vbuf);
			if (i < 38)
			    pmsprintf(buf, sizeof(buf), "\"%s\"", ival->value.pval->vbuf);
			else
			    pmsprintf(buf, sizeof(buf), "\"%34.34s...\"", ival->value.pval->vbuf);
			vp = buf;
		    }
		}
		if (otype != PM_TYPE_STRING) {
		    sts = PM_ERR_CONV;
		    break;
		}
		if ((oval->cp = (char *) malloc(len + 1)) == NULL) {
		    pmNoMem("pmExtractValue.string", len + 1, PM_FATAL_ERR);
		}
		memcpy(oval->cp, ival->value.pval->vbuf, len);
		oval->cp[len] = '\0';
		break;

	    case PM_TYPE_AGGREGATE:
		if (ival->value.pval->vtype != PM_TYPE_AGGREGATE && ival->value.pval->vtype != 0) {
		    sts = PM_ERR_CONV;
		    break;
		}
		len = ival->value.pval->vlen;
		if (pmDebugOptions.value) {
		    int vlen;
		    int i;
		    vlen = ival->value.pval->vlen - PM_VAL_HDR_SIZE;
		    if (vlen == 0)
			pmsprintf(buf, sizeof(buf), "[len=%d]", vlen);
		    else {
			char *cp;
			char *bp;
			pmsprintf(buf, sizeof(buf), "[len=%d]", vlen);
			cp = (char *) ival->value.pval->vbuf;
			for (i = 0; i < vlen && i < 12; i++) {
			    bp = &buf[strlen(buf)];
			    if ((i % 4) == 0)
				pmsprintf(bp, sizeof(buf) - (bp - buf), " %02x", *cp & 0xff);
			    else
				pmsprintf(bp, sizeof(buf) - (bp - buf), "%02x", *cp & 0xff);
			    cp++;
			}
			if (vlen > 12) {
			    bp = &buf[strlen(buf)];
			    pmsprintf(bp, sizeof(buf) - (bp - buf), " ...");
			}
		    }
		    vp = buf;
		}
		if (otype != PM_TYPE_AGGREGATE) {
		    sts = PM_ERR_CONV;
		    break;
		}
		if ((oval->vbp = (pmValueBlock *) malloc(len)) == NULL) {
		    pmNoMem("pmExtractValue.aggr", len, PM_FATAL_ERR);
		}
		memcpy(oval->vbp, ival->value.pval, len);
		break;

	    case PM_TYPE_32:
	    case PM_TYPE_U32:
	    case PM_TYPE_EVENT:
	    case PM_TYPE_HIGHRES_EVENT:
	    default:
		sts = PM_ERR_CONV;
	}
    }
    else
	sts = PM_ERR_CONV;

    if (pmDebugOptions.value) {
	char errmsg[PM_MAXERRMSGLEN];
	fprintf(stderr, " %s", vp);
	fprintf(stderr, " [%s]", pmTypeStr_r(itype, strbuf, sizeof(strbuf)));
	if (sts == 0)
	    fprintf(stderr, " -> %s", pmAtomStr_r(oval, otype, strbuf, sizeof(strbuf)));
	else
	    fprintf(stderr, " -> Error: %s", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
	fprintf(stderr, " [%s]\n", pmTypeStr_r(otype, strbuf, sizeof(strbuf)));
    }

    return sts;
}

/*
 * An internal variant of pmUnits, but without the narrow bitfields.
 * That way, we can tolerate intermediate arithmetic that goes out of
 * range of the 4-bit bitfields.
 */
typedef struct __pmUnits {
    int			dimSpace;	/* space dimension */
    int			dimTime;	/* time dimension */
    int			dimCount;	/* event dimension */
    unsigned int	scaleSpace;	/* one of PM_SPACE_* */
    unsigned int	scaleTime;	/* one of PM_TIME_* */
    int			scaleCount;	/* one of PM_COUNT_* */
} __pmUnits;

/*
 * Parse a general "N $units" string into a pmUnits tuple and a multiplier.
 * @units can be a series of SCALE-UNIT^EXPONENT, each unit dimension appearing
 * at most once.
 */

static int
__pmParseUnitsStrPart(const char *str, const char *str_end, __pmUnits *out, double *multiplier, char **errMsg)
{
    int sts = 0;
    unsigned i;
    const char *ptr;		/* scanning along str */
    enum { d_none, d_space, d_time, d_count } dimension;
    struct unit_keyword_t {
	const char	*keyword;
	int		scale;
    };
    static const struct unit_keyword_t time_keywords[] = {
	{"nanoseconds", PM_TIME_NSEC}, {"nanosecond", PM_TIME_NSEC},
	{"nanosec", PM_TIME_NSEC}, {"ns", PM_TIME_NSEC},
	{"microseconds", PM_TIME_USEC}, {"microsecond", PM_TIME_USEC},
	{"microsec", PM_TIME_USEC}, {"us", PM_TIME_USEC},
	{"milliseconds", PM_TIME_MSEC}, {"millisecond", PM_TIME_MSEC},
	{"millisec", PM_TIME_MSEC}, {"ms", PM_TIME_MSEC},
	{"seconds", PM_TIME_SEC}, {"second", PM_TIME_SEC},
	{"sec", PM_TIME_SEC}, {"s", PM_TIME_SEC},
	{"minutes", PM_TIME_MIN}, {"minute", PM_TIME_MIN}, {"min", PM_TIME_MIN},
	{"hours", PM_TIME_HOUR}, {"hour", PM_TIME_HOUR},
	{"hr", PM_TIME_HOUR}, {"h", PM_TIME_HOUR},
	{"time-0", 0}, /* { "time-1", 1 }, */ {"time-2", 2}, {"time-3", 3},
	{"time-4", 4}, {"time-5", 5}, {"time-6", 6}, {"time-7", 7},
	{"time-8", 8}, {"time-9", 9}, {"time-10", 10}, {"time-11", 11},
	{"time-12", 12}, {"time-13", 13}, {"time-14", 14}, {"time-15", 15},
	{"time-1", 1},
    };
    const size_t num_time_keywords = sizeof(time_keywords) / sizeof(time_keywords[0]);
    static const struct unit_keyword_t space_keywords[] = {
	{"bytes", PM_SPACE_BYTE}, {"byte", PM_SPACE_BYTE},
	{"KB", PM_SPACE_KBYTE}, {"KiB", PM_SPACE_KBYTE},
	{"Kbytes", PM_SPACE_KBYTE}, {"Kbyte", PM_SPACE_KBYTE},
	{"Kibibytes", PM_SPACE_KBYTE}, {"Kibibyte", PM_SPACE_KBYTE},
	{"Kilobytes", PM_SPACE_KBYTE}, {"Kilobyte", PM_SPACE_KBYTE},	/* back-compat */
	{"MB", PM_SPACE_MBYTE}, {"MiB", PM_SPACE_MBYTE},
	{"Mbytes", PM_SPACE_MBYTE}, {"Mbyte", PM_SPACE_MBYTE},
	{"Mebibytes", PM_SPACE_MBYTE}, {"Mebibyte", PM_SPACE_MBYTE},
	{"Megabytes", PM_SPACE_MBYTE}, {"Megabyte", PM_SPACE_MBYTE},	/* back-compat */
	{"GB", PM_SPACE_GBYTE}, {"GiB", PM_SPACE_GBYTE},
	{"Gbytes", PM_SPACE_GBYTE}, {"Gbyte", PM_SPACE_GBYTE},
	{"Gibibytes", PM_SPACE_GBYTE}, {"Gibibyte", PM_SPACE_GBYTE},
	{"Gigabytes", PM_SPACE_GBYTE}, {"Gigabyte", PM_SPACE_GBYTE},	/* back-compat */
	{"TB", PM_SPACE_TBYTE}, {"TiB", PM_SPACE_TBYTE},
	{"Tbytes", PM_SPACE_TBYTE}, {"Tbyte", PM_SPACE_TBYTE},
	{"Tebibytes", PM_SPACE_TBYTE}, {"Tebibyte", PM_SPACE_TBYTE},
	{"Terabytes", PM_SPACE_TBYTE}, {"Terabyte", PM_SPACE_TBYTE},	/* back-compat */
	{"PB", PM_SPACE_PBYTE}, {"PiB", PM_SPACE_PBYTE},
	{"Pbytes", PM_SPACE_PBYTE}, {"Pbyte", PM_SPACE_PBYTE},
	{"Pebibytes", PM_SPACE_PBYTE}, {"Pebibyte", PM_SPACE_PBYTE},
	{"Petabytes", PM_SPACE_PBYTE}, {"Petabyte", PM_SPACE_PBYTE},	/* back-compat */
	{"EB", PM_SPACE_EBYTE}, {"EiB", PM_SPACE_EBYTE},
	{"Ebytes", PM_SPACE_EBYTE}, {"Ebyte", PM_SPACE_EBYTE},
	{"Exbibytes", PM_SPACE_EBYTE}, {"Exbibyte", PM_SPACE_EBYTE},
	{"Exabytes", PM_SPACE_EBYTE}, {"Exabyte", PM_SPACE_EBYTE},	/* back-compat */
	{"ZB", PM_SPACE_ZBYTE}, {"ZiB", PM_SPACE_ZBYTE},
	{"Zbytes", PM_SPACE_ZBYTE}, {"Zbyte", PM_SPACE_ZBYTE},
	{"Zebibytes", PM_SPACE_ZBYTE}, {"Zebibyte", PM_SPACE_ZBYTE},
	{"Zettabytes", PM_SPACE_ZBYTE}, {"Zettabyte", PM_SPACE_ZBYTE},	/* back-compat */
	{"YB", PM_SPACE_YBYTE}, {"YiB", PM_SPACE_YBYTE},
	{"Ybytes", PM_SPACE_YBYTE}, {"Ybyte", PM_SPACE_YBYTE},
	{"Yobibytes", PM_SPACE_YBYTE}, {"Yobibyte", PM_SPACE_YBYTE},
	{"Yottabytes", PM_SPACE_YBYTE}, {"Yottabyte", PM_SPACE_YBYTE},	/* back-compat */
	{"space-0", 0}, /* { "space-1", 1 }, */ {"space-2", 2}, {"space-3", 3},
	{"space-4", 4}, {"space-5", 5}, {"space-6", 6}, {"space-7", 7},
	{"space-8", 8}, {"space-9", 9}, {"space-10", 10}, {"space-11", 11},
	{"space-12", 12}, {"space-13", 13}, {"space-14", 14}, {"space-15", 15},
	{"space-1", 1},
    };
    const size_t num_space_keywords = sizeof(space_keywords) / sizeof(space_keywords[0]);
    static const struct unit_keyword_t count_keywords[] = {
	{"count x 10^-8", -8},
	{"count x 10^-7", -7},
	{"count x 10^-6", -6},
	{"count x 10^-5", -5},
	{"count x 10^-4", -4},
	{"count x 10^-3", -3},
	{"count x 10^-2", -2},
	{"count x 10^-1", -1},
	/* { "count", 0 }, { "counts", 0 }, */
	/* { "count x 10", 1 }, */
	{"count x 10^2", 2},
	{"count x 10^3", 3},
	{"count x 10^4", 4},
	{"count x 10^5", 5},
	{"count x 10^6", 6},
	{"count x 10^7", 7},
	{"count x 10", 1},
	{"counts", 0},
	{"count", 0},
	/* NB: we don't support the anomalous "x 10^SCALE" syntax for the dimCount=0 case. */
    };
    const size_t num_count_keywords = sizeof(count_keywords) / sizeof(count_keywords[0]);
    static const struct unit_keyword_t exponent_keywords[] = {
	{"^-8", -8}, {"^-7", -7}, {"^-6", -6}, {"^-5", -5},
	{"^-4", -4}, {"^-3", -3}, {"^-2", -2}, {"^-1", -1},
	{"^0", 0}, /*{ "^1", 1 }, */ {"^2", 2}, {"^3", 3},
	{"^4", 4}, {"^5", 5}, {"^6", 6}, {"^7", 7},
	/*
	 * NB: the following larger exponents are enabled by use of __pmUnits
	 * above.  They are necessary because pmUnitsStr emits foo-dim=-8 as
	 * "/ foo^8", * so the denominator can encounter wider-than-bitfield
	 * exponents.
	 */
	{"^8", 8}, {"^9", 9}, {"^10", 10}, {"^11", 11},
	{"^12", 12}, {"^13", 13}, {"^14", 14}, {"^15", 15},
	{"^1", 1},
    };
    const size_t num_exponent_keywords = sizeof(exponent_keywords) / sizeof(exponent_keywords[0]);

    *multiplier = 1.0;
    memset(out, 0, sizeof(*out));
    ptr = str;

    while (ptr != str_end) {	/* parse whole string */
	assert(*ptr != '\0');

	if (isspace((int)(*ptr))) {	/* skip whitespace */
	    ptr++;
	    continue;
	}

	if (*ptr == '-' || *ptr == '.' || isdigit((int)(*ptr))) {
	    /* possible floating-point number - parse with strtod(3). */
	    char *newptr;
	    errno = 0;
	    double m = strtod(ptr, &newptr);
	    if (errno || newptr == ptr || newptr > str_end) {
		sts = PM_ERR_CONV;
		*errMsg = strdup("invalid floating point literal");
		goto out;
	    }
	    ptr = newptr;
	    *multiplier *= m;
	    continue;
	}

	dimension = d_none;	/* classify dimension of base unit */

	/* match & skip over keyword (followed by space, ^, or EOL) */
#define streqskip(q) (((ptr+strlen(q) <= str_end) &&        \
                       (strncasecmp(ptr,q,strlen(q))==0) && \
                       ((isspace((int)(*(ptr+strlen(q))))) ||      \
                        (*(ptr+strlen(q))=='^') ||          \
                        (ptr+strlen(q)==str_end)))          \
                       ? (ptr += strlen(q), 1) : 0)

	/*
	 * Parse base unit, only once per input string.  We don't support
	 * "microsec millisec", as that would require arithmetic on the scales.
	 * We could support "sec sec" (and turn that into sec^2) in the future.
	 */
	if (dimension == d_none && out->dimTime == 0)
	    for (i = 0; i < num_time_keywords; i++)
		if (streqskip(time_keywords[i].keyword)) {
		    out->scaleTime = time_keywords[i].scale;
		    dimension = d_time;
		    break;
		}
	if (dimension == d_none && out->dimSpace == 0)
	    for (i = 0; i < num_space_keywords; i++)
		if (streqskip(space_keywords[i].keyword)) {
		    out->scaleSpace = space_keywords[i].scale;
		    dimension = d_space;
		    break;
		}
	if (dimension == d_none && out->dimCount == 0)
	    for (i = 0; i < num_count_keywords; i++)
		if (streqskip(count_keywords[i].keyword)) {
		    out->scaleCount = count_keywords[i].scale;
		    dimension = d_count;
		    break;
		}

	/* parse optional dimension exponent */
	switch (dimension) {
	    case d_none:
		*errMsg = strdup("unrecognized or duplicate base unit");
		sts = PM_ERR_CONV;
		goto out;

	    case d_time:
		if (ptr == str_end || isspace((int)(*ptr))) {
		    out->dimTime = 1;
		}
		else {
		    for (i = 0; i < num_exponent_keywords; i++)
			if (streqskip(exponent_keywords[i].keyword)) {
			    out->dimTime = exponent_keywords[i].scale;
			    break;
			}
		}
		break;

	    case d_space:
		if (ptr == str_end || isspace((int)(*ptr))) {
		    out->dimSpace = 1;
		}
		else {
		    for (i = 0; i < num_exponent_keywords; i++)
			if (streqskip(exponent_keywords[i].keyword)) {
			    out->dimSpace = exponent_keywords[i].scale;
			    break;
			}
		}
		break;

	    case d_count:
		if (ptr == str_end || isspace((int)(*ptr))) {
		    out->dimCount = 1;
		}
		else {
		    for (i = 0; i < num_exponent_keywords; i++)
			if (streqskip(exponent_keywords[i].keyword)) {
			    out->dimCount = exponent_keywords[i].scale;
			    break;
			}
		}
		break;
	}

	/* fall through to next unit^exponent bit, if any */
    }

out:
    return sts;
}

/* Parse a general "N $units / M $units" string into a pmUnits tuple and a multiplier. */
int
pmParseUnitsStr(const char *str, pmUnits *out, double *multiplier, char **errMsg)
{
    const char *slash;
    double dividend_mult, divisor_mult;
    __pmUnits dividend, divisor;
    int dim, sts;

    memset(out, 0, sizeof(*out));

    /* Parse the dividend and divisor separately */
    slash = strchr(str, '/');
    if (slash == NULL) {
	sts = __pmParseUnitsStrPart(str, str + strlen(str), &dividend, &dividend_mult, errMsg);
	if (sts < 0)
	    goto out;
	/* empty string for nonexistent denominator */
	memset(&divisor, 0, sizeof(divisor));
	divisor_mult = 1.0;
    }
    else {
	sts = __pmParseUnitsStrPart(str, slash, &dividend, &dividend_mult, errMsg);
	if (sts < 0)
	    goto out;
	sts = __pmParseUnitsStrPart(slash + 1, str + strlen(str), &divisor, &divisor_mult, errMsg);
	if (sts < 0)
	    goto out;
    }

    /* Compute the quotient dimensionality, check for possible bitfield overflow. */
    dim = dividend.dimSpace - divisor.dimSpace;
    out->dimSpace = dim;	/* might get truncated */
    if (out->dimSpace != dim) {
	*errMsg = strdup("space dimension out of bounds");
	sts = PM_ERR_CONV;
	goto out;
    }
    dim = dividend.dimTime - divisor.dimTime;
    out->dimTime = dim;		/* might get truncated */
    if (out->dimTime != dim) {
	*errMsg = strdup("time dimension out of bounds");
	sts = PM_ERR_CONV;
	goto out;
    }
    dim = dividend.dimCount - divisor.dimCount;
    out->dimCount = dim;	/* might get truncated */
    if (out->dimCount != dim) {
	*errMsg = strdup("count dimension out of bounds");
	sts = PM_ERR_CONV;
	goto out;
    }

    /*
     * Compute the individual scales.  In theory, we have considerable
     * freedom here, because we are also outputting a multiplier.  We
     * could just set all out->scale* to 0, and accumulate the
     * compensating scaling multipliers there.  But in order to
     * fulfill the testing-oriented invariant:
     *
     * for all valid pmUnits u:
     *     pmParseUnitsStr(pmUnitsStr(u), out_u, out_multiplier) succeeds, and
     *     out_u == u, and
     *     out_multiplier == 1.0
     *
     * we need to propagate scales to some extent.  It is helpful to
     * realize that pmUnitsStr() never generates multiplier literals,
     *  nor the same dimension in the dividend and divisor.
     */

    *multiplier = divisor_mult / dividend_mult;	/* NB: note reciprocation */

    if (dividend.dimSpace == 0 && divisor.dimSpace != 0)
	out->scaleSpace = divisor.scaleSpace;
    else if (divisor.dimSpace == 0 && dividend.dimSpace != 0)
	out->scaleSpace = dividend.scaleSpace;
    else {			/* both have space dimension; must compute a scale/multiplier */
	out->scaleSpace = PM_SPACE_BYTE;
	*multiplier *= pow(pow(1024.0, (double) dividend.scaleSpace), -(double) dividend.dimSpace);
	*multiplier *= pow(pow(1024.0, (double) divisor.scaleSpace), (double) divisor.dimSpace);
	if (out->dimSpace == 0)	/* became dimensionless? */
	    out->scaleSpace = 0;
    }

    if (dividend.dimCount == 0 && divisor.dimCount != 0)
	out->scaleCount = divisor.scaleCount;
    else if (divisor.dimCount == 0 && dividend.dimCount != 0)
	out->scaleCount = dividend.scaleCount;
    else {			/* both have count dimension; must compute a scale/multiplier */
	out->scaleCount = PM_COUNT_ONE;
	*multiplier *= pow(pow(10.0, (double) dividend.scaleCount), -(double) dividend.dimCount);
	*multiplier *= pow(pow(10.0, (double) divisor.scaleCount), (double) divisor.dimCount);
	if (out->dimCount == 0)	/* became dimensionless? */
	    out->scaleCount = 0;
    }

    if (dividend.dimTime == 0 && divisor.dimTime != 0)
	out->scaleTime = divisor.scaleTime;
    else if (divisor.dimTime == 0 && dividend.dimTime != 0)
	out->scaleTime = dividend.scaleTime;
    else {			/* both have time dimension; must compute a scale/multiplier */
	out->scaleTime = PM_TIME_SEC;
	static const double time_scales[] = {[PM_TIME_NSEC] = 0.000000001,
	    [PM_TIME_USEC] = 0.000001,
	    [PM_TIME_MSEC] = 0.001,
	    [PM_TIME_SEC] = 1,
	    [PM_TIME_MIN] = 60,
	    [PM_TIME_HOUR] = 3600
	};
	/* guaranteed by __pmParseUnitsStrPart; ensure in-range array access */
	assert(dividend.scaleTime <= PM_TIME_HOUR);
	assert(divisor.scaleTime <= PM_TIME_HOUR);
	*multiplier *= pow(time_scales[dividend.scaleTime], -(double) dividend.dimTime);
	*multiplier *= pow(time_scales[divisor.scaleTime], (double) divisor.dimTime);
	if (out->dimTime == 0)	/* became dimensionless? */
	    out->scaleTime = 0;
    }

out:
    if (sts < 0) {
	memset(out, 0, sizeof(*out));	/* clear partially filled in pmUnits */
	*multiplier = 1.0;
    }
    return sts;
}
