PDA

View Full Version : locale messes up sprintf, but only in Qt program



PierreA
13th September 2017, 17:15
I have the following function:

string ldecimal(double x,double toler)
{
double x2;
int h,i,iexp,chexp;
size_t zpos;
char *dotpos,*epos,*pLcNumeric;
string ret,s,m,antissa,exponent,saveLcNumeric;
char buffer[32],fmt[8];
assert(toler>=0);
pLcNumeric=getenv("LC_NUMERIC");
if (pLcNumeric)
saveLcNumeric=pLcNumeric;
setenv("LC_NUMERIC","C",true);
if (toler>0 && x!=0)
{
iexp=floor(log10(fabs(x/toler))-1);
if (iexp<0)
iexp=0;
}
else
iexp=DBL_DIG-1;
h=-1;
i=iexp;
while (true)
{
sprintf(fmt,"%%.%de",i);
sprintf(buffer,fmt,x);
x2=atof(buffer);
if (h>0 && (fabs(x-x2)<=toler || i>=DBL_DIG+3))
break;
if (fabs(x-x2)>toler || i<=0)
h=1;
i+=h;
}
dotpos=strchr(buffer,'.');
epos=strchr(buffer,'e');
if (epos && !dotpos) // e.g. 2e+00 becomes 2.e+00
{
memmove(epos+1,epos,buffer+31-epos);
dotpos=epos++;
*dotpos=='.';
}
if (dotpos && epos)
{
m=string(buffer,dotpos-buffer);
antissa=string(dotpos+1,epos-dotpos-1);
exponent=string(epos+1);
if (m.length()>1)
{
s=m.substr(0,1);
m.erase(0,1);
}
iexp=atoi(exponent.c_str());
zpos=antissa.find_last_not_of('0');
antissa.erase(zpos+1);
iexp=stoi(exponent);
if (iexp<0 && iexp>-5)
{
antissa=m+antissa;
m="";
iexp++;
}
if (iexp>0)
{
chexp=iexp;
if (chexp>antissa.length())
chexp=antissa.length();
m+=antissa.substr(0,chexp);
antissa.erase(0,chexp);
iexp-=chexp;
}
while (iexp>-5 && iexp<0 && m.length()==0)
{
antissa="0"+antissa;
iexp++;
}
while (iexp<3 && iexp>0 && antissa.length()==0)
{
m+='0';
iexp--;
}
sprintf(buffer,"%d",iexp);
exponent=buffer;
ret=s+m;
if (antissa.length())
ret+='.'+antissa;
if (iexp)
ret+='e'+exponent;
}
else
ret=buffer;
setenv("LC_NUMERIC",saveLcNumeric.c_str(),true);
return ret;
}
The function is used to format numbers when writing to files, so it has to use '.' for the decimal point regardless of locale. I added the saving and restoring LC_NUMERIC code to fix this bug. It didn't work.

I am using it in at least two programs: bezitest, which is a non-Qt test program, and viewtin, which is a Qt GUI program. Until I implement hit-testing, viewtin is displaying the coordinates of the cursor in world space. "bezitest ldecimal" outputs, among others, "3.141592653589793". viewtin displays a tooltip saying something like "3,141592653589793,2,718281828459045". I ran it in gdb and found that the comma is put there by sprintf.

locale outputs the following:

LANG=en_DK.UTF-8
LANGUAGE=en_US:es
LC_CTYPE="en_DK.UTF-8"
LC_NUMERIC="en_DK.UTF-8"
LC_TIME="en_DK.UTF-8"
LC_COLLATE="en_DK.UTF-8"
LC_MONETARY="en_DK.UTF-8"
LC_MESSAGES="en_DK.UTF-8"
LC_PAPER="en_DK.UTF-8"
LC_NAME="en_DK.UTF-8"
LC_ADDRESS="en_DK.UTF-8"
LC_TELEPHONE="en_DK.UTF-8"
LC_MEASUREMENT="en_DK.UTF-8"
LC_IDENTIFICATION="en_DK.UTF-8"
LC_ALL=
LC_NUMERIC is actually unset.

PierreA
17th September 2017, 23:41
I catted /proc/<pid>/environ into two files while the programs were running and compared them. They are identical, except for the last entry, which is the name of the program. Here it is, with some entries snipped:

XDG_VTNR=7
XDG_SESSION_ID=2
SSH_AGENT_PID=3140
PAM_KWALLET5_LOGIN=<snip>
KDE_MULTIHEAD=false
GPG_AGENT_INFO=/home/phma/.gnupg/S.gpg-agent:0:1
TERM=xterm
SHELL=/bin/bash
XDG_SESSION_COOKIE=<snip>
GTK2_RC_FILES=/etc/gtk-2.0/gtkrc:/home/phma/.gtkrc-2.0:/home/phma/.config/gtkrc-2.0
KONSOLE_DBUS_SERVICE=:1.500
KONSOLE_PROFILE_NAME=Shell
QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
GS_LIB=/home/phma/.fonts
GTK_RC_FILES=/etc/gtk/gtkrc:/home/phma/.gtkrc:/home/phma/.config/gtkrc
WINDOWID=310378531
SHELL_SESSION_ID=<snip>
XDG_SESSION_CLASS=user
KDE_FULL_SESSION=true
USER=phma
LS_COLORS=<snip>
XCURSOR_SIZE=0
QT_ACCESSIBILITY=1
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session1
XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0
SSH_AUTH_SOCK=<snip>
DEFAULTS_PATH=/usr/share/gconf//usr/share/xsessions/plasma.default.path
SESSION_MANAGER=<snip>
XDG_CONFIG_DIRS=/etc/xdg/xdg-/usr/share/xsessions/plasma:/etc/xdg:/usr/share/kubuntu-default-settings/kf5-settings
PATH=/home/phma/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
DESKTOP_SESSION=/usr/share/xsessions/plasma
QT_QPA_PLATFORMTHEME=appmenu-qt5
QT_IM_MODULE=compose
PWD=/home/phma/build/bezitopo/dbg
XDG_SESSION_TYPE=x11
KONSOLE_DBUS_WINDOW=/Windows/2
KDE_SESSION_UID=1000
LANG=en_DK.UTF-8
MANDATORY_PATH=/usr/share/gconf//usr/share/xsessions/plasma.mandatory.path
KONSOLE_DBUS_SESSION=/Sessions/4
SHLVL=1
XDG_SEAT=seat0
COLORFGBG=15;0
HOME=/home/phma
KDE_SESSION_VERSION=5
LANGUAGE=en_US:es
XCURSOR_THEME=oxy-blue
LOGNAME=phma
XDG_SESSION_DESKTOP=KDE
XDG_DATA_DIRS=/usr/share//usr/share/xsessions/plasma:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop
DBUS_SESSION_BUS_ADDRESS=<snip>
LESSOPEN=| /usr/bin/lesspipe %s
DISPLAY=:0
XDG_RUNTIME_DIR=/run/user/1000
PROFILEHOME=
XDG_CURRENT_DESKTOP=KDE
GTK_IM_MODULE=xim
LESSCLOSE=/usr/bin/lesspipe %s %s
PAM_KWALLET_LOGIN=/tmp/kwallet_phma.socket
XAUTHORITY=/tmp/xauth-1000-_0
OLDPWD=/home/phma
_=./viewtin

PierreA
27th October 2017, 03:39
Figured it out. I had to call setlocale, not setenv. The constructor of QApplication calls setlocale, but my non-Qt program was not calling setlocale, so they behave differently, which I thought was something wrong with Qt, but it isn't.