@@ -229,6 +229,7 @@ pub struct FormatSpec {
229229 align : Option < FormatAlign > ,
230230 align_specified : bool ,
231231 sign : Option < FormatSign > ,
232+ no_neg_0 : bool ,
232233 alternate_form : bool ,
233234 width : Option < usize > ,
234235 grouping_option : Option < FormatGrouping > ,
@@ -285,6 +286,14 @@ fn parse_alternate_form(text: &Wtf8) -> (bool, &Wtf8) {
285286 }
286287}
287288
289+ fn parse_no_negative_zero ( text : & Wtf8 ) -> ( bool , & Wtf8 ) {
290+ let mut chars = text. code_points ( ) ;
291+ match chars. next ( ) . and_then ( CodePoint :: to_char) {
292+ Some ( 'z' ) => ( true , chars. as_wtf8 ( ) ) ,
293+ _ => ( false , text) ,
294+ }
295+ }
296+
288297fn parse_zero ( text : & Wtf8 ) -> ( bool , & Wtf8 ) {
289298 let mut chars = text. code_points ( ) ;
290299 match chars. next ( ) . and_then ( CodePoint :: to_char) {
@@ -349,6 +358,7 @@ impl FormatSpec {
349358 let ( mut fill, mut align, text) = parse_fill_and_align ( text) ;
350359 let align_specified = align. is_some ( ) ;
351360 let ( sign, text) = FormatSign :: parse ( text) ;
361+ let ( no_neg_0, text) = parse_no_negative_zero ( text) ;
352362 let ( alternate_form, text) = parse_alternate_form ( text) ;
353363 let ( zero, text) = parse_zero ( text) ;
354364 let ( width, text) = parse_number ( text) ?;
@@ -378,6 +388,7 @@ impl FormatSpec {
378388 align,
379389 align_specified,
380390 sign,
391+ no_neg_0,
381392 alternate_form,
382393 width,
383394 grouping_option,
@@ -505,6 +516,25 @@ impl FormatSpec {
505516 Ok ( ( ) )
506517 }
507518
519+ fn formatted_magnitude_is_zero ( magnitude : & str ) -> bool {
520+ let mut saw_digit = false ;
521+ for byte in magnitude. bytes ( ) {
522+ if byte. is_ascii_digit ( ) {
523+ saw_digit = true ;
524+ if byte != b'0' {
525+ return false ;
526+ }
527+ }
528+ }
529+ saw_digit
530+ }
531+
532+ fn is_negative_after_zero_coercion ( & self , num : f64 , magnitude : & str ) -> bool {
533+ num. is_sign_negative ( )
534+ && !num. is_nan ( )
535+ && !( self . no_neg_0 && Self :: formatted_magnitude_is_zero ( magnitude) )
536+ }
537+
508538 fn validate_complex_padding_and_alignment ( & self ) -> Result < ( ) , FormatSpecError > {
509539 match & self . fill . unwrap_or_else ( || ' ' . into ( ) ) . to_char ( ) {
510540 Some ( '0' ) => Err ( FormatSpecError :: ZeroPadding ) ,
@@ -674,6 +704,9 @@ impl FormatSpec {
674704 Some ( FormatType :: Number ( Case :: Lower ) ) => self . format_int_radix ( magnitude, 10 ) ,
675705 _ => return self . format_int ( num) ,
676706 } ?;
707+ if self . no_neg_0 {
708+ return Err ( FormatSpecError :: NegativeZeroCoercionNotAllowed ( "integer" ) ) ;
709+ }
677710
678711 let magnitude_str = Self :: apply_locale_formatting ( raw_magnitude_str, locale) ;
679712
@@ -723,7 +756,7 @@ impl FormatSpec {
723756 let magnitude_str = Self :: apply_locale_formatting ( raw_magnitude_str, locale) ;
724757
725758 let format_sign = self . sign . unwrap_or ( FormatSign :: Minus ) ;
726- let sign_str = if num . is_sign_negative ( ) && !num . is_nan ( ) {
759+ let sign_str = if self . is_negative_after_zero_coercion ( num , & magnitude_str ) {
727760 "-"
728761 } else {
729762 match format_sign {
@@ -812,11 +845,16 @@ impl FormatSpec {
812845 self . format_float ( x as f64 )
813846 }
814847 None => {
848+ if self . no_neg_0 {
849+ return Err ( FormatSpecError :: NegativeZeroCoercionNotAllowed ( "integer" ) ) ;
850+ }
815851 let first_letter = ( input. to_string ( ) . as_bytes ( ) [ 0 ] as char ) . to_uppercase ( ) ;
816852 Ok ( first_letter. collect :: < String > ( ) + & input. to_string ( ) [ 1 ..] )
817853 }
818- Some ( FormatType :: Unknown ( c) ) => Err ( FormatSpecError :: UnknownFormatCode ( * c, "int" ) ) ,
819- _ => Err ( FormatSpecError :: InvalidFormatSpecifier ) ,
854+ Some ( format_type) => {
855+ let ch = char:: from ( format_type) ;
856+ Err ( FormatSpecError :: UnknownFormatCode ( ch, "bool" ) )
857+ }
820858 }
821859 }
822860
@@ -925,8 +963,9 @@ impl FormatSpec {
925963 } ,
926964 } ,
927965 } ;
966+ let raw_magnitude_str = raw_magnitude_str?;
928967 let format_sign = self . sign . unwrap_or ( FormatSign :: Minus ) ;
929- let sign_str = if num . is_sign_negative ( ) && !num . is_nan ( ) {
968+ let sign_str = if self . is_negative_after_zero_coercion ( num , & raw_magnitude_str ) {
930969 "-"
931970 } else {
932971 match format_sign {
@@ -935,7 +974,7 @@ impl FormatSpec {
935974 FormatSign :: MinusOrSpace => " " ,
936975 }
937976 } ;
938- let magnitude_str = self . add_magnitude_separators ( raw_magnitude_str? , sign_str) ;
977+ let magnitude_str = self . add_magnitude_separators ( raw_magnitude_str, sign_str) ;
939978 let magnitude_str = self . add_frac_separators ( magnitude_str) ;
940979 Ok (
941980 self . format_sign_and_align (
@@ -986,15 +1025,24 @@ impl FormatSpec {
9861025 Err ( FormatSpecError :: UnknownFormatCode ( 'N' , "int" ) )
9871026 }
9881027 Some ( FormatType :: String ) => Err ( FormatSpecError :: UnknownFormatCode ( 's' , "int" ) ) ,
989- Some ( FormatType :: Character ) => match ( self . precision , self . sign , self . alternate_form ) {
990- ( Some ( _) , _, _) => Err ( FormatSpecError :: PrecisionNotAllowed ) ,
991- ( _, Some ( _) , _) => Err ( FormatSpecError :: NotAllowed ( "Sign" ) ) ,
992- ( _, _, true ) => Err ( FormatSpecError :: NotAllowed ( "Alternate form (#)" ) ) ,
993- ( _, _, _) => match num. to_u32 ( ) {
994- Some ( n) if n <= 0x10ffff => Ok ( core:: char:: from_u32 ( n) . unwrap ( ) . to_string ( ) ) ,
995- Some ( _) | None => Err ( FormatSpecError :: CodeNotInRange ) ,
996- } ,
997- } ,
1028+ Some ( FormatType :: Character ) => {
1029+ if self . precision . is_some ( ) {
1030+ Err ( FormatSpecError :: PrecisionNotAllowed )
1031+ } else if self . no_neg_0 {
1032+ Err ( FormatSpecError :: NegativeZeroCoercionNotAllowed ( "integer" ) )
1033+ } else {
1034+ match ( self . sign , self . alternate_form ) {
1035+ ( Some ( _) , _) => Err ( FormatSpecError :: NotAllowed ( "Sign" ) ) ,
1036+ ( _, true ) => Err ( FormatSpecError :: NotAllowed ( "Alternate form (#)" ) ) ,
1037+ _ => match num. to_u32 ( ) {
1038+ Some ( n) if n <= 0x10ffff => {
1039+ Ok ( core:: char:: from_u32 ( n) . unwrap ( ) . to_string ( ) )
1040+ }
1041+ Some ( _) | None => Err ( FormatSpecError :: CodeNotInRange ) ,
1042+ } ,
1043+ }
1044+ }
1045+ }
9981046 Some (
9991047 FormatType :: GeneralFormat ( _)
10001048 | FormatType :: FixedPoint ( _)
@@ -1007,6 +1055,9 @@ impl FormatSpec {
10071055 Some ( FormatType :: Unknown ( c) ) => Err ( FormatSpecError :: UnknownFormatCode ( c, "int" ) ) ,
10081056 None => self . format_int_radix ( magnitude, 10 ) ,
10091057 } ?;
1058+ if self . no_neg_0 {
1059+ return Err ( FormatSpecError :: NegativeZeroCoercionNotAllowed ( "integer" ) ) ;
1060+ }
10101061 let format_sign = self . sign . unwrap_or ( FormatSign :: Minus ) ;
10111062 let sign_str = match num. sign ( ) {
10121063 Sign :: Minus => "-" ,
@@ -1032,6 +1083,9 @@ impl FormatSpec {
10321083 self . validate_format ( FormatType :: String ) ?;
10331084 match self . format_type {
10341085 Some ( FormatType :: String ) | None => {
1086+ if self . no_neg_0 {
1087+ return Err ( FormatSpecError :: NegativeZeroCoercionNotAllowed ( "string" ) ) ;
1088+ }
10351089 if self . align == Some ( FormatAlign :: AfterSign ) && self . align_specified {
10361090 return Err ( FormatSpecError :: StringAlignmentFlag ) ;
10371091 }
@@ -1074,7 +1128,8 @@ impl FormatSpec {
10741128 // Format real part
10751129 let formatted_re =
10761130 if num. re != 0.0 || num. re . is_negative_zero ( ) || self . format_type . is_some ( ) {
1077- let sign_re = if num. re . is_sign_negative ( ) && !num. is_nan ( ) {
1131+ let re = self . format_complex_float ( num. re ) ?;
1132+ let sign_re = if self . is_negative_after_zero_coercion ( num. re , & re) {
10781133 "-"
10791134 } else {
10801135 match self . sign . unwrap_or ( FormatSign :: Minus ) {
@@ -1083,21 +1138,24 @@ impl FormatSpec {
10831138 FormatSign :: MinusOrSpace => " " ,
10841139 }
10851140 } ;
1086- let re = self . format_complex_float ( num. re ) ?;
10871141 format ! ( "{sign_re}{re}" )
10881142 } else {
10891143 String :: new ( )
10901144 } ;
10911145
10921146 // Format imaginary part
1093- let sign_im = if num. im . is_sign_negative ( ) && !num. im . is_nan ( ) {
1147+ let im = self . format_complex_float ( num. im ) ?;
1148+ let sign_im = if self . is_negative_after_zero_coercion ( num. im , & im) {
10941149 "-"
10951150 } else if formatted_re. is_empty ( ) {
1096- ""
1151+ match self . sign . unwrap_or ( FormatSign :: Minus ) {
1152+ FormatSign :: Plus => "+" ,
1153+ FormatSign :: Minus => "" ,
1154+ FormatSign :: MinusOrSpace => " " ,
1155+ }
10971156 } else {
10981157 "+"
10991158 } ;
1100- let im = self . format_complex_float ( num. im ) ?;
11011159 Ok ( ( formatted_re, format ! ( "{sign_im}{im}j" ) ) )
11021160 }
11031161
@@ -1274,6 +1332,7 @@ pub enum FormatSpecError {
12741332 CodeNotInRange ,
12751333 ZeroPadding ,
12761334 AlignmentFlag ,
1335+ NegativeZeroCoercionNotAllowed ( & ' static str ) ,
12771336 StringAlignmentFlag ,
12781337 NotImplemented ( char , & ' static str ) ,
12791338}
@@ -1625,6 +1684,7 @@ mod tests {
16251684 align : None ,
16261685 align_specified : false ,
16271686 sign : None ,
1687+ no_neg_0 : false ,
16281688 alternate_form : false ,
16291689 width : Some ( 33 ) ,
16301690 grouping_option : None ,
@@ -1643,6 +1703,7 @@ mod tests {
16431703 align : Some ( FormatAlign :: Right ) ,
16441704 align_specified : true ,
16451705 sign : None ,
1706+ no_neg_0 : false ,
16461707 alternate_form : false ,
16471708 width : Some ( 33 ) ,
16481709 grouping_option : None ,
@@ -1661,6 +1722,7 @@ mod tests {
16611722 align : Some ( FormatAlign :: Right ) ,
16621723 align_specified : true ,
16631724 sign : Some ( FormatSign :: Minus ) ,
1725+ no_neg_0 : false ,
16641726 alternate_form : true ,
16651727 width : Some ( 23 ) ,
16661728 grouping_option : Some ( FormatGrouping :: Comma ) ,
@@ -1779,6 +1841,92 @@ mod tests {
17791841 ) ;
17801842 }
17811843
1844+ #[ test]
1845+ fn format_negative_zero_coercion ( ) {
1846+ let int_spec = FormatSpec :: parse ( "z8" ) . unwrap ( ) ;
1847+ assert_eq ! (
1848+ int_spec. format_int( & BigInt :: from( -42 ) ) ,
1849+ Err ( FormatSpecError :: NegativeZeroCoercionNotAllowed ( "integer" ) )
1850+ ) ;
1851+ assert_eq ! (
1852+ FormatSpec :: parse( "zs" )
1853+ . unwrap( )
1854+ . format_int( & BigInt :: from( 0 ) ) ,
1855+ Err ( FormatSpecError :: UnknownFormatCode ( 's' , "int" ) )
1856+ ) ;
1857+ assert_eq ! (
1858+ FormatSpec :: parse( "z.1d" )
1859+ . unwrap( )
1860+ . format_int( & BigInt :: from( 0 ) ) ,
1861+ Err ( FormatSpecError :: PrecisionNotAllowed )
1862+ ) ;
1863+ assert_eq ! (
1864+ FormatSpec :: parse( "+zc" )
1865+ . unwrap( )
1866+ . format_int( & BigInt :: from( 0 ) ) ,
1867+ Err ( FormatSpecError :: NegativeZeroCoercionNotAllowed ( "integer" ) )
1868+ ) ;
1869+
1870+ let float_spec = FormatSpec :: parse ( "z.2f" ) . unwrap ( ) ;
1871+ assert_eq ! ( float_spec. format_float( -0.0001 ) , Ok ( "0.00" . to_owned( ) ) ) ;
1872+
1873+ let complex_spec = FormatSpec :: parse ( "z" ) . unwrap ( ) ;
1874+ assert_eq ! (
1875+ complex_spec. format_complex( & Complex64 :: new( -0.0 , -0.0 ) ) ,
1876+ Ok ( "(0+0j)" . to_owned( ) )
1877+ ) ;
1878+ let pure_imaginary = Complex64 :: new ( 0.0 , -0.0 ) ;
1879+ assert_eq ! (
1880+ FormatSpec :: parse( "+z" )
1881+ . unwrap( )
1882+ . format_complex( & pure_imaginary) ,
1883+ Ok ( "+0j" . to_owned( ) )
1884+ ) ;
1885+ assert_eq ! (
1886+ FormatSpec :: parse( " z" )
1887+ . unwrap( )
1888+ . format_complex( & pure_imaginary) ,
1889+ Ok ( " 0j" . to_owned( ) )
1890+ ) ;
1891+
1892+ let string_value = "value" . to_owned ( ) ;
1893+ assert_eq ! (
1894+ FormatSpec :: parse( "z" ) . unwrap( ) . format_string( & string_value) ,
1895+ Err ( FormatSpecError :: NegativeZeroCoercionNotAllowed ( "string" ) )
1896+ ) ;
1897+ assert_eq ! (
1898+ FormatSpec :: parse( "zd" )
1899+ . unwrap( )
1900+ . format_string( & string_value) ,
1901+ Err ( FormatSpecError :: UnknownFormatCode ( 'd' , "str" ) )
1902+ ) ;
1903+ assert_eq ! (
1904+ FormatSpec :: parse( "zs" ) . unwrap( ) . format_bool( false ) ,
1905+ Err ( FormatSpecError :: UnknownFormatCode ( 's' , "bool" ) )
1906+ ) ;
1907+
1908+ let locale = LocaleInfo {
1909+ thousands_sep : "," . to_owned ( ) ,
1910+ decimal_point : "." . to_owned ( ) ,
1911+ grouping : vec ! [ 3 , 0 ] ,
1912+ } ;
1913+ let locale_spec = FormatSpec :: parse ( "zn" ) . unwrap ( ) ;
1914+ assert_eq ! (
1915+ locale_spec. format_float_locale( -0.0 , & locale) ,
1916+ Ok ( "0" . to_owned( ) )
1917+ ) ;
1918+ assert_eq ! (
1919+ locale_spec. format_complex_locale( & Complex64 :: new( -0.0 , -0.0 ) , & locale) ,
1920+ Ok ( "0+0j" . to_owned( ) )
1921+ ) ;
1922+ assert_eq ! (
1923+ FormatSpec :: parse( "z.1n" )
1924+ . unwrap( )
1925+ . format_int_locale( & BigInt :: from( 0 ) , & locale) ,
1926+ Err ( FormatSpecError :: PrecisionNotAllowed )
1927+ ) ;
1928+ }
1929+
17821930 #[ test]
17831931 fn format_int ( ) {
17841932 assert_eq ! (
0 commit comments