原理
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 的位結構
#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)){// 指數位全為 1if (MantissaBits() != 0){// 尾數位不全為 0,return base::bit::FloatNumberValueType::NaN;}// 尾數位全為 0if (!SignBit()){// 正無窮return base::bit::FloatNumberValueType::PositiveInfinite;}// 負無窮return base::bit::FloatNumberValueType::NegativeInfinite;}// 指數位不全為 1if (ExponentBits() == 0){// 指數位全為 0return 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 的位結構之后,分數類就可以基于它進行構造了
base::Fraction::Fraction(base::Double const &value)
{if (value.Value() == 0.0){SetNum(0);SetDen(1);return;}base::bit::DoubleBitView view{value.Value()};switch (view.ValueType()){case base::bit::FloatNumberValueType::Normalized:{base::Fraction f1 = base::pow(base::BigInteger{2},base::BigInteger{view.ExponentBits() - 1023});base::Fraction f2 = base::Fraction{view.MantissaBits(),base::pow(base::BigInteger{2}, base::BigInteger{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::pow(base::BigInteger{2},base::BigInteger{-1022});base::Fraction f2 = base::Fraction{view.MantissaBits(),base::pow(base::BigInteger{2}, base::BigInteger{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 π 常數轉換為分數
#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;}
}
運行結果
分數: 884279719003555 / 281474976710656
std::numbers::pi: 3.141592653589793115997963468544185161590576171875
分數表示的 pi 轉為 double: 3.141592653589793115997963468544185161590576171875
誤差: 0
π \pi π 用分數近似表示就是 884279719003555 / 281474976710656