From 770d49e9d2626707b7572d9b976642555c6db34e Mon Sep 17 00:00:00 2001 From: Nick Guletskii Date: Fri, 24 Apr 2020 04:46:35 +0300 Subject: [PATCH] [1.x] Fix incorrect calculation results when the C locale is set to a locale that uses commas as the decimal separator (#17177) * Add a test for floating point parsing locale invariance * Use locale-invariant dmlc:stod/stof instead of std:stod/stof * Change the new operator tutorial to use dmlc:stod instead of std::stod * Rename locale invariance test * Skip test_scalarop_locale_invariance if the locales aren't available * Fix linter errors due to incorrect include order --- cpp-package/include/mxnet-cpp/optimizer.h | 3 +- cpp-package/include/mxnet-cpp/optimizer.hpp | 25 ++++++------ docs/static_site/src/pages/api/faq/new_op.md | 2 +- plugin/torch/torch_function.h | 3 +- src/nnvm/legacy_op_util.cc | 3 +- .../contrib/gradient_multiplier_op.cc | 3 +- src/operator/numpy/np_boolean_mask_assign.cc | 3 +- src/operator/numpy/np_boolean_mask_assign.cu | 3 +- .../numpy/np_elemwise_broadcast_logic_op.cc | 3 +- .../numpy/np_elemwise_broadcast_op.cc | 3 +- .../np_elemwise_broadcast_op_extended.cc | 17 +++++---- src/operator/numpy/np_true_divide.cc | 5 ++- ...kldnn_post_quantize_align_scale_property.h | 5 ++- .../tensor/elemwise_binary_scalar_op.h | 3 +- .../tensor/elemwise_binary_scalar_op_basic.cc | 9 +++-- .../elemwise_binary_scalar_op_extended.cc | 15 ++++---- tests/python/unittest/test_operator.py | 38 +++++++++++++++++++ 17 files changed, 98 insertions(+), 45 deletions(-) diff --git a/cpp-package/include/mxnet-cpp/optimizer.h b/cpp-package/include/mxnet-cpp/optimizer.h index 320b13eebf2d..ac842874fea6 100644 --- a/cpp-package/include/mxnet-cpp/optimizer.h +++ b/cpp-package/include/mxnet-cpp/optimizer.h @@ -27,6 +27,7 @@ #ifndef MXNET_CPP_OPTIMIZER_H_ #define MXNET_CPP_OPTIMIZER_H_ +#include #include #include #include @@ -84,7 +85,7 @@ class Optimizer { Optimizer *SetLRScheduler(std::unique_ptr lrScheduler) { CHECK(lrScheduler); lrScheduler_ = std::move(lrScheduler); - lrScheduler_->SetLR(std::stof(params_["lr"])); + lrScheduler_->SetLR(dmlc::stof(params_["lr"])); return this; } /*! diff --git a/cpp-package/include/mxnet-cpp/optimizer.hpp b/cpp-package/include/mxnet-cpp/optimizer.hpp index 26fd00f3a162..f3c71df24c59 100644 --- a/cpp-package/include/mxnet-cpp/optimizer.hpp +++ b/cpp-package/include/mxnet-cpp/optimizer.hpp @@ -26,6 +26,7 @@ #ifndef MXNET_CPP_OPTIMIZER_HPP_ #define MXNET_CPP_OPTIMIZER_HPP_ +#include #include #include #include @@ -116,11 +117,11 @@ inline float Optimizer::GetLR_(int index) { if (nullptr != lrScheduler_) { return lrScheduler_->GetLR(num_update_); } - return std::stof(params_["lr"]); + return dmlc::stof(params_["lr"]); } inline float Optimizer::GetWD_(int index) { - float wd = std::stof(params_["wd"]); + float wd = dmlc::stof(params_["wd"]); return wd; } @@ -362,9 +363,9 @@ inline void AdamOptimizer::Update(int index, NDArray weight, NDArray grad) { auto values = GetParamValues_(); CHECK_EQ(keys.size(), values.size()); - float lr = std::stof(params_["lr"]); - float b1 = std::stof(params_["beta1"]); - float b2 = std::stof(params_["beta2"]); + float lr = dmlc::stof(params_["lr"]); + float b1 = dmlc::stof(params_["beta1"]); + float b2 = dmlc::stof(params_["beta2"]); float t = count_[index]; float coef1 = 1.0f - std::pow(b1, t); float coef2 = 1.0f - std::pow(b2, t); @@ -407,15 +408,15 @@ inline void AdaGradOptimizer::Update(int index, NDArray weight, NDArray grad) { CreateState_(index, weight); } - float eps = std::stof(params_["eps"]); + float eps = dmlc::stof(params_["eps"]); float lr = GetLR_(index); float wd = GetWD_(index); UpdateCount_(index); if (params_.count("rescale_grad") > 0) { - grad *= std::stof(params_["rescale_grad"]); + grad *= dmlc::stof(params_["rescale_grad"]); } if (params_.count("clip_gradient") > 0) { - _clip(grad, std::stof(params_["clip_gradient"])); + _clip(grad, dmlc::stof(params_["clip_gradient"])); } auto& history = *history_[index]; history += grad * grad; @@ -448,16 +449,16 @@ inline void AdaDeltaOptimizer::Update(int index, NDArray weight, NDArray grad) { CreateState_(index, weight); } - float rho = std::stof(params_["rho"]); - float epsilon = std::stof(params_["epsilon"]); + float rho = dmlc::stof(params_["rho"]); + float epsilon = dmlc::stof(params_["epsilon"]); float wd = GetWD_(index); UpdateCount_(index); if (params_.count("rescale_grad") > 0) { - grad *= std::stof(params_["rescale_grad"]); + grad *= dmlc::stof(params_["rescale_grad"]); } if (params_.count("clip_gradient") > 0) { - _clip(grad, std::stof(params_["clip_gradient"])); + _clip(grad, dmlc::stof(params_["clip_gradient"])); } auto& acc_g = *acc_g_[index]; diff --git a/docs/static_site/src/pages/api/faq/new_op.md b/docs/static_site/src/pages/api/faq/new_op.md index 787b4038dbf4..053182b79805 100644 --- a/docs/static_site/src/pages/api/faq/new_op.md +++ b/docs/static_site/src/pages/api/faq/new_op.md @@ -204,7 +204,7 @@ Simple arguments can be parsed like NNVM_REGISTER_OP(scalar_op) .set_attr_parser( [](NodeAttrs* attrs) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) ``` diff --git a/plugin/torch/torch_function.h b/plugin/torch/torch_function.h index f6f760231bdf..6a33e706ff2f 100644 --- a/plugin/torch/torch_function.h +++ b/plugin/torch/torch_function.h @@ -28,6 +28,7 @@ #include "./torch_base.h" #include #include +#include #include #include #include @@ -69,7 +70,7 @@ void TorchRunOp(std::vector arr_in, lua_pushinteger(L, std::stoi(val)); break; case 'f': - lua_pushnumber(L, std::stof(val)); + lua_pushnumber(L, dmlc::stof(val)); break; case 's': lua_pushstring(L, val.c_str()); diff --git a/src/nnvm/legacy_op_util.cc b/src/nnvm/legacy_op_util.cc index 851552a56016..f66350fff404 100644 --- a/src/nnvm/legacy_op_util.cc +++ b/src/nnvm/legacy_op_util.cc @@ -23,6 +23,7 @@ * \brief Utility to adapt OpProperty to the new NNVM registery */ #include +#include #include #include #include @@ -511,7 +512,7 @@ void RegisterLegacyNDFunc() { const std::string& name = reg->arguments[i+reg->num_use_vars].name; auto s = dict.find(name); CHECK(s != dict.end()) << "Missing scalar param " << name; - scalars.push_back(std::stof(s->second)); + scalars.push_back(dmlc::stof(s->second)); dict.erase(s); } diff --git a/src/operator/contrib/gradient_multiplier_op.cc b/src/operator/contrib/gradient_multiplier_op.cc index 47f891ef802b..0a49ec1c36b3 100644 --- a/src/operator/contrib/gradient_multiplier_op.cc +++ b/src/operator/contrib/gradient_multiplier_op.cc @@ -23,6 +23,7 @@ * \brief * \author Istvan Fehervari */ +#include #include "../tensor/elemwise_unary_op.h" #include "../tensor/elemwise_binary_scalar_op.h" @@ -77,7 +78,7 @@ multiplies the gradient from the subsequent level by a scalar factor lambda and the preceding layer. )code" ADD_FILELINE) .set_attr_parser([](NodeAttrs* attrs) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FInferStorageType", ElemwiseStorageType<1, 1, false, true, true>) .set_attr("FCompute", UnaryOp::IdentityCompute) diff --git a/src/operator/numpy/np_boolean_mask_assign.cc b/src/operator/numpy/np_boolean_mask_assign.cc index e01ebb7c6c24..ef7cce4d2491 100644 --- a/src/operator/numpy/np_boolean_mask_assign.cc +++ b/src/operator/numpy/np_boolean_mask_assign.cc @@ -22,6 +22,7 @@ * \brief CPU implementation of Boolean Mask Assign */ +#include #include "../../common/utils.h" #include "../contrib/boolean_mask-inl.h" @@ -272,7 +273,7 @@ void NumpyBooleanAssignForwardCPU(const nnvm::NodeAttrs& attrs, MSHADOW_TYPE_SWITCH_WITH_BOOL(data.type_flag_, DType, { Kernel, cpu>::Launch( s, valid_num, data.dptr(), prefix_sum.data(), prefix_sum.size(), - leading, middle, trailing, static_cast(std::stod(attrs.dict.at("value")))); + leading, middle, trailing, static_cast(dmlc::stod(attrs.dict.at("value")))); }); } } diff --git a/src/operator/numpy/np_boolean_mask_assign.cu b/src/operator/numpy/np_boolean_mask_assign.cu index e1e645268565..6fa59bea7710 100644 --- a/src/operator/numpy/np_boolean_mask_assign.cu +++ b/src/operator/numpy/np_boolean_mask_assign.cu @@ -23,6 +23,7 @@ */ #include +#include #include "../../common/utils.h" #include "../contrib/boolean_mask-inl.h" @@ -252,7 +253,7 @@ void NumpyBooleanAssignForwardGPU(const nnvm::NodeAttrs& attrs, } } else { CHECK(attrs.dict.find("value") != attrs.dict.end()) << "value is not provided"; - double value = std::stod(attrs.dict.at("value")); + double value = dmlc::stod(attrs.dict.at("value")); MSHADOW_TYPE_SWITCH_WITH_BOOL(data.type_flag_, DType, { Kernel, gpu>::Launch( s, leading * valid_num * trailing, data.dptr(), prefix_sum, mask_size + 1, diff --git a/src/operator/numpy/np_elemwise_broadcast_logic_op.cc b/src/operator/numpy/np_elemwise_broadcast_logic_op.cc index 8395cafd119a..74db52d33f03 100644 --- a/src/operator/numpy/np_elemwise_broadcast_logic_op.cc +++ b/src/operator/numpy/np_elemwise_broadcast_logic_op.cc @@ -30,6 +30,7 @@ #include "../tvmop/op_module.h" #endif // MXNET_USE_TVM_OP +#include #include "../tensor/elemwise_binary_broadcast_op.h" #include "../tensor/elemwise_binary_scalar_op.h" @@ -225,7 +226,7 @@ struct TVMBinaryBroadcastScalarCompute { .set_num_inputs(1) \ .set_num_outputs(1) \ .set_attr_parser([](NodeAttrs* attrs) { \ - attrs->parsed = std::stod(attrs->dict["scalar"]); \ + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); \ }) \ .set_attr("FListInputNames", \ [](const NodeAttrs& attrs) { \ diff --git a/src/operator/numpy/np_elemwise_broadcast_op.cc b/src/operator/numpy/np_elemwise_broadcast_op.cc index 6409d4322a27..ae285caa9094 100644 --- a/src/operator/numpy/np_elemwise_broadcast_op.cc +++ b/src/operator/numpy/np_elemwise_broadcast_op.cc @@ -23,6 +23,7 @@ * \brief CPU Implementation of basic functions for elementwise numpy binary broadcast operator. */ +#include #include "./np_elemwise_broadcast_op.h" namespace mxnet { @@ -33,7 +34,7 @@ namespace op { .set_num_inputs(1) \ .set_num_outputs(1) \ .set_attr_parser([](NodeAttrs* attrs) { \ - attrs->parsed = std::stod(attrs->dict["scalar"]); \ + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); \ }) \ .set_attr("FInferShape", ElemwiseShape<1, 1>) \ .set_attr("FInferType", NumpyBinaryScalarType) \ diff --git a/src/operator/numpy/np_elemwise_broadcast_op_extended.cc b/src/operator/numpy/np_elemwise_broadcast_op_extended.cc index 70233a596dc7..52d681885d82 100644 --- a/src/operator/numpy/np_elemwise_broadcast_op_extended.cc +++ b/src/operator/numpy/np_elemwise_broadcast_op_extended.cc @@ -23,6 +23,7 @@ * \brief CPU Implementation of extended functions for elementwise numpy binary broadcast operator. */ +#include #include "../../common/utils.h" #include "./np_elemwise_broadcast_op.h" @@ -34,7 +35,7 @@ namespace op { .set_num_inputs(1) \ .set_num_outputs(1) \ .set_attr_parser([](NodeAttrs* attrs) { \ - attrs->parsed = std::stod(attrs->dict["scalar"]); \ + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); \ }) \ .set_attr("FInferShape", ElemwiseShape<1, 1>) \ .set_attr("FInferType", NumpyBinaryScalarType) \ @@ -87,7 +88,7 @@ NNVM_REGISTER_OP(_npi_lcm_scalar) .set_num_inputs(1) .set_num_outputs(1) .set_attr_parser([](NodeAttrs* attrs) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FInferShape", ElemwiseShape<1, 1>) .set_attr("FInferType", ElemwiseIntType<1, 1>) @@ -175,7 +176,7 @@ NNVM_REGISTER_OP(_npi_bitwise_xor_scalar) .set_num_inputs(1) .set_num_outputs(1) .set_attr_parser([](NodeAttrs* attrs) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FInferShape", ElemwiseShape<1, 1>) .set_attr("FInferType", ElemwiseIntType<1, 1>) @@ -192,7 +193,7 @@ NNVM_REGISTER_OP(_npi_bitwise_or_scalar) .set_num_inputs(1) .set_num_outputs(1) .set_attr_parser([](NodeAttrs* attrs) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FInferShape", ElemwiseShape<1, 1>) .set_attr("FInferType", ElemwiseIntType<1, 1>) @@ -275,13 +276,13 @@ MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(_npi_rarctan2_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_npi_arctan2_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward); MXNET_OPERATOR_REGISTER_BINARY(_backward_npi_rarctan2_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward); @@ -363,12 +364,12 @@ NNVM_REGISTER_OP(_backward_npi_ldexp) MXNET_OPERATOR_REGISTER_BINARY(_backward_npi_ldexp_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward); MXNET_OPERATOR_REGISTER_BINARY(_backward_npi_rldexp_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward); } // namespace op diff --git a/src/operator/numpy/np_true_divide.cc b/src/operator/numpy/np_true_divide.cc index 1e46cc9d13b5..6edfb4dd0901 100644 --- a/src/operator/numpy/np_true_divide.cc +++ b/src/operator/numpy/np_true_divide.cc @@ -23,6 +23,7 @@ * \brief CPU Implementation of true_divide operator. */ +#include #include "./np_true_divide-inl.h" namespace mxnet { @@ -88,7 +89,7 @@ NNVM_REGISTER_OP(_npi_true_divide_scalar) .set_num_inputs(1) .set_num_outputs(1) .set_attr_parser([](NodeAttrs* attrs) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FInferShape", ElemwiseShape<1, 1>) .set_attr("FInferType", TrueDivideType<1>) @@ -111,7 +112,7 @@ NNVM_REGISTER_OP(_npi_rtrue_divide_scalar) .set_num_inputs(1) .set_num_outputs(1) .set_attr_parser([](NodeAttrs* attrs) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FInferShape", ElemwiseShape<1, 1>) .set_attr("FInferType", TrueDivideType<1>) diff --git a/src/operator/subgraph/mkldnn/mkldnn_post_quantize_align_scale_property.h b/src/operator/subgraph/mkldnn/mkldnn_post_quantize_align_scale_property.h index c05c2a8e4a6a..85691c19993b 100644 --- a/src/operator/subgraph/mkldnn/mkldnn_post_quantize_align_scale_property.h +++ b/src/operator/subgraph/mkldnn/mkldnn_post_quantize_align_scale_property.h @@ -21,6 +21,7 @@ #define MXNET_OPERATOR_SUBGRAPH_MKLDNN_MKLDNN_POST_QUANTIZE_ALIGN_SCALE_PROPERTY_H_ #if MXNET_USE_MKLDNN == 1 +#include #include #include #include "../common.h" @@ -146,8 +147,8 @@ class SgMKLDNNPostQuantizeAlignScaleProperty : public SubgraphProperty { float min_calib = 0.0f; float max_calib = 0.0f; for (size_t i = 0; i < subgraph_nodes.size(); ++i) { - auto this_min_calib = std::stof(subgraph_nodes[i]->attrs.dict["min_calib_range"]); - auto this_max_calib = std::stof(subgraph_nodes[i]->attrs.dict["max_calib_range"]); + auto this_min_calib = dmlc::stof(subgraph_nodes[i]->attrs.dict["min_calib_range"]); + auto this_max_calib = dmlc::stof(subgraph_nodes[i]->attrs.dict["max_calib_range"]); if (min_calib > this_min_calib) min_calib = this_min_calib; if (max_calib < this_max_calib) max_calib = this_max_calib; } diff --git a/src/operator/tensor/elemwise_binary_scalar_op.h b/src/operator/tensor/elemwise_binary_scalar_op.h index f974332252d8..53161ee2354f 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op.h +++ b/src/operator/tensor/elemwise_binary_scalar_op.h @@ -26,6 +26,7 @@ #define MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_SCALAR_OP_H_ #include +#include #include #include #include "../mshadow_op.h" @@ -400,7 +401,7 @@ class BinaryScalarOp : public UnaryOp { .set_num_inputs(1) \ .set_num_outputs(1) \ .set_attr_parser([](NodeAttrs* attrs) { \ - attrs->parsed = std::stod(attrs->dict["scalar"]); \ + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); \ }) \ .set_attr("FInferShape", ElemwiseShape<1, 1>) \ .set_attr("FInferType", ElemwiseType<1, 1>) \ diff --git a/src/operator/tensor/elemwise_binary_scalar_op_basic.cc b/src/operator/tensor/elemwise_binary_scalar_op_basic.cc index ae356deff0a1..13014b35c2fe 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op_basic.cc +++ b/src/operator/tensor/elemwise_binary_scalar_op_basic.cc @@ -22,6 +22,7 @@ * \file elemwise_binary_scalar_op_basic.cc * \brief CPU Implementation of basic binary scalar functions. */ +#include #include "../../common/utils.h" #include "./elemwise_binary_op.h" #include "./elemwise_binary_scalar_op.h" @@ -31,7 +32,7 @@ .set_num_inputs(1) \ .set_num_outputs(1) \ .set_attr_parser([](NodeAttrs* attrs) { \ - attrs->parsed = std::stod(attrs->dict["scalar"]); \ + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); \ }) \ .set_attr("FInferShape", ElemwiseShape<1, 1>) \ .set_attr("FInferType", ElemwiseType<1, 1>) \ @@ -189,7 +190,7 @@ MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_rdiv_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_rdiv_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward< cpu, mshadow_op::rdiv_grad>); @@ -200,7 +201,7 @@ MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_mod_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_mod_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward< cpu, mshadow_op::mod_grad>); @@ -211,7 +212,7 @@ MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_rmod_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_rmod_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward< cpu, mshadow_op::rmod_grad>); diff --git a/src/operator/tensor/elemwise_binary_scalar_op_extended.cc b/src/operator/tensor/elemwise_binary_scalar_op_extended.cc index 4ada2f036f7d..7dd8cf41c59e 100644 --- a/src/operator/tensor/elemwise_binary_scalar_op_extended.cc +++ b/src/operator/tensor/elemwise_binary_scalar_op_extended.cc @@ -22,6 +22,7 @@ * \file elemwise_binary_scalar_op_extended.cc * \brief CPU Implementation of extended binary scalar functions. */ +#include #include "./elemwise_unary_op.h" #include "./elemwise_binary_op.h" #include "./elemwise_binary_scalar_op.h" @@ -36,7 +37,7 @@ MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_maximum_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_maximum_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward); MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_minimum_scalar) @@ -47,7 +48,7 @@ MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_minimum_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_minimum_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward); MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_power_scalar) @@ -57,7 +58,7 @@ MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_power_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_power_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward< cpu, mshadow_op::power_grad>); @@ -69,7 +70,7 @@ MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_rpower_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_rpower_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward< cpu, mshadow_op::rpower_grad>); @@ -82,7 +83,7 @@ MXNET_OPERATOR_REGISTER_BINARY_SCALAR(_hypot_scalar) MXNET_OPERATOR_REGISTER_BINARY(_backward_hypot_scalar) .add_argument("scalar", "float", "scalar value") -.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = dmlc::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward< cpu, mshadow_op::hypot_grad_left>); @@ -110,7 +111,7 @@ Example:: .set_num_outputs(1) .set_attr_parser([](NodeAttrs* attrs) { if (attrs->dict.find("scalar") != attrs->dict.end()) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); } else { attrs->parsed = 1.0; } @@ -129,7 +130,7 @@ Example:: MXNET_OPERATOR_REGISTER_BINARY(_backward_smooth_l1) .set_attr_parser([](NodeAttrs *attrs) { if (attrs->dict.find("scalar") != attrs->dict.end()) { - attrs->parsed = std::stod(attrs->dict["scalar"]); + attrs->parsed = dmlc::stod(attrs->dict["scalar"]); } else { attrs->parsed = 1.0; } diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index 481cd0064bfd..e22d529eeb41 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -34,6 +34,7 @@ from nose.tools import assert_raises, ok_ import unittest import os +import locale def check_rnn_consistency(cell1, cell2, T, N, I, H, grad_req, rtol=1e-2, atol=1e-4): dshape = (N, T, I) @@ -9973,6 +9974,43 @@ def test_broadcast_ops_on_misaligned_input_oneside(): mx.nd.waitall() assert_almost_equal(f, expected) +def test_scalarop_locale_invariance(): + arr = mx.nd.zeros((1,)) + prev = locale.getlocale(locale.LC_NUMERIC) + try: + locales_to_try = [ + "en_DK.UTF-8", # Non-standard locale that uses , as the decimal separator, installed + # on English Ubuntu by default. + "ru_RU.UTF-8", # Uses , as the decimal separator. May not be installed on the system. + "German" # A Windows locale. Uses , as the decimal separator. + ] + + locale_set = False + for loc in locales_to_try: + try: + locale.setlocale(locale.LC_NUMERIC, loc) + locale_set = True + break + except locale.Error as e: + print("Couldn't enable locale", loc, ": ", str(e)) + + if locale_set: + scalar = 0.3 + assert "," in locale.str(scalar) + assert_almost_equal( + arr.asnumpy() + scalar, + (arr + scalar).asnumpy(), + rtol=1e-5, + atol=1e-5 + ) + else: + # Shouldn't happen on Windows: "German" should always be available. + raise unittest.SkipTest("Couldn't find a locale suitable for " + "test_scalarop_locale_invariance. Please install en_DK.UTF-8 " + "or ru_RU.UTF-8 to run this test.") + finally: + locale.setlocale(locale.LC_NUMERIC, prev) + if __name__ == '__main__': import nose nose.runmodule()