将浮点数转换为分数

原理

double 由以下部分组成:

  1. 符号位
  2. 指数部分
  3. 尾数部分
  • 符号位的含义:为 0 表示正数,为 1 表示负数。
  • 指数部分的含义:在规格化数中,指数部分的整型值减去 1023 就是实际的指数值。在非规格化数中,指数恒为 -1022 这个常数。
  • 尾数部分:尾数部分的整型值就是它的值,不像指数部分那样需要分别处理。

规格化数的值:
value = ( − 1 ) sign × 2 exponent − 1023 × ( 1 + mantissa 2 52 ) \text{value} = (-1)^{\text{sign}} \times 2^{\text{exponent} - 1023} \times \left(1 + \frac{\text{mantissa}}{2^{52}}\right) value=(−1)sign×2exponent−1023×(1+252mantissa)

非规格化数的值:
value = ( − 1 ) sign × 2 − 1022 × ( mantissa 2 52 ) \text{value} = (-1)^{\text{sign}} \times 2^{-1022} \times \left(\frac{\text{mantissa}}{2^{52}}\right) value=(−1)sign×2−1022×(252mantissa)

浮点特殊值

NaN

  • 指数部分所有位都为 1.
  • 尾数部分不为 0.

正无穷、负无穷

  • 指数部分所有位都为 1.
  • 尾数部分所有位都为 0.

则根据符号位来确定是正无穷还是负无穷。符号位为 0 则是正无穷,符号位为 1 则是负无穷。

DoubleBitView

设计一个类,用来解析 double 的位结构

cpp 复制代码
#pragma once
#include "base/bit/bit.h"
#include "base/math/FloatNumberValueType.h"
#include <cstdint>

namespace base
{
	namespace bit
	{
		class DoubleBitView
		{
		private:
			union Union
			{
				double _double;
				uint64_t _uint64;
			};

			Union _value_union{};

		public:
			constexpr DoubleBitView() = default;

			constexpr DoubleBitView(double value)
			{
				_value_union._double = value;
			}

			constexpr double Value() const
			{
				return _value_union._double;
			}

			constexpr uint64_t AsUint64() const
			{
				return _value_union._uint64;
			}

			///
			/// @brief 尾数部分的位。
			///
			/// @return
			///
			constexpr uint64_t MantissaBits() const
			{
				return base::bit::ReadBits(_value_union._uint64, 0, 52);
			}

			///
			/// @brief 指数部分的位。
			///
			/// @return
			///
			constexpr uint64_t ExponentBits() const
			{
				return base::bit::ReadBits(_value_union._uint64, 52, 63);
			}

			///
			/// @brief 符号位。
			///
			/// @return
			///
			constexpr bool SignBit() const
			{
				return base::bit::ReadBit(_value_union._uint64, 63);
			}

			///
			/// @brief 浮点值的类型。
			///
			/// @return
			///
			constexpr base::bit::FloatNumberValueType ValueType() const
			{
				if (ExponentBits() == base::bit::ReadBits(UINT64_MAX, 52, 63))
				{
					// 指数位全为 1

					if (MantissaBits() != 0)
					{
						// 尾数位不全为 0,
						return base::bit::FloatNumberValueType::NaN;
					}

					// 尾数位全为 0

					if (!SignBit())
					{
						// 正无穷
						return base::bit::FloatNumberValueType::PositiveInfinite;
					}

					// 负无穷
					return base::bit::FloatNumberValueType::NegativeInfinite;
				}

				// 指数位不全为 1

				if (ExponentBits() == 0)
				{
					// 指数位全为 0
					return base::bit::FloatNumberValueType::Denormalized;
				}

				return base::bit::FloatNumberValueType::Normalized;
			}

			///
			/// @brief 是正数。
			///
			/// @return
			///
			constexpr bool Positive() const
			{
				// 符号位位 0 则是正数
				return !SignBit();
			}

			///
			/// @brief 是负数。
			///
			/// @return
			///
			constexpr bool Negative() const
			{
				// 符号位位 1 则是负数
				return SignBit();
			}
		};

	} // namespace bit
} // namespace base

分数类

有了 double 的位结构之后,分数类就可以基于它进行构造了

