定点小数与分数

8 位定点小数

8 位有符号整型的值域是 [-128, 127] , 8 位定点小数就是将这个整型视为一个分数的分子,这个分数的分母是 21282^{128}2128 。

这样这个分数的值域就是:

−1,127128\] \[-1, \\frac{127}{128}\] \[−1,128127

于是就可以表示小于 1 的数了。

分辨率为:
128−1=127=1128 \frac{1}{2^{8-1}}=\frac{1}{2^7}=\frac{1}{128} 28−11=271=1281

所以 8 位定点小数本质上就是一个整型值 int8_t, 只不过计算的时候要记得除以 128, 这样才能得到实际值。

定点小数与分数类

在 C++ 中可以直接设计一个分数类

cpp 复制代码
	///
	/// @brief 分数类
	///
	class Int64Fraction final :
		public base::ICanToString
	{
	private:
		int64_t _num = 0;
		int64_t _den = 1;

	public:
		/* #region 构造函数 */

		///
		/// @brief 默认构造,分子为 0,分母为 1.
		///
		constexpr Int64Fraction() = default;

		///
		/// @brief 从整型值构造。分子为 num, 分母为 1.
		///
		/// @param num 分子。
		///
		template <typename T>
			requires(std::is_integral_v<T>)
		constexpr Int64Fraction(T num)
		{
			_num = num;
			_den = 1;
		}

		///
		/// @brief 通过分子,分母进行构造。
		/// @param num 分子
		/// @param den 分母
		///
		constexpr Int64Fraction(int64_t num, int64_t den)
		{
			SetNum(num);
			if (num == 0)
			{
				SetDen(1);
			}
			else
			{
				SetDen(den);
			}
		}

		///
		/// @brief 通过浮点数构造。
		///
		/// @param value
		///
		constexpr Int64Fraction(base::Double const &value)
		{
			if (value.Value() == 0.0)
			{
				SetNum(0);
				SetDen(1);
				return;
			}

			double db = value.Value();

			// 要保证分数计算过程不溢出,需要保证 factor * db <= INT64_MAX.
			int64_t factor = INT64_MAX / base::ceil(db);

			base::Int64Fraction int_part{static_cast<int64_t>(db)};
			db -= static_cast<double>(int_part);

			db *= factor;
			base::Int64Fraction fractional_part{static_cast<int64_t>(db), factor};

			*this += int_part + fractional_part;
		}

		/* #endregion */

		/* #region 分子分母 */

		///
		/// @brief 获取分子。
		///
		/// @return
		///
		constexpr int64_t Num() const
		{
			return _num;
		}

		///
		/// @brief 设置分子。
		///
		/// @param value
		///
		constexpr void SetNum(int64_t value)
		{
			_num = value;
		}

		///
		/// @brief 获取分母。
		///
		/// @return
		///
		constexpr int64_t Den() const
		{
			return _den;
		}

		///
		/// @brief 设置分母。
		///
		/// @param value
		///
		constexpr void SetDen(int64_t value)
		{
			if (value == 0)
			{
				throw std::invalid_argument{"分母不能为 0."};
			}

			_den = value;
		}

		/* #endregion */

		/* #region 计算函数 */

		///
		/// @brief 化简自身。
		///
		///
		constexpr void Simplify()
		{
			if (_den == 0)
			{
				throw std::invalid_argument{"分母不能为 0."};
			}

			if (_num == 0)
			{
				_den = 1;
				return;
			}

			// 分子分母同时除以最大公约数
			int64_t gcd_value = std::gcd(_num, _den);
			int64_t scaled_num = _num / gcd_value;
			int64_t scaled_den = _den / gcd_value;

			if (scaled_den < 0)
			{
				// 如果分母小于 0,分子分母同时取相反数
				scaled_num = -scaled_num;
				scaled_den = -scaled_den;
			}

			_num = scaled_num;
			_den = scaled_den;
		}

		///
		/// @brief 化简后的形式。
		///
		/// @note 返回化简后的值,不改变自身。
		///
		/// @return
		///
		constexpr Int64Fraction SimplifiedForm() const
		{
			base::Int64Fraction ret{*this};
			ret.Simplify();
			return ret;
		}

		///
		/// @brief 倒数
		/// @return
		///
		constexpr Int64Fraction Reciprocal() const
		{
			base::Int64Fraction ret{_den, _num};
			return ret.SimplifiedForm();
		}

		///
		/// @brief 取绝对值。
		///
		/// @return
		///
		constexpr Int64Fraction Abs() const
		{
			if (*this < 0)
			{
				return -*this;
			}

			return *this;
		}

		///
		/// @brief 向下取整
		/// @return
		///
		constexpr int64_t Floor() const
		{
			int64_t ret = Div();
			if (*this < 0)
			{
				if (Mod())
				{
					ret -= 1;
				}
			}
			else
			{
				/* 因为 C++ 除法近 0 截断,所以如果 Div >0 ,本来就是向下取整了,
				 * 不用再额外的操作了。
				 *
				 * Div = 0 就更不用说了,也不用什么额外的操作,直接返回 0 就完事了。
				 */
			}

			return ret;
		}

		///
		/// @brief 向上取整
		/// @return
		///
		constexpr int64_t Ceil() const
		{
			int64_t ret = Div();
			if (*this > 0)
			{
				if (Mod())
				{
					ret += 1;
				}
			}

			return ret;
		}

		///
		/// @brief 获取分子除以分母的值
		/// @return
		///
		constexpr int64_t Div() const
		{
			return _num / _den;
		}

		///
		/// @brief 获取分子除以分母的余数
		/// @return
		///
		constexpr int64_t Mod() const
		{
			return _num % _den;
		}

		///
		/// @brief 降低分辨率。
		///
		/// @param resolution
		///
		constexpr void ReduceResolution(base::Int64Fraction const &resolution)
		{
			if (resolution <= 0)
			{
				throw std::invalid_argument{CODE_POS_STR + "分辨率不能 <= 0."};
			}

			Simplify();
			if (_den >= resolution._den)
			{
				// 本分数的分母比 resolution 的分母大,说明本分数的分辨率大于 resolution.
				//
				// 首先需要减小本分数的分母,将分辨率降下来。分子分母同时除以一个系数进行截断,
				// 从而降低分辨率。
				int64_t multiple = _den / resolution._den;

				// 首先将分辨率降低到 1 / resolution._den.
				_num /= multiple;
				_den /= multiple;

				// 如果 resolution._num > 1, 则还不够,刚才的分辨率降低到 1 / resolution._den 了,
				// 还要继续降低。
				_num = _num / resolution._num * resolution._num;
			}
			else
			{
				// 本分数的分母比 resolution 的分母小。但这不能说明本分数的分辨率小于 resolution,
				// 因为 resolution 的分子可能较大。
				//
				// 将 resolution 的分子分母同时除以一个系数,将 resolution 的分母调整到与本分数的分母
				// 相等,然后看一下调整后的 resolution 的分子,如果不等于 0, 即没有被截断成 0, 说明原本的
				// 分子确实较大,大到足以放大 resolution 的大分母所导致的小步长,导致步长很大,分辨率低。
				// 这种情况下本分数的分辨率才是高于 resolution, 才需要降低分辨率。
				int64_t multiple = resolution._den / _den;
				int64_t div = resolution._num / multiple;
				if (div != 0)
				{
					_num = _num / div * div;
				}
			}

			Simplify();
		}

		/* #endregion */

		constexpr Int64Fraction operator-() const
		{
			Int64Fraction ret{-_num, _den};
			return ret.SimplifiedForm();
		}

		/* #region 四则运算符 */

		constexpr Int64Fraction operator+(Int64Fraction const &value) const
		{
			// 通分后的分母为本对象的分母和 value 的分母的最小公倍数
			int64_t scaled_den = std::lcm(_den, value.Den());

			// 通分后的分子为本对象的分子乘上分母所乘的倍数
			int64_t scaled_num = _num * (scaled_den / _den);
			int64_t value_scaled_num = value.Num() * (scaled_den / value.Den());

			Int64Fraction ret{
				scaled_num + value_scaled_num,
				scaled_den,
			};

			return ret.SimplifiedForm();
		}

		constexpr Int64Fraction operator-(Int64Fraction const &value) const
		{
			Int64Fraction ret = *this + (-value);
			return ret.SimplifiedForm();
		}

		constexpr Int64Fraction operator*(Int64Fraction const &value) const
		{
			base::Int64Fraction ret;
			ret.SetNum(_num * value.Num());
			ret.SetDen(_den * value.Den());
			return ret.SimplifiedForm();
		}

		constexpr Int64Fraction operator/(Int64Fraction const &value) const
		{
			Int64Fraction ret{*this * value.Reciprocal()};
			return ret.SimplifiedForm();
		}

		/* #endregion */

		/* #region 自改变四则运算符 */

		constexpr Int64Fraction &operator+=(Int64Fraction const &value)
		{
			*this = *this + value;
			return *this;
		}

		constexpr Int64Fraction &operator-=(Int64Fraction const &value)
		{
			*this = *this - value;
			return *this;
		}

		constexpr Int64Fraction &operator*=(Int64Fraction const &value)
		{
			*this = *this * value;
			return *this;
		}

		constexpr Int64Fraction &operator/=(Int64Fraction const &value)
		{
			*this = *this / value;
			return *this;
		}

		/* #endregion */

		///
		/// @brief 将分数转化为字符串
		/// @return
		///
		virtual std::string ToString() const override
		{
			return std::to_string(_num) + " / " + std::to_string(_den);
		}

		/* #region 强制转换运算符 */

		constexpr explicit operator int64_t() const
		{
			return Div();
		}

		constexpr explicit operator uint64_t() const
		{
			return static_cast<uint64_t>(Div());
		}

		constexpr explicit operator int32_t() const
		{
			return static_cast<int32_t>(Div());
		}

		constexpr explicit operator uint32_t() const
		{
			return static_cast<uint32_t>(Div());
		}

		constexpr explicit operator int16_t() const
		{
			return static_cast<int16_t>(Div());
		}

		constexpr explicit operator uint16_t() const
		{
			return static_cast<uint16_t>(Div());
		}

		constexpr explicit operator int8_t() const
		{
			return static_cast<int8_t>(Div());
		}

		constexpr explicit operator uint8_t() const
		{
			return static_cast<uint8_t>(Div());
		}

		constexpr explicit operator double() const
		{
			base::Int64Fraction copy{*this};
			double int_part = static_cast<double>(copy.Div());
			copy -= copy.Div();
			double fraction_part = static_cast<double>(copy.Num()) / static_cast<double>(copy.Den());
			return int_part + fraction_part;
		}

		/* #endregion */

		/* #region 比较 */

		///
		/// @brief 本对象等于 another.
		/// @param another
		/// @return
		///
		constexpr bool operator==(Int64Fraction const &another) const
		{
			if (Num() == 0 && another.Num() == 0)
			{
				/* 2 个分子都为 0 直接返回相等,这样更加安全,避免分子都为 0
				 * 分母不相等时错误地将两个分数判断为不相等。
				 */
				return true;
			}

			Int64Fraction f1 = SimplifiedForm();
			Int64Fraction f2 = another.SimplifiedForm();
			return f1.Num() == f2.Num() && f1.Den() == f2.Den();
		}

		///
		/// @brief 本对象大于 another.
		/// @param another
		/// @return
		///
		constexpr bool operator>(Int64Fraction const &another) const
		{
			// 先化简,避免分母为负数,然后使用交叉乘法比大小。
			Int64Fraction f1 = SimplifiedForm();
			Int64Fraction f2 = another.SimplifiedForm();
			int64_t num1{f1.Num()};
			int64_t den1{f1.Den()};
			int64_t num2{f2.Num()};
			int64_t den2{f2.Den()};
			return num1 * den2 > num2 * den1;
		}

		///
		/// @brief 本对象小于 another.
		/// @param another
		/// @return
		///
		constexpr bool operator<(Int64Fraction const &another) const
		{
			// 先化简,避免分母为负数,然后使用交叉乘法比大小。
			Int64Fraction f1 = SimplifiedForm();
			Int64Fraction f2 = another.SimplifiedForm();
			int64_t num1{f1.Num()};
			int64_t den1{f1.Den()};
			int64_t num2{f2.Num()};
			int64_t den2{f2.Den()};
			return num1 * den2 < num2 * den1;
		}

		///
		/// @brief 本对象大于等于 another.
		///
		/// @param another
		/// @return true
		/// @return false
		///
		constexpr bool operator>=(Int64Fraction const &another) const
		{
			if (*this == another)
			{
				return true;
			}

			if (*this > another)
			{
				return true;
			}

			return false;
		}

		///
		/// @brief 本对象小于等于 another.
		///
		/// @param another
		/// @return true
		/// @return false
		///
		constexpr bool operator<=(Int64Fraction const &another) const
		{
			if (*this == another)
			{
				return true;
			}

			if (*this < another)
			{
				return true;
			}

			return false;
		}

		/* #endregion */
	};

当一个 int8_t 变量 a 被视为 8 位定点小数,用它来初始化这个分数时就是

cpp 复制代码
base::Int64Fraction frac{a, 128};

即把这个 int8_t 变量 a 作为分数的分子,128 作为分母,得到的分数值就是这个定点小数要表示的值。

视为每一位的权重发生改变了

N 位定点小数

8 位定点小数可以看作是分子是 8 位有符号整型,分母是 28−1=27=1282^{8-1} =2^7=12828−1=27=128 的分数,则 N 位定点小数可以看作是分子是 N 位有符号整型,分母是 2N−12^{N-1}2N−1 的分数。

值域为:

−1,2N−1−12N−1\] \[-1, \\frac{2\^{N-1}-1}{2\^N-1}\] \[−1,2N−12N−1−1

分辨率为:
12N−1 \frac{1}{2^{N-1}} 2N−11

相关的软考例题


例12 的那些选项应该是打错了,实际上这些选项想要表示的应该是 2 的幂,只不过 2 右边的内容没有往 2 的右上角偏,而是错误地与 2 平齐了。

上文说定点小数实际上就是一个补码形式的有符号整型,只不过你要当作它有一个分母存在。N 位的整型共有 2N2^N2N 种不同的值,所以被解释成定点小数后也是有这么多个值,所以选 A.

例13 所谓的定点整数其实就是有符号整型。所以选 B.

相关推荐
小苏兮2 小时前
【C语言】字符串与字符函数详解(上)
c语言·开发语言·算法
一只小蒟蒻2 小时前
DFS 迷宫问题 难度:★★★★☆
算法·深度优先·dfs·最短路·迷宫问题·找过程
martian6652 小时前
深入详解随机森林在眼科影像分析中的应用及实现细节
人工智能·算法·随机森林·机器学习·医学影像
apocelipes3 小时前
使用uint64_t批量比较短字符串
c语言·数据结构·c++·算法·性能优化·golang
一只IT攻城狮3 小时前
构建一个简单的Java框架来测量并发执行任务的时间
java·算法·多线程·并发编程
WanderInk3 小时前
在递归中为什么用 `int[]` 而不是 `int`?——揭秘 Java 参数传递的秘密
java·后端·算法
ai小鬼头4 小时前
创业心态崩了?熊哥教你用缺德哲学活得更爽
前端·后端·算法
拾光拾趣录4 小时前
算法 | 下一个更大的排列
前端·算法
熬了夜的程序员4 小时前
【华为机试】122. 买卖股票的最佳时机 II
开发语言·算法·华为·面试·golang
qqxhb4 小时前
零基础数据结构与算法——第五章:高级算法-动态规划经典-背包问题
算法·动态规划·完全背包·编辑距离·0-1背包