From 7676efca447294945dd109ff7a2d64e2264e3d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Wed, 19 Nov 2025 02:31:41 +0100 Subject: [PATCH] FIX: Issues with SIGN and EXP Fixes #563 --- base/src/cast.rs | 67 +++++++++++++--------------- base/src/functions/macros.rs | 6 +-- base/src/functions/mathematical.rs | 8 +++- xlsx/tests/calc_tests/EXP_SIGN.xlsx | Bin 0 -> 12954 bytes 4 files changed, 41 insertions(+), 40 deletions(-) create mode 100644 xlsx/tests/calc_tests/EXP_SIGN.xlsx diff --git a/base/src/cast.rs b/base/src/cast.rs index 912594d..33fe7af 100644 --- a/base/src/cast.rs +++ b/base/src/cast.rs @@ -15,6 +15,23 @@ pub(crate) enum NumberOrArray { } impl Model { + pub(crate) fn cast_number(&self, s: &str) -> Option { + match s.trim().parse::() { + Ok(f) => Some(f), + _ => { + let currency = &self.locale.currency.symbol; + let mut currencies = vec!["$", "€"]; + if !currencies.iter().any(|e| *e == currency) { + currencies.push(currency); + } + // Try to parse as a formatted number (e.g., dates, currencies, percentages) + if let Ok((v, _number_format)) = parse_formatted_number(s, ¤cies) { + return Some(v); + } + None + } + } + } pub(crate) fn get_number_or_array( &mut self, node: &Node, @@ -22,24 +39,13 @@ impl Model { ) -> Result { match self.evaluate_node_in_context(node, cell) { CalcResult::Number(f) => Ok(NumberOrArray::Number(f)), - CalcResult::String(s) => match s.parse::() { - Ok(f) => Ok(NumberOrArray::Number(f)), - _ => { - let mut currencies = vec!["$", "€"]; - let currency = &self.locale.currency.symbol; - if !currencies.iter().any(|e| e == currency) { - currencies.push(currency); - } - // Try to parse as a formatted number (e.g., dates, currencies, percentages) - if let Ok((v, _number_format)) = parse_formatted_number(&s, ¤cies) { - return Ok(NumberOrArray::Number(v)); - } - Err(CalcResult::new_error( - Error::VALUE, - cell, - "Expecting number".to_string(), - )) - } + CalcResult::String(s) => match self.cast_number(&s) { + Some(f) => Ok(NumberOrArray::Number(f)), + None => Err(CalcResult::new_error( + Error::VALUE, + cell, + "Expecting number".to_string(), + )), }, CalcResult::Boolean(f) => { if f { @@ -108,24 +114,13 @@ impl Model { ) -> Result { match result { CalcResult::Number(f) => Ok(f), - CalcResult::String(s) => match s.trim().parse::() { - Ok(f) => Ok(f), - _ => { - let mut currencies = vec!["$", "€"]; - let currency = &self.locale.currency.symbol; - if !currencies.iter().any(|e| e == currency) { - currencies.push(currency); - } - // Try to parse as a formatted number (e.g., dates, currencies, percentages) - if let Ok((v, _number_format)) = parse_formatted_number(&s, ¤cies) { - return Ok(v); - } - Err(CalcResult::new_error( - Error::VALUE, - cell, - "Expecting number".to_string(), - )) - } + CalcResult::String(s) => match self.cast_number(&s) { + Some(f) => Ok(f), + None => Err(CalcResult::new_error( + Error::VALUE, + cell, + "Expecting number".to_string(), + )), }, CalcResult::Boolean(f) => { if f { diff --git a/base/src/functions/macros.rs b/base/src/functions/macros.rs index 378aa18..336954f 100644 --- a/base/src/functions/macros.rs +++ b/base/src/functions/macros.rs @@ -68,14 +68,14 @@ macro_rules! single_number_fn { }, // If String, parse to f64 then apply or #VALUE! error ArrayNode::String(s) => { - let node = match s.parse::() { - Ok(f) => match $op(f) { + let node = match self.cast_number(&s) { + Some(f) => match $op(f) { Ok(x) => ArrayNode::Number(x), Err(Error::DIV) => ArrayNode::Error(Error::DIV), Err(Error::VALUE) => ArrayNode::Error(Error::VALUE), Err(e) => ArrayNode::Error(e), }, - Err(_) => ArrayNode::Error(Error::VALUE), + None => ArrayNode::Error(Error::VALUE), }; data_row.push(node); } diff --git a/base/src/functions/mathematical.rs b/base/src/functions/mathematical.rs index 057fff6..025e109 100644 --- a/base/src/functions/mathematical.rs +++ b/base/src/functions/mathematical.rs @@ -1336,7 +1336,13 @@ impl Model { } Ok(acc) }); - single_number_fn!(fn_sign, |f| Ok(f64::signum(f))); + single_number_fn!(fn_sign, |f| { + if f == 0.0 { + Ok(0.0) + } else { + Ok(f64::signum(f)) + } + }); single_number_fn!(fn_degrees, |f| Ok(f * (180.0 / PI))); single_number_fn!(fn_radians, |f| Ok(f * (PI / 180.0))); single_number_fn!(fn_odd, |f| { diff --git a/xlsx/tests/calc_tests/EXP_SIGN.xlsx b/xlsx/tests/calc_tests/EXP_SIGN.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..6774bdd794b945fb29001f0c97d87f4575d3f9b5 GIT binary patch literal 12954 zcmeHN1y@{avTfYmCAbryaR~12?li$YxDyEOL4&)yyL)hV*WeO7fyd3vynAnE-YRar{sHpm_g@qUcvc|L0E0T0brA0yNXmF z3|9k8!(_djr=XkX!~n?v$qdkL-um?6#qPy|htN(su8g^_XGs=uxDPSn2U9FevaiYaOLT0 zRff86?O?r!b)U^nd05IS_S`ZT&Sp*)&N8FH6S0l>VGnvZTV>&6a0r+lI|vj$oh{jg zoIZr`kK!gvpei4%3+Op(j(%{wWaA>{9{A`>y?CIicE^hV$^u7`N_X1DPWw5c$*z_A zobMXv@ug78X~>jW-ALv$K)Yza4io?di>xFLr3M1 zDE^^rs}Id(?q=>bSwh;K#-%-)uC%ErTV{BJTzuwIv>J7SQ3D?iBcCt`n=jc#l3j)J}|_UJUYSL&{`q==UrW5`&akH@@0+Y?mYH?tPqay`@7J!~WsK zMeDER_3uH_O_DtW2>}44!vFv%uTRF!n#tA9!OFUd%5!7t%jdJn(FL+O}R*^ zsj1AN7g3YMs|`pD?|6JqWKmHv3!Vfwt|j$&6g7J^wt1X%jI=X?Gmr=mh<7#^3n=2% ztK5meZ0az=q7{l`(KiH>=$JeX!$_OOBh49do&gfJch#xh!4h>se#)nflWblxw?Vm4!!>Bka0_MA4mBo~o56oGFz;BG=3p61q4(ds3qKl6%BaO`uCHypY* zV}mT8`yK`}{6vxBy=z+X={SCW7qrm=Us1xNJ0!hV61$@v0Eb2KtyGVz28kPvgs!9H z2iBO7)vdL0$>ZkYxJm_}jKu5fH~c}KCJg2FJ^j?J;!XIr=ubKq4$%vn7-TZY4BXI) zcMo%}krnS_ZpoY}^mN?8wnncN>Z7cT_tuoVR|la^7K@LXa-!b|n%4PbsY`2FN^qVpO& zZw@G)Jc*wChm6ZT!H?( zvoXzqqxK3OTRVBh5$issPi`DES zr$V5zsN%@7F2qw?+<*>-QgZ6lJApG!Hq@GS7h~x}ynUnb4{ZTLJmkgER)@LXf?aof zS{p9jxe%D;uwp5J9zpw0QTaFMxPdNCWc0lX0`9H4?GGu2)!V>*KPSiAUHmtB z5}+0wLb@A}tb1)%GwW03i>VvR)WNRs&f%7D@6?(xfAi!mN1A|ucoIGewGXgu)rTt; z$#=ZBa6a&PFyKh8dvCd}c)ekG6X!J?{yktKK1^2OyoS=?YtY~WV8LDk<{!lJ*Rc5) z$$-6i78-h@9BZG*PP)VAVQ^zDR2t-@S)RYG5R}@s zRyi@XL+%q?d3Peljop^oEW$i0^3b~{=A~t;$uBg-{wQ~AY1w&;s)-a?OXD8xXLmgk zK8C;{c&Q9@#?f@D7IF}oT|+>N@5UD1epgqm@@@mXe_A64qCObDpH6O}M}g1HXLr~| z+o5x2Aq~qLCvL*CwWbV<|J}`cPCf=2nc_wtOcl6SAjN}Sx~Mp#{9t8bL?IV8;Y_eH zq8Z0v48!onw;%ktqWIj%%BUh{*rx3i5&q(QxJ9S1jxqaG4y}jEKV*0C;Mhjp$+}+toJu6BcdI4@9KJ9J%N4NU^aZv3B5dhF<0|0Pe@AzjPb~HCJadKq( zbz%M^A*aRZy(Z+>+LHQIK(m7yx<|raxa6(eoOJ7#TlZ63jIzZqOY(7+N4^`~5yr}S zbS03B_CNT~xo<%qT$R!J9N!1p$`Hy4N4SkRQ7-F+`g-}on&N6ntb|FgU7>f{ho2`B z3%re5KC$U-77;YJ2_-ma@kbrOuP91CjA}$Ju2-7vE?q)f!IP(FQjYBdeL)iw3x^Q~ z*@6yRup>6CTol`!A^Pm4-t5{DOf7vbSY+T58+)FFk}X9>)gcY^yi|<9q|b0E=WwFW zK2wQT43dYgz&-nBzSC*IP1_yO(BgtY5=VM)e05+fPs0;eaoh=qLD_d~@`3K(QG5t#i zB{;6F4mx8uy=^sB-U3>xkwCkwOn>52U&r0UTY{uCZe%Jnae3EF_tSY@X;g*m$fu|^ z%l7kpg(@0IjAP1nI1_o^YtTa>n_tDtsW(W!4CcLqi#{XcHFG%3#y11R5;r-N>GJv? zlynP%9K#gRJr8UOm$*MlqN86G*P`WZN_9a(z*{CM5i)b;jL zGBvLEdd>0J^{vPCraIybmRFk*ryl75{jE!2iTV~jOWESbA|ZX|*aZaI2*Wh#pl~}n zxVX%QU;!W35A+=!>t4cd0t|DC)pA$#oy#oTB}C<=I(_d>^@IuP38biMW48t~8;?WH zn;r`+mDqBx51s~R3`*v9zRk$750wgKi#>a&RDYFbY~L-JuQ~UeYK&A&KdO6I#*Vhm z!s(&#<$QN;ob_OC{s_CN65(t2IEC%@SVhhAN2~O?nMxfS4s+xqVphyH7R(Q35*!QM z)zE}uU>~RNFUC=_h;ajx8>ue8@RcDF6uez44|ehc+b?R7waE3 zgl5>~XM(oMR2JE|CX(euG9jYV!oo%l#7-@I0%>UuOV3vjWf~PwEwQ&DY*AHdB_7gLQJ zH<+Pdf;>~&$kI&MnR_`C`0%~?l#9<>ds8qphza}qIxJ3#w0>+|98)Z10igsk1emx9Jj@X#ND$@_3?t3$`hav_d`J}OVYZ=^3`RP+9F!@;Rf`ygq zQ_N;ko}}jy>c*`4ggq(X-u0K`LWyK344C3N3q$)hd<-G0-kBW^mT|J> zP?JHEu-(#8rL?5#Gh%ZjpODfuLD8BS%1O9zG9U{Gj`WvA=P1Y%!Ny1YfruywcI_}; zI0V&+4k^32zv!$i`4Mg!F?_KYV|N%8r_V*P`#W%T82opgj6QGUCI(O_qCK!| zUC5viLiHJ`pYE&mgJHotm2tiUNlOdb*Hg~{hTx(WrRZCa=}102wH>hcND8gcZat^t z3H1BPXK!YvLK#*&MB%y6TQw;R8LZAobvnR;S)dX*pIilOafiV_Z8^=)QA%{^P~FML zkZ+K?JNftMiRTGYD9!KtmhP{e26l3EG_R~G<6L|+o|>Vq(Q)lX@ z3H89{ODmb{RdB;_TIhWm9DMRsD}4&~Q~_tJaG^IWf3rx0a=!60ety4p8r*AF#K0Rf zyxdd$Mo+6y*5$tYDv%R|Zp~^^BSAGXKfFT@nhIX~aqjyP9F1c|^v2L(n8^NTsomj7 zpU~KF(w)4rPSGAE&cqG{=tIa{&0~H`fS1vP19B`<(+^(C+K=|hUi?|nf+|u|yyobz zDf~A5nE}!2inB*qSK{e+FSK=C65~uFT!hT<1AzqPqrKFG@*ny*i6p#4;#t0%1#X@+ zFvLyRrsB*9=l4Kq>}R7R7t%f7ZM^tBMlZ}fizVnm%jMX%L|rG+uU`hqtPwI|iQS0j zBxQRrmCv!QAqXkz z#JRpl(95S9RZ2L%e<#5uEDFfO_!7-N(aR*YiF4F$N~lfebG!q)_N)bLP^9fY&(M>o zgINr>tiyziA_mRZWn4j_Cc|iP@wQ-#nc!4N3=3Og8}zup!Q_n12vn-kaIgH#4&B^L z1j3F~J$xSKfkBZCd@tmO!d$c;?mEIO?oLz;@1D-| zn%)oN$JU-@AJ8~2G`8o-sgr0~>cJcL;2V;Ot@QX}>={HYT-06yFzZy^+n{@!3XArj z+r~Go^{o!U??^zOCeS@Rcwu_2Iy&-?kh`o`zDC`cGs%y5IP$^^!As?2KuwyiW8NoG zTpES;eopKndHT9_V>s(|J(mnS9v4Xd%>thkJz>xY7?E9Wg5hi(Svu*?v6>|cF;j4o zE1Ze=c+{qH0m$0YrXONHvghW@2;#4OY5?pu_kOE}${NbJeQ$5E7?;=2H_sP65D&$i z<+h<-&DDN$ux0B2`fy?2t?Sfp6)zX+|Mvl$^XCo~PcZ;tTDJ6O%cwhi%g z(M7qXP=i|$IY8rttxA;EUF9v4O?8CRV%z{QU(9}d#`0GVgrViBt!w*sk7tazPADoL z=R;c)mV#Y!O&4AT%^G18z{&n?Xz{}p7S-P4!D0<{GeSYl)(ReinUchY0W@$EoXR_$ zdBohz^s|yVqlA3FtAcx4j%06H`4X+J#3-?9KREUWx(1C;1H69I4<%KW5SE`5^*PZE zbqcE(R;KRsY2?pJw#h_1>k(E(ytzOM3Zh|##$chPysF(>hwmxw5QEG+VvwvhFA4UI z>cn6ZwrJ%#gngiQ-T>F32AHL>xuetH_0!=xTkl{#h&fXW86qn?H6XdFKXixaft&Jq zO96dIlX{|QxE@73%1=7r<)$h_j`0`#+F`}E3a50$&b1~XYc1r0b#Std<>3$G!MCl| zDtSEC?8^|8cFI%+k&OpFDo)-qY)-x4ZlUs z)`q!-iZ4((iT%gnUT0<)-t8gRtR(fKTUVTVPj=oxrWs+4xndYnP#K&rxD}=6` z0%>@u2TIC^W}v>frIEBHT8LR$UQA-90r<}0%FaJ_#jNlj{|-XoxQLW4JYYv`0~d>@2qriTBcAP@Ghc|I&DAKoNIXZv zuU~cd^S9L0NIEh4ie6nlZ{(De4*`%Q5%Z^r(j33UjLZ%b5gn$X94AT^Q*?8T2O^Ip zXqzY&_xe6u&nASyE_X}TCboTq+c1yyBIK7J)~#Dp=q@c4$`J5$xw`l^+-smf#P`nq z-f_Lr!DLZSXjrz<^PyM6*ZZ#d`haPp)9Z)x{EY?g!X=Z><95GYXXjVGh$h(nYrBj# zx5wRwMuX?eo%i8)@?qldyd;fA)2;3on}>~XvjPQ(^6gBSBT$*&DnfQJR6LQfd|XHj zS+>`+4!UrGr+W{fufNLPL?rr67*%FMBX<#S#wy9LE?9BS$>(#Vq$vIigv3@>TY`tS%wdCQh!f%-byr1GGEVbZI<&-^h%R zhGQZkgy|wGVsNi&>d*pI396SmPo41UBU^%_WhvUQL$rrbnAv;W@E9P&Y#4)S3Ou^* zVnVDbP!hjs4&F3>l$`*Y$ou+pH!kTafZDXDEB$*GXF3#;szYU=xa%HPMIycp5%D0B zV@{N(NuhsRRJbRNr*W_af`<5MTjna%mt!-#YCB*}WewqE(_PC3FIZT5G;x3ud!mZ#$&{S?G3 zB^$cgafkNLsS@_db{L4OZ^gb2NG0D#gdCCxJ8Euf%yBpkI4-cBjp7A6cUr0w7z3q{q} z3fb{yQx6?ru|8N;n5LTG=WmI-EzWKZPFlt7;sr?P+Re!nbu1B+kOyT%25jIec*kUHB98RTW3-Byx`PvkYq(@4~hrPc0_dV`-|z3
KlGuhl>b0ox#)#m98H_=c~7~(zv(V*3QCzvMt65h+NOYc^sSH^gAY`ty(A!)}ORX z^tZ%KY|A41#;KDZq*3bJCSJI7_YURC1Tlg)E}9n`Rlncr&h9^Qn7ROA$o1<4Ssobx zK>7!VIXbypn>hY4;~Uk`vCHMc^f9n_fwfAo)- zp=@1#=@{gIoVF;mFgrlc1HfdixFTnwgu5b{twSiWCywm(vxjU(P9na+s-i- z(TmwD5m1i%b0JcL4XToufyS?8BJ9hac~yNF-2>Uzl*MnRmW7EX4$fVL>Do+CEiocL zoUu=d_Uq@qqgH=RCYE=kw9-1FuOb+#$>VQVQW_?yVPbYU9I% z@hYnD*Xf2pOf*%r>k{ z=*BC5mml1mg-r~9v^xi5y}(W0Yc^JP$4`Oq(hluPYr?V`Jych!E$d6G$;B8HH+(AA z8>CfTcyu9JeVH%w=t)|(QHeKM8oQxFv~k(!$ws<*qP}|p$6G`Pe~hnId5Vyw6spqg zN-YRQ$6Iwfz4=+~{|=r6N&}_!EO3q|Fj^^1w?a`y908->)~2NHbq~P{!<(7!5|lbWqaO5`;K|@;M{UAgO~kx=OLU zm6hXkR~SY+4!eOqNn<3Rhc32{Suazr`r}EW&Fr}>T4PuN75Uw@l4LUDOn#3;i`WFys^v6f4x#QjI;UF zd2B+9zuI(hDhdFhywB{9yPygc79a@xEReBTERY_7E+CWR#)?;I8}}BkFQe@egA_4m z$lLnMW?XYY8j!9<+ZhXAI$P5IDUiay0$u2ZwCTsM!ufaQLYRj?0sO0K;Q7&nG%BSG zio!AHjoQlPq=+aD#Ok&loMf+@`wXH4JplqF7$<$LuW?KLY+w`?GGfjdytZ~!#ON17 zinMi0`{bTmI-z4XU{Y?_}$%(&^} zg)^nIuwrnME6PK0BO{R7$^zQDw|;jTQlH(`jI%WxO@kR_L_X*1uP7XY zti2oc_(TQOFc#sdWHmf>9}C8XE>?c*)!qHU=BFo;z3hF^49E9FOjEfgV-QOIIONoJfc zOumgPem9LJpWD1glZ{2X2%h0yn)4*rN{GAw0hs3v!BgM!38OU;ttT5#X*RmF=PkM; zQE`nak#~iaOoJEK@QzdpsNK`W;G2SQw*A^%;x=psCC2A_GxQ{~%@7}N1*SV7^fDcW zO&fcpx-yg^<`-@m_MRgm?@|cWaeUNCB%M;RJ{bCwMF*Jo&r3jNqMIRPf&_78Cdf-p z-p#1YDCreyF4#43(eYYfsz|8v<*aD#cga1gzz*1Q=?i62C^cF*{>7>=g{QTNDVZX8q+Ux%0>m*0f!Oq^1>AjtU$v;(U|J{~+eRN*R z{~JKKEj%@8`Bs5_Pz~9ISh$mz1THf)9CMa3{(ZA!8ww4&)Oca*-|vZtho~zm4O$x9 z22dkw38=8K=qLSW2F^H6d^}b3Ysg}2wCl&WgWdpZj`w$h+1=n6H={Rsp;>Ulca3u) zJ3-N+`VK@}XgpXrQXq_nP2obC?y4RA?zm;r%F-i$S4nAlWiQ~{pC?09GDx-g8^JDY zlU>#Q$P8kP*#5-z0ombp91)|THLOk-uZ+K{VuzHRHpetI+S754^V5Nle2nS7a^>80 zrzF2xMEzr#mQ{>#QNQU}^u+hDCjnnHnk2IrSlFVWK-p1}{vQFs>rihn9D|gj2VlG1 zq%)JA#^I&bN+cVdUF@CkI$4XN6N_4*QT>MeyD@lpdVo~7w=2(qZl(wQ_PB~1WMn8kA1D|b&l&&Wo zVa5lTdnwM4Pu@%}_CzaWZx=7m)co)&J^A-8pEy-7{QPy9!d`tD^{;*mvbX=Q8^1o7 ze;#Que@d8W?%44pF;b>wcVu*_xFXhaXHUtrd8B)0pFI&Vpg!!HKGhR>l1xFbV!eqX)v5;4fW z*$7X|f)=U5KINU54FT_JoG&pTyk*)W-|+26B(`uah`r<{{)87{f~(BuE0BN~hxHi8 z0Kc2Yy>u%AzfwU%Nbr;hQ${rktoY9}W}e`RPsWicxjgtey`eIW-}~qpe`E9;+^uk=`t4nl8V)7if}Nelp~s^HZ@)?Uf-l}Z2DHMcQw0vUsxK!5m% zv674e0W)fc#`f!8xm_D6rf^*=4fviYr7F$QT=ih6TBot6g}T~{r&)UdG}qD&p6@kZ zO6n)}T_~gJ^?E1%Qd%(TwprVSX<~&`ODUS0f6VW}U8GapBpK!A#PvLZ!DxTiuKoUhwima60|+7FPyiuj8rD z%j3gdD*b*wp1d3l$6(_&qP)MsHSuhy@qT z&IeX`@7glr!UKm6c^k)+37gjMdU!6agEa;aO})>Wvh_114*mHq26|KF%DN8&COe$5 zLUq!1t{Zk*u^+@sLv#|2)28S)6Dje`>aLnhq$=jGUMjm75TMX9q&94fQRS?N1w^r9 z2(ziP_&Fk2D#`r>OT~!vkgFpR z)o9P&>hSFktmv0WjI}dFdbFKhsvG)VV&g|~_q61EIRq3CY9Z8M8#4@P$JCQp3C+h= zO#uCjS7LMJ$RyI>T=E^N>W8+}D{`6}zw&gvyJ`*GH`GaoIAON`nLQ?w8r(3{_=SC2 zbil6%)-mc!-B=C(a|lg2B2UGthh~mHEE|oQP_w^epT9qBNgfIM))WbP)4V3rcO@an zH4W76QJN4)T-*>tek@Vo96Z?~6x3&%&IV#5)NXaU5W^%a(MEE=Izk~m-9QzXr0Kmfk0{HN9{ch#=Df*vQ!tj1s`F*DT zyMf^J@jLo=*7*~kK>zFO|CNJ&2mhYm{sfyd`~v?i+bPOGy*d^EfcW})dL_9~ Ira%7rAG}}rbN~PV literal 0 HcmV?d00001