Skip to content

Commit 750024a

Browse files
authored
Clarify formatting decimals, add %f formatting test cases around rounding (#485)
* Clarify rounding mode, add %f test cases around rounding * Add test cases for round to evens
1 parent 9dd5f5c commit 750024a

File tree

2 files changed

+75
-3
lines changed

2 files changed

+75
-3
lines changed

doc/extensions/strings.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ Optional. In the form of a period `.` followed by a required positive decimal di
3030

3131
| Character | Precision | Description |
3232
| --- | --- | --- |
33-
| `s` | N | <table><tbody><tr><td><code>bool</code></td><td>The value is foramtted as <code>true</code> or <code>false</code>.</td></tr><tr><td><code>int</code></td><td>The value is formatted in base 10 with a preceding <code>-</code> if the value is negative. No insignificant <code>0</code>s must be included.</td></tr><tr><td><code>uint</code></td><td>The value is formatted in base 10. No insignificant <code>0</code>s must be included.</td></tr><tr><td><code>double</code></td><td>The value is formatted in base 10. No insignificant <code>0</code>s must be included. If there are no significant digits after the <code>.</code> then it must be excluded.</td></tr><tr><td><code>bytes</code></td><td>The value is formatted as if `string(value)` was performed and any invalid UTF-8 sequences are replaced with <code>\ufffd</code>. Multiple adjacent invalid UTF-8 sequences must be replaced with a single <code>\ufffd</code>.</td></tr><tr><td><code>string</code></td><td>The value is included as is.</td></tr><tr><td><code>duration</code></td><td>The value is formatted as decimal seconds as if the value was converted to <code>double</code> and then formatted as <code>%ds</code>.</td></tr><tr><td><code>timestamp</code></td><td>The value is formatted according to RFC 3339 and is always in UTC.</td></tr><tr><td><code>null_type</code></td><td>The value is formatted as <code>null</code>.</td></tr><tr><td><code>type</code></td><td>The value is formatted as a string.</td></tr><tr><td><code>list</code></td><td>The value is formatted as if each element was formatted as <code>"%s".format([element])</code>, joined together with <code>, </code> and enclosed with <code>[</code> and <code>]</code>.</td></tr><tr><td><code>map</code></td><td>The value is formatted as if each entry was formatted as <code>"%s: %s".format([key, value])</code>, sorted by the formatted keys in ascending order, joined together with <code>, </code>, and enclosed with <code>{</code> and <code>}</code>.</td></tr></tbody></table> |
34-
| `d` | N | <table><tbody><tr><td><code>int</code></td><td>The value is formatted in base 10 with a preceding <code>-</code> if the value is negative. No insignificant <code>0</code>s must be included.</td></tr><tr><td><code>uint</code></td><td>The value is formatted in base 10. No insignificant <code>0</code>s must be included.</td></tr><tr><td><code>double</code></td><td>The value is formatted in base 10. No insignificant <code>0</code>s must be included. If there are no significant digits after the <code>.</code> then it must be excluded.</td></tr></tbody></table> |
35-
| `f` | Y | `int` `uint` `double`: The value is converted to the style `[-]dddddd.dddddd` where there is at least one digit before the decimal and exactly `precision` digits after the decimal. If `precision` is 0, then the decimal is excluded. |
33+
| `s` | N | <table><tbody><tr><td><code>bool</code></td><td>The value is formatted as <code>true</code> or <code>false</code>.</td></tr><tr><td><code>int</code></td><td>The value is formatted in base 10 with a preceding <code>-</code> if the value is negative. Insignificant <code>0</code>s must be excluded.</td></tr><tr><td><code>uint</code></td><td>The value is formatted in base 10. Insignificant <code>0</code>s must be excluded.</td></tr><tr><td><code>double</code></td><td>The value is formatted in base 10. Insignificant <code>0</code>s must be excluded. If there are no significant digits after the <code>.</code> then it must be excluded.</td></tr><tr><td><code>bytes</code></td><td>The value is formatted as if `string(value)` was performed and any invalid UTF-8 sequences are replaced with <code>\ufffd</code>. Multiple adjacent invalid UTF-8 sequences must be replaced with a single <code>\ufffd</code>.</td></tr><tr><td><code>string</code></td><td>The value is included as is.</td></tr><tr><td><code>duration</code></td><td>The value is formatted as decimal seconds as if the value was converted to <code>double</code> and then formatted as <code>%ds</code>.</td></tr><tr><td><code>timestamp</code></td><td>The value is formatted according to RFC 3339 and is always in UTC.</td></tr><tr><td><code>null_type</code></td><td>The value is formatted as <code>null</code>.</td></tr><tr><td><code>type</code></td><td>The value is formatted as a string.</td></tr><tr><td><code>list</code></td><td>The value is formatted as if each element was formatted as <code>"%s".format([element])</code>, joined together with <code>, </code> and enclosed with <code>[</code> and <code>]</code>.</td></tr><tr><td><code>map</code></td><td>The value is formatted as if each entry was formatted as <code>"%s: %s".format([key, value])</code>, sorted by the formatted keys in ascending order, joined together with <code>, </code>, and enclosed with <code>{</code> and <code>}</code>.</td></tr></tbody></table> |
34+
| `d` | N | <table><tbody><tr><td><code>int</code></td><td>The value is formatted in base 10 with a preceding <code>-</code> if the value is negative. Insignificant <code>0</code>s must be excluded.</td></tr><tr><td><code>uint</code></td><td>The value is formatted in base 10. Insignificant <code>0</code>s must be excluded.</td></tr></tbody></table> |
35+
| `f` | Y | `int` `uint` `double`: The value is converted to the style `[-]dddddd.dddddd` where there is at least one digit before the decimal and exactly `precision` digits after the decimal. If `precision` is 0, then the decimal is excluded. The value is rounded to the specified precision using the "round half to even" method (banker's rounding), where values exactly halfway between two numbers are rounded to the nearest even digit.
3636
| `e` | Y | `int` `uint` `double`: The value is converted to the style `[-]d.dddddde±dd` where there is one digit before the decimal and `precision` digits after the decimal followed by `e`, then the plus or minus, and then two digits. |
3737
| `x` `X` | N | Values are formatted in base 16. For `x` lowercase letters are used. For `X` uppercase letters are used.<table><tbody><tr><td><code>int</code> <code>uint</code></td><td>The value is formatted in base 16 with no insignificant digits. If the value was negative <code>-</code> is prepended.</td></tr><tr><td><code>string</code></td><td>The value is formatted as if `bytes(value)` was used to convert the <code>string</code> to <code>bytes</code> and then each byte is formatted in base 16 with exactly 2 digits.</td></tr><tr><td><code>bytes</code></td><td>The value is formatted as if each byte is formatted in base 16 with exactly 2 digits.</td></tr></tbody></table> |
3838
| `o` | N | `int` `uint`: The value is converted to base 8 with no insignificant digits. If the value was negative `-` is prepended. |
@@ -57,4 +57,6 @@ Optional. In the form of a period `.` followed by a required positive decimal di
5757
"%e".format([3.14]) // 3.140000e+00
5858
"%.1e".format([3.14]) // 3.1e+00
5959
"%.1e".format([-3.14]) // -3.1e+00
60+
'%.3f'.format([123.4999]) // 123.500
61+
'%.3f'.format([123.4994]) // 123.499
6062
```

tests/simple/testdata/string_ext.textproto

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,76 @@ section: {
888888
string_value: '2.718280',
889889
}
890890
}
891+
test: {
892+
name: "format_%f_insignificant_zeroes_removed"
893+
expr: '"%.0f".format([123.000000])'
894+
value: {
895+
string_value: '123',
896+
}
897+
}
898+
test: {
899+
name: "format_%f_positive_round_to_whole_number"
900+
expr: '"%.0f".format([3.5001])'
901+
value: {
902+
string_value: '4',
903+
}
904+
}
905+
test: {
906+
name: "format_%f_negative_truncate_to_whole_number"
907+
expr: '"%.0f".format([3.4999])'
908+
value: {
909+
string_value: '3',
910+
}
911+
}
912+
test: {
913+
name: "format_%f_halfway_round_up_to_nearest_even"
914+
expr: '"%.0f".format([1.5])'
915+
value: {
916+
string_value: '2',
917+
}
918+
}
919+
test: {
920+
name: "format_%f_halfway_truncate_to_nearest_even"
921+
expr: '"%.0f".format([2.5])'
922+
value: {
923+
string_value: '2',
924+
}
925+
}
926+
test: {
927+
name: "format_%f_positive_round_up"
928+
expr: '"%.3f".format([123.4999])'
929+
value: {
930+
string_value: '123.500',
931+
}
932+
}
933+
test: {
934+
name: "format_%f_positive_round_down"
935+
expr: '"%.3f".format([123.4994])'
936+
value: {
937+
string_value: '123.499',
938+
}
939+
}
940+
test: {
941+
name: "format_%f_negative_round_up"
942+
expr: '"%.3f".format([-123.4999])'
943+
value: {
944+
string_value: '-123.500',
945+
}
946+
}
947+
test: {
948+
name: "format_%f_negative_round_down"
949+
expr: '"%.3f".format([-123.4994])'
950+
value: {
951+
string_value: '-123.499',
952+
}
953+
}
954+
test: {
955+
name: "format_%f_zero_padding"
956+
expr: '"%.5f".format([-1.2])'
957+
value: {
958+
string_value: '-1.20000',
959+
}
960+
}
891961
}
892962
section: {
893963
name: "format_errors"

0 commit comments

Comments
 (0)