将Python浮点数传递给C函数时,舍入模式不一致 [英] Inconsistent Rounding Mode When Passing Python Float to C function
问题描述
我对C ++中的舍入模式有一个疑问.我想更改舍入模式,以查看是否可以获取给定变量的上限和下限.
通过使用以下代码运行一些玩具示例,我可以获得一些超级奇怪的发现.
#include< iostream>#include< iomanip>#include< string>#include< cfenv>#include< cmath>无效test_c(const bool rounding_up,const float x){#pragma STDC FENV_ACCESS ON浮点y = 1e-6;如果(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";}别的{std :: fesetround(FE_DOWNWARD);std :: cout<<std :: setprecision(10)<<" x_down_python"<<x<<"\ n";std :: cout<<std :: setprecision(10)<<" y_down_c ++"<<y<<"\ n";}}外部"C"{无效测试(const bool rounding_up,const float x){test_c(四舍五入,x);}}
import os将numpy导入为np从numpy.ctypeslib导入ndpointer导入ctypes从ctypes导入cdll从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')清晰度测试(x,rounding_up = True):lib.test.restype =无lib.test.argtypes = [ctypes.c_bool,ctypes.c_float]lib.test(四舍五入,x)如果__name__ =='__main__':x = 1e-6测试(x,True)测试(x,False)
首先,让我们看一下 y
.如果舍入模式设置为 FE_UPWARD
,则结果值为 9.999999975e-07
.相比之下,如果将舍入模式设置为 FE_DOWNWARD
,则结果值为 9.999999974e-07
.
第二,如果我在 python
脚本中定义 x = 1e-6
,然后使用 ctype
传递 x
到此C函数.结果相反,即 FE_UPWARD
返回 9.999999975e-07
,而 FE_DOWNWARD
返回 1.000000111e-06
.>
所以,我总共有2个问题:
-
第一次观察,为什么它们都小于
1e-6
? -
对于第二个观察,为什么这两个值的关系相反?
非常感谢!
对于您的第一个问题,浮点二进制文件中的1e-6是重复的分数,因此精度会损失.单精度浮点为尾数保留23位,其余的位被舍弃,最低有效位舍入,因此当以更高的精度写出时,数字可能会变小或变大.23位表示大约7位十进制精度.
对于第二个问题,更改舍入模式很麻烦,并且在调用C ++函数后无法恢复.它也会影响通过ctypes将Python float转换为C float的Python转换.将代码更改为以下代码以保存和恢复原始模式,您将获得一致性:
float y = 1e-6;自动组织= std :: fegetround();//保存原始模式如果(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";}别的{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);//恢复原始模式
x_up_python 9.999999975e-07y_up_c ++ 9.999999975e-07x_down_python 9.999999974e-07y_down_c ++ 9.999999974e-07
调试器中 x
浮点数的IEEE十六进制转储在一种模式下显示 0x358637BD
,在另一种模式下显示 0x358637BE
,因此尾数(最后一个)23位)在Python浮点数(在CPython中内部为C double)到C浮点数转换的舍入过程中有所不同. y
浮点数始终为 0x358637BD
.
I have a quesiton regarding the rounding mode in C++. I would like to change the rounding mode to see if we can get the upper- and lowerbound of a given variable.
By running some toy examples with following code, I can get some super strange observations.
#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)
Firstly, let's have a look at y
. If the rounding mode is set to be FE_UPWARD
, the resulted value is 9.999999975e-07
. In comparison, if the rounding mode is set to be FE_DOWNWARD
, the resulted value is 9.999999974e-07
.
Secondly, if I define x=1e-6
in a python
script, and then use ctype
to pass x
to this C function. The results are reversed, namely FE_UPWARD
returns 9.999999975e-07
and FE_DOWNWARD
returns 1.000000111e-06
.
So, I have 2 questions in total:
For the first observation, why both of them are smaller than
1e-6
?As for the second observation, why is the relation of these 2 values reversed?
Thank you very much!
For your first question 1e-6 in floating point binary is a repeating fraction so precision is lost. Single-precision floating point reserves 23 bits for mantissa and the rest are dropped and the least significant bit is rounded, so the number can appear smaller or larger when written out to more precision. 23 bits translates to about 7 digits of decimal precision.
For the second question, changing the rounding mode is sticky and isn't restored after calling your C++ function. It affects Python's conversions of Python float to C float through ctypes as well. Change the code to the following to save and restore the original mode and you'll get consistency:
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
The IEEE hex dump of the x
float in a debugger showed 0x358637BD
in one mode and 0x358637BE
in the other so the mantissa (last 23 bits) was rounding differently during the Python float (internally in CPython as C double) to C float conversion. The y
float always read 0x358637BD
.
这篇关于将Python浮点数传递给C函数时,舍入模式不一致的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!