将Python浮点数传递给C函数时,舍入模式不一致 [英] Inconsistent Rounding Mode When Passing Python Float to C function

查看:64
本文介绍了将Python浮点数传递给C函数时,舍入模式不一致的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对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个问题:

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

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

非常感谢!

解决方案

对于您的第一个问题,浮点二进制文件中的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:

  1. For the first observation, why both of them are smaller than 1e-6?

  2. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