При преобразовании int64 в uint64 знак сохраняется?

У меня есть переменная int64, содержащая отрицательное число, и я хочу вычесть ее из переменной uint64, содержащей положительное число:

var endTime uint64
now := time.Now().Unix()
endTime = uint64(now)
var interval int64
interval = -3600
endTime = endTime + uint64(interval)

Приведенный выше код работает, но мне интересно, могу ли я на него положиться. Я удивлен, будучи новичком в Go, что после приведения отрицательного числа к uint64 оно остается отрицательным - я планировал вычесть теперь положительное значение (после приведения), чтобы получить то, что я хотел.


person Jeff    schedule 12.06.2018    source источник
comment
Ну, во-первых, uint(a) — это не приведение типов в Go, а преобразование типов, которое просто переинтерпретирует биты a как uint. Теперь uint — это просто битовые шаблоны, которые не являются ни положительными, ни отрицательными, и добавление их — это битовая операция, приводящая к новому битовому шаблону, если вы интерпретируете этот битовый шаблон как целое число со знаком, тогда вы увидите, что происходит вычитание, потому что, ну, вот как два дополняют произведения. Совет: не используйте uint для чисел, если вы не чувствуете себя комфортно с дополнением до двух.   -  person Volker    schedule 12.06.2018
comment
Формально ответ был бы таким: Да, вы можете полагаться на язык, соответствующий спецификации языка. что немного бессмысленный ответ.   -  person Volker    schedule 12.06.2018
comment
В Go вообще нет кастинга.   -  person Flimzy    schedule 12.06.2018


Ответы (1)


Преобразование числа со знаком в число без знака не останется отрицательным, оно не может, так как допустимый диапазон типов без знака не включает отрицательные числа. Если вы напечатаете 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
comment
Существующий код уже использует uint64 для представления времени. Я не уверен в мотивации этого или в том, безопасно ли изменять типы переменных. - person Jeff; 12.06.2018
comment
@Jeff Тогда вы можете продолжать использовать тип uint64, но затем делать вычитание, а не сложение, например interval := uint64(3600), а затем endTime -= interval. - person icza; 12.06.2018
comment
код несколько упрощен. интервал может быть положительным или отрицательным значением, указанным пользователем в командной строке. Если оно отрицательное, оно добавляется ко времени окончания, чтобы получить фактическое время начала; если он положительный, он добавляется к времени начала, чтобы получить время окончания. поэтому я могу упустить лучший способ сделать это, но именно поэтому я добавил интервал. - person Jeff; 12.06.2018
comment
@icza Не будет ли переполнения при преобразовании числа со знаком в беззнаковое? - person Metalhead1247; 12.06.2018
comment
@ Metalhead1247 Преобразование не приводит к переполнению. Преобразование не изменяет представление памяти, а только тип. Переполнение может быть результатом какой-то арифметической операции, преобразование таковым не является. - person icza; 12.06.2018