原理
double 由以下部分组成:
- 符号位
- 指数部分
- 尾数部分
- 符号位的含义:为 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