python - 将 Python 浮点数传递给 C 函数时舍入模式不一致
问题描述
我对 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
。
其次,如果我x=1e-6
在python
脚本中定义,然后使用ctype
传递x
给这个 C 函数。结果是相反的,即FE_UPWARD
returns9.999999975e-07
和FE_DOWNWARD
returns 1.000000111e-06
。
所以,我总共有2个问题:
对于第一个观察,为什么它们都小于
1e-6
?至于第二个观察,为什么这两个值的关系是相反的?
非常感谢你!
解决方案
对于您的第一个问题,浮点二进制中的 1e-6 是一个重复分数,因此会丢失精度。单精度浮点为尾数保留 23 位,其余的被丢弃,最低有效位被四舍五入,因此当写出更精确的数字时,数字可能会显得更小或更大。23 位转换为大约 7 位的十进制精度。
对于第二个问题,更改舍入模式是粘性的,并且在调用 C++ 函数后不会恢复。它也会影响 Python 通过 ctypes 将 Python float 转换为 C float。将代码更改为以下以保存并恢复原始模式,您将获得一致性:
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 双精度)到 C 浮点转换期间舍入不同。y
浮点数总是0x358637BD
读取。
推荐阅读
- unit-testing - 对我的 ViewModel 进行单元测试时,列表总是返回空
- javascript - 如何使用反应钩子重新渲染组件
- c# - 使用依赖注入通过构造函数初始化值
- visual-studio-2010 - GetReferenceNearestTargetFramwork 任务意外失败。无效的框架 V4.0
- hadoop - aws emr 上的 YARN 日志聚合 - 期望一个容器文件,但仍然找到多个
- python-3.x - 无论我使用 tweepy 搜索什么关键字,我的 python 脚本都会输出“无坐标”
- babeljs - NPX 真的需要 NPM 预装 babel 组件才能运行吗?
- django-class-based-views - 如何在 DetailView 中使用通用关系
- c# - 发现差异
- openapi - OpenAPI:如何表示一个可以做所有事情的端点?