Преобразование числа со знаком в число без знака не останется отрицательным, оно не может, так как допустимый диапазон типов без знака не включает отрицательные числа. Если вы напечатаете uint(interval)
, вы обязательно увидите положительное число.
То, что вы испытываете, детерминировано, и вы можете на него положиться (но это не значит, что вы должны это делать). Это результат того, что Go (и большинство других языков программирования) хранит целочисленные типы со знаком с использованием дополнения 2< /а> представление.
Это означает, что в случае отрицательных чисел с использованием бита n
значение -x
(где x
является положительным) сохраняется как двоичное представление положительного значения 2^n - x
. Преимущество этого заключается в том, что числа можно добавлять побитно, и результат будет правильным независимо от того, отрицательные они или положительные.
Итак, когда у вас есть отрицательное число со знаком, оно в основном хранится в памяти, как если бы вы вычли его абсолютное значение из 0
. Это означает, что если вы преобразуете отрицательное значение со знаком в беззнаковое и добавляете его к беззнаковому значению, результат будет правильным, потому что произойдет переполнение полезным способом.
Преобразование значения типа int64
в uint64
не изменяет макет памяти, а только тип. Итак, какие 8 байтов были у int64
, у преобразованного uint64
будут те же самые 8 байт. И, как упоминалось выше, представление, хранящееся в этих 8 байтах, представляет собой битовую комбинацию, идентичную битовой комбинации значения 0 - abs(x)
. Таким образом, результатом преобразования будет число, которое вы получите, если вычтете abs(x)
из 0
в беззнаковом мире. Да, это не будет отрицательным значением (поскольку тип беззнаковый), вместо этого это будет «большое» число, отсчитываемое от максимального значения типа uint64
. Но если вы добавите к этому «большому» числу число y
больше, чем abs(x)
, произойдет переполнение, и результат будет похож на y - abs(x)
.
Посмотрите этот простой пример, демонстрирующий, что происходит (попробуйте его на Go Playground):
a := uint8(100)
b := int8(-10)
fmt.Println(uint8(b)) // Prints 226 which is: 0 - 10 = 256 - 10
a = a + uint8(b)
fmt.Println(a) // Prints 90 which is: 100 + 226 = 326 = 90
// after overflow: 326 - 256 = 90
Как было сказано выше, не стоит на это полагаться, так как это может вызвать путаницу. Если вы собираетесь работать с отрицательными числами, используйте подписанные типы.
А если вы работаете с кодовой базой, которая уже использует значения uint64
, то вместо сложения сделайте вычитание, используя значения uint64
:
interval := uint64(3600)
endTime -= interval
Также обратите внимание, что если у вас есть значения time.Time
, вы должны воспользоваться его Time.Add()
:
func (t Time) Add(d Duration) Time
Вы можете указать time.Duration
, чтобы добавить ко времени, которое может быть отрицательным, если вы хотите вернуться в прошлое, вот так:
t := time.Now()
t = t.Add(-3600 * time.Second)
time.Duration
более выразительно: мы видим, что указанное выше значение явно использует секунды.
person
icza
schedule
12.06.2018
uint(a)
— это не приведение типов в Go, а преобразование типов, которое просто переинтерпретирует биты a как uint. Теперь uint — это просто битовые шаблоны, которые не являются ни положительными, ни отрицательными, и добавление их — это битовая операция, приводящая к новому битовому шаблону, если вы интерпретируете этот битовый шаблон как целое число со знаком, тогда вы увидите, что происходит вычитание, потому что, ну, вот как два дополняют произведения. Совет: не используйте uint для чисел, если вы не чувствуете себя комфортно с дополнением до двух. - person Volker   schedule 12.06.2018