【问题标题】:Inconsistent Rounding Mode When Passing Python Float to C function将 Python 浮点数传递给 C 函数时舍入模式不一致
【发布时间】:2021-03-02 19:35:46
【问题描述】:

我对 C++ 中的舍入模式有疑问。我想更改舍入模式,看看我们是否可以得到给定变量的上限和下限。

通过使用以下代码运行一些玩具示例,我可以获得一些超级奇怪的观察结果。

#include <iostream>
#include <iomanip>
#include <string>
#include <cfenv>
#include <cmath>
void test_c(const bool rounding_up, const float x) 
{   
    #pragma STDC FENV_ACCESS ON
    float y = 1e-6;
    if (rounding_up){
        std::fesetround(FE_UPWARD);
        std::cout << std::setprecision(10) << "x_up_python   " << x << "\n";
        std::cout << std::setprecision(10) << "y_up_c++   " << y << "\n";
    }
    else{
        std::fesetround(FE_DOWNWARD);
        std::cout << std::setprecision(10) << "x_down_python " << x << "\n";
        std::cout << std::setprecision(10) << "y_down_c++ " << y << "\n";
    }
}
extern "C" {
    void test(const bool rounding_up, const float x){
        test_c(rounding_up, x);
        }
}

import os
import numpy as np
from numpy.ctypeslib import ndpointer

import ctypes
from ctypes import cdll
from ctypes import *

os.system('g++ -c -fPIC post_processing.cpp -o post_processing.o')
os.system('g++ -shared -Wl,-soname,lib_rounding.so -o lib_rounding.so post_processing.o')
lib = cdll.LoadLibrary('./lib_rounding.so')

def test(x, rounding_up=True):
    lib.test.restype = None
    lib.test.argtypes = [ctypes.c_bool, ctypes.c_float]
    lib.test(rounding_up, x)

if __name__ == '__main__':
    x = 1e-6
    test(x, True)
    test(x, False)

首先,我们来看看y。如果舍入模式设置为FE_UPWARD,则结果值为9.999999975e-07。相比之下,如果舍入模式设置为FE_DOWNWARD,则结果值为9.999999974e-07

其次,如果我在python 脚本中定义x=1e-6,然后使用ctypex 传递给这个C 函数。结果反过来,即FE_UPWARD返回9.999999975e-07FE_DOWNWARD返回1.000000111e-06

所以,我总共有 2 个问题:

  1. 对于第一个观察,为什么它们都小于1e-6

  2. 至于第二个观察,为什么这两个值的关系是相反的?

非常感谢!

【问题讨论】:

  • 请同时显示用于调用该函数的 Python 代码。
  • 嗨,我刚刚添加了 Python 代码和左侧的 C++ 代码。

标签: python c++ ctypes


【解决方案1】:

对于您的第一个问题,浮点二进制中的 1e-6 是一个重复分数,因此会丢失精度。单精度浮点为尾数保留 23 位,其余的被丢弃,最低有效位被四舍五入,因此当写出更精确的数字时,数字可能会显得更小或更大。 23 位转换为大约 7 位十进制精度。

对于第二个问题,更改舍入模式具有粘性,并且在调用 C++ 函数后不会恢复。它也会影响 Python 通过 ctypes 将 Python 浮点数转换为 C 浮点数。把代码改成下面这样保存和恢复原来的模式,你会得到一致性:

    float y = 1e-6;

    auto org = std::fegetround();  // Save original mode

    if (rounding_up){
        std::fesetround(FE_UPWARD);
        std::cout << std::setprecision(10) << "x_up_python   " << x << "\n";
        std::cout << std::setprecision(10) << "y_up_c++   " << y << "\n";
    }
    else{
        std::fesetround(FE_DOWNWARD);
        std::cout << std::setprecision(10) << "x_down_python " << x << "\n";
        std::cout << std::setprecision(10) << "y_down_c++ " << y << "\n";
    }

    std::fesetround(org);  // Restore original mode
x_up_python   9.999999975e-07
y_up_c++   9.999999975e-07
x_down_python 9.999999974e-07
y_down_c++ 9.999999974e-07

调试器中x 浮点数的 IEEE 十六进制转储在一种模式下显示0x358637BD,在另一种模式下显示0x358637BE,因此尾数(最后 23 位)在 Python 浮点数期间舍入不同(在 CPython 内部为C double) 到 C 浮点数的转换。 y float 始终读取 0x358637BD

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-23
    • 1970-01-01
    • 2022-12-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多