OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / atoms / value / Float32Value.cc

318 lines · cc ref:
/*
 * opencog/atoms/value/Float32Value.cc
 *
 * Copyright (C) 2025 BrainyBlaze Dynamics Inc.
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License v3 as
 * published by the Free Software Foundation and including the exceptions
 * at http://opencog.org/wiki/Licenses
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, write to:
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <opencog/util/exceptions.h>
#include <opencog/atoms/value/Float32Value.h>
#include <opencog/atoms/value/ValueFactory.h>

using namespace opencog;

ValuePtr Float32Value::incrementCount(const std::vector<float>& v) const
{
	// Make a copy
	std::vector<float> new_vect = _value;

	// Increase size to fit.
	if (new_vect.size() < v.size())
		new_vect.resize(v.size(), 0.0f);

	// Increment
	for (size_t idx=0; idx < new_vect.size(); idx++)
		new_vect[idx] += v[idx];

	// Return a brand new value of the same type.
	return valueserver().create(_type, std::move(new_vect));
}

bool Float32Value::operator==(const Value& other) const
{
	// Unlike Atoms, we are willing to compare other types, as long
	// as the type hierarchy makes sense, and the values compare.
	// XXX Except that, currently, we do not compare to the 64-bit doubles.
	// Perhaps we should, someday.
	if (not other.is_type(FLOAT32_VALUE)) return false;

	const Float32Value* fov = (const Float32Value*) &other;
	if (_value.size() != fov->_value.size()) return false;
	size_t len = _value.size();
	for (size_t i=0; i<len; i++)
	{
		// Compare floats with ULPS, because they are lexicographically
		// ordered. For technical explanation, see
		// http://www.cygnus-software.com/papers/comparingfloats/Comparing%20floating%20point%20numbers.htm

		// For 32-bit floats, we use int32_t instead of int64_t
		int32_t self = *(int32_t*) &(_value[i]);
		int32_t other= *(int32_t*) &(fov->_value[i]);
		int32_t lili = self - other;
		if (0L > lili) lili = -lili;
		uint32_t lulu = (uint32_t) lili;

		// For 32-bit floats, we use a smaller ULPS threshold
		// Since we have less precision, we need fewer ULPS
		// 12 ULPS is approximately one part in 1e-6
#define MAX_ULPS_32 12UL
		if (MAX_ULPS_32 < lulu) return false;
	}
	return true;
}

// ==============================================================

std::string Float32Value::to_string(const std::string& indent, Type t) const
{
	std::string rv = indent + "(" + nameserver().getTypeName(t);
	SAFE_UPDATE(rv,
	{
		for (float v :_value)
		{
			char buf[40];
			// 32-bit floats have a 23-bit mantissa, so about 7.2 bits
			// of precision. Round up and print 8 decimal places.
			snprintf(buf, 40, "%.8g", v);
			rv += std::string(" ") + buf;
		}
	});

	rv += ")";
	return rv;
}

// ==============================================================

/// Scalar addition
std::vector<float> opencog::plus(float scalar, const std::vector<float>& fv)
{
	size_t len = fv.size();
	std::vector<float> sum(len);
	for (size_t i=0; i<len; i++)
		sum[i] = scalar + fv[i];

	return sum;
}

/// Scalar subtraction
std::vector<float> opencog::minus(float scalar, const std::vector<float>& fv)
{
	size_t len = fv.size();
	std::vector<float> diff(len);
	for (size_t i=0; i<len; i++)
		diff[i] = scalar - fv[i];

	return diff;
}

std::vector<float> opencog::minus(const std::vector<float>& fv, float scalar)
{
	size_t len = fv.size();
	std::vector<float> diff(len);
	for (size_t i=0; i<len; i++)
		diff[i] = fv[i] - scalar;

	return diff;
}

/// Scalar multiplication
std::vector<float> opencog::times(float scalar, const std::vector<float>& fv)
{
	size_t len = fv.size();
	std::vector<float> prod(len);
	for (size_t i=0; i<len; i++)
		prod[i] = scalar * fv[i];

	return prod;
}

/// Scalar division
std::vector<float> opencog::divide(float scalar, const std::vector<float>& fv)
{
	size_t len = fv.size();
	std::vector<float> ratio(len);
	for (size_t i=0; i<len; i++)
		ratio[i] = scalar / fv[i];

	return ratio;
}

/// Vector (point-wise) addition
/// The shorter vector is assumed to be zero-padded.
std::vector<float> opencog::plus(const std::vector<float>& fva,
                                  const std::vector<float>& fvb)
{
	size_t lena = fva.size();
	size_t lenb = fvb.size();

	if (1 == lena)
		return plus(fva[0], fvb);

	if (1 == lenb)
		return plus(fvb[0], fva);

	std::vector<float> sum(std::max(lena, lenb));
	if (lena < lenb)
	{
		size_t i=0;
		for (; i<lena; i++)
			sum[i] = fva[i] + fvb[i];
		for (; i<lenb; i++)
			sum[i] = fvb[i];
	}
	else
	{
		size_t i=0;
		for (; i<lenb; i++)
			sum[i] = fva[i] + fvb[i];
		for (; i<lena; i++)
			sum[i] = fva[i];
	}
	return sum;
}

/// Vector (point-wise) subtraction
/// The shorter vector is assumed to be zero-padded.
std::vector<float> opencog::minus(const std::vector<float>& fva,
                                   const std::vector<float>& fvb)
{
	size_t lena = fva.size();
	size_t lenb = fvb.size();

	if (1 == lena)
		return minus(fva[0], fvb);

	if (1 == lenb)
		return minus(fva, fvb[0]);

	std::vector<float> diff(std::max(lena, lenb));
	if (lena < lenb)
	{
		size_t i=0;
		for (; i<lena; i++)
			diff[i] = fva[i] - fvb[i];
		for (; i<lenb; i++)
			diff[i] = -fvb[i];
	}
	else
	{
		size_t i=0;
		for (; i<lenb; i++)
			diff[i] = fva[i] - fvb[i];
		for (; i<lena; i++)
			diff[i] = fva[i];
	}
	return diff;
}

/// Vector (point-wise) multiplication
/// The shorter vector is assumed to be one-padded.
/// Unless the shorter vector is a scalar, in which case we do scalar
/// multiplication. This is the "right thing to do", because that
/// is the general user intent.  We could detect this case in all
/// the callers to this routine, or we could just handle it here.
/// This may seem messy to you, but this is the easiest solution.
std::vector<float> opencog::times(const std::vector<float>& fva,
                                   const std::vector<float>& fvb)
{
	size_t lena = fva.size();
	size_t lenb = fvb.size();

	std::vector<float> prod(std::max(lena, lenb));
	if (1 == lena)
	{
		float f = fva[0];
		for (size_t i=0; i<lenb; i++)
			prod[i] = f * fvb[i];
	}
	else
	if (1 == lenb)
	{
		float f = fvb[0];
		for (size_t i=0; i<lena; i++)
			prod[i] = f * fva[i];
	}
	else
	if (lena < lenb)
	{
		size_t i=0;
		for (; i<lena; i++)
			prod[i] = fva[i] * fvb[i];
		for (; i<lenb; i++)
			prod[i] = fvb[i];
	}
	else
	{
		size_t i=0;
		for (; i<lenb; i++)
			prod[i] = fva[i] * fvb[i];
		for (; i<lena; i++)
			prod[i] = fva[i];
	}
	return prod;
}

/// Vector (point-wise) division
/// The shorter vector is assumed to be one-padded.
/// If the shorter vector has length one, assume its a scalar.
/// See comments on times() above about scalars.
std::vector<float> opencog::divide(const std::vector<float>& fva,
                                    const std::vector<float>& fvb)
{
	size_t lena = fva.size();
	size_t lenb = fvb.size();

	std::vector<float> ratio(std::max(lena, lenb));
	if (1 == lena)
	{
		float f = fva[0];
		for (size_t i=0; i<lenb; i++)
			ratio[i] = f / fvb[i];
	}
	else
	if (1 == lenb)
	{
		float f = fvb[0];
		for (size_t i=0; i<lena; i++)
			ratio[i] = fva[i] / f;
	}
	else
	if (lena < lenb)
	{
		size_t i=0;
		for (; i<lena; i++)
			ratio[i] = fva[i] / fvb[i];
		for (; i<lenb; i++)
			ratio[i] = 1.0f / fvb[i];
	}
	else
	{
		size_t i=0;
		for (; i<lenb; i++)
			ratio[i] = fva[i] / fvb[i];
		for (; i<lena; i++)
			ratio[i] = fva[i];
	}
	return ratio;
}

// Adds factory when the library is loaded.
DEFINE_VALUE_FACTORY(FLOAT32_VALUE,
                     createFloat32Value, std::vector<float>)