cpp 复制代码
base::Fraction::Fraction(base::Double const &value)
{
	if (value.Value() == 0.0)
	{
		_num = 0;
		_den = 1;
		return;
	}

	base::bit::DoubleBitView view{value.Value()};

	switch (view.ValueType())
	{
	case base::bit::FloatNumberValueType::Normalized:
		{
			base::Fraction f1{
				base::BigInteger{1} << view.ExponentBits(),
				base::BigInteger{1} << 1023,
			};

			base::Fraction f2 = base::Fraction{
				view.MantissaBits(),
				base::BigInteger{1} << 52,
			};

			base::Fraction value = f1 * (1 + f2);
			if (view.Positive())
			{
				*this = value;
			}
			else
			{
				*this = -value;
			}

			break;
		}
	case base::bit::FloatNumberValueType::Denormalized:
		{
			base::Fraction f1{
				base::BigInteger{2},
				base::BigInteger{1} << 1022,
			};

			base::Fraction f2 = base::Fraction{
				view.MantissaBits(),
				base::BigInteger{1} << 52,
			};

			base::Fraction value = f1 * f2;
			if (view.Positive())
			{
				*this = value;
			}
			else
			{
				*this = -value;
			}

			break;
		}
	case base::bit::FloatNumberValueType::NaN:
		{
			throw std::invalid_argument{CODE_POS_STR + "此浮点数是 NaN."};
		}
	case base::bit::FloatNumberValueType::PositiveInfinite:
		{
			throw std::invalid_argument{CODE_POS_STR + "此浮点数是正无穷。"};
		}
	case base::bit::FloatNumberValueType::NegativeInfinite:
		{
			throw std::invalid_argument{CODE_POS_STR + "此浮点数是负无穷。"};
		}
	default:
		{
			throw std::runtime_error{CODE_POS_STR + "非法的枚举值。"};
		}
	}
}

无损地将标准库中的 π \pi π 常数转换为分数

cpp 复制代码
#include "base/math/Fraction.h"
#include "base/wrapper/number-wrapper.h"
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <numbers>
#include <stdlib.h>

int main()
{
	{
		base::Fraction f{base::Double{std::numbers::pi}};
		constexpr int precision = 512;

		std::cout << "分数: " << f << std::endl;

		std::cout << "std::numbers::pi: \t\t"
				  << std::setprecision(precision)
				  << std::numbers::pi
				  << std::endl;

		std::cout << "分数表示的 pi 转为 double: \t"
				  << std::setprecision(precision)
				  << static_cast<double>(f)
				  << std::endl;

		std::cout << "误差: "
				  << std::setprecision(precision)
				  << static_cast<double>(f) - std::numbers::pi
				  << std::endl;
	}
}

运行结果

bash 复制代码
分数: 884279719003555 / 281474976710656
std::numbers::pi:               3.141592653589793115997963468544185161590576171875
分数表示的 pi 转为 double:      3.141592653589793115997963468544185161590576171875
误差: 0

π \pi π 用分数近似表示就是 884279719003555 / 281474976710656

相关推荐
牛油果子哥q6 分钟前
【C++ STL vector】C++ STL vector 终极精讲:动态数组底层原理、两倍扩容机制、迭代器失效、增删查改、性能剖析与工程避坑指南
开发语言·c++
为何创造硅基生物2 小时前
独占指针的创建std::make_unique 本身自带堆出现
c++
kyle~2 小时前
ROS 2 与 Isaac Sim 联合仿真(一)体系架构、环境选型与基础通信闭环
c++·机器人·nvidia·仿真·ros2
努力努力再努力wz3 小时前
【内存管理与高并发内存池系列】从 mmap 到 malloc:文件映射、匿名映射与 glibc 内存分配机制详解
linux·c语言·数据结构·数据库·c++·qt·链表
八解毒剂3 小时前
数据结构-平衡二叉树——对二叉搜索树的优化
数据结构·c++·算法
起床困难户5753 小时前
条款20:协助完成返回值优化
c++
啦啦啦啦啦zzzz3 小时前
算法总结(二分查找、双指针)
c++·算法
不负岁月无痕5 小时前
C++ 模板核心内容与高频面试题汇总
java·开发语言·c++
无限进步_5 小时前
从零实现一个迷你Shell——深入理解Linux命令行解释器
linux·运维·服务器·开发语言·c++·chrome
fpcc6 小时前
工具使用——CMake中的函数和宏
c++·cmake